webpack.config.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /*eslint-env node*/
  2. /*eslint no-var:0*/
  3. var path = require('path'),
  4. fs = require('fs'),
  5. webpack = require('webpack'),
  6. ExtractTextPlugin = require('extract-text-webpack-plugin');
  7. var staticPrefix = 'src/sentry/static/sentry',
  8. distPath = staticPrefix + '/dist';
  9. // this is set by setup.py sdist
  10. if (process.env.SENTRY_STATIC_DIST_PATH) {
  11. distPath = process.env.SENTRY_STATIC_DIST_PATH;
  12. }
  13. var IS_PRODUCTION = process.env.NODE_ENV === 'production';
  14. var babelQuery = {
  15. plugins: [],
  16. extra: {}
  17. };
  18. // only extract po files if we need to
  19. if (process.env.SENTRY_EXTRACT_TRANSLATIONS === '1') {
  20. babelQuery.plugins.push('babel-gettext-extractor');
  21. babelQuery.extra.gettext = {
  22. fileName: 'build/javascript.po',
  23. baseDirectory: path.join(__dirname, 'src/sentry'),
  24. functionNames: {
  25. gettext: ['msgid'],
  26. ngettext: ['msgid', 'msgid_plural', 'count'],
  27. gettextComponentTemplate: ['msgid'],
  28. t: ['msgid'],
  29. tn: ['msgid', 'msgid_plural', 'count'],
  30. tct: ['msgid']
  31. },
  32. };
  33. }
  34. var entry = {
  35. // js
  36. 'app': 'app',
  37. 'vendor': [
  38. 'babel-core/polyfill',
  39. 'bootstrap/js/dropdown',
  40. 'bootstrap/js/tab',
  41. 'bootstrap/js/tooltip',
  42. 'bootstrap/js/alert',
  43. 'crypto-js/md5',
  44. 'jed',
  45. 'jquery',
  46. 'marked',
  47. 'moment',
  48. 'moment-timezone',
  49. 'raven-js',
  50. 'react-document-title',
  51. 'react-router',
  52. 'react-bootstrap/lib/Modal',
  53. 'react-sparklines',
  54. 'reflux',
  55. 'select2',
  56. 'flot/jquery.flot',
  57. 'flot/jquery.flot.stack',
  58. 'flot/jquery.flot.time',
  59. 'flot-tooltip/jquery.flot.tooltip',
  60. 'vendor/simple-slider/simple-slider'
  61. ],
  62. // css
  63. // NOTE: this will also create an empty 'sentry.js' file
  64. // TODO: figure out how to not generate this
  65. 'sentry': 'less/sentry.less'
  66. };
  67. // dynamically iterate over locale files and add to `entry` config
  68. var localeCatalogPath = path.join(__dirname, 'src', 'sentry', 'locale', 'catalogs.json');
  69. var localeCatalog = JSON.parse(fs.readFileSync(localeCatalogPath, 'utf8'));
  70. var localeEntries = [];
  71. localeCatalog.supported_locales.forEach(function (locale) {
  72. if (locale === 'en')
  73. return;
  74. // Django locale names are "zh_CN", moment's are "zh-cn"
  75. var normalizedLocale = locale.toLowerCase().replace('_', '-');
  76. entry['locale/' + normalizedLocale] = [
  77. 'moment/locale/' + normalizedLocale,
  78. 'sentry-locale/' + locale + '/LC_MESSAGES/django.po' // relative to static/sentry
  79. ];
  80. localeEntries.push('locale/' + normalizedLocale);
  81. });
  82. var config = {
  83. entry: entry,
  84. context: path.join(__dirname, staticPrefix),
  85. module: {
  86. loaders: [
  87. {
  88. test: /\.jsx?$/,
  89. loader: 'babel-loader',
  90. include: path.join(__dirname, staticPrefix),
  91. exclude: /(vendor|node_modules|dist)/,
  92. query: babelQuery
  93. },
  94. {
  95. test: /\.po$/,
  96. loader: 'po-catalog-loader',
  97. query: {
  98. referenceExtensions: ['.js', '.jsx'],
  99. domain: 'sentry'
  100. }
  101. },
  102. {
  103. test: /\.json$/,
  104. loader: 'json-loader'
  105. },
  106. {
  107. test: /\.less$/,
  108. include: path.join(__dirname, staticPrefix),
  109. loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader')
  110. },
  111. {
  112. test: /\.(woff|woff2|ttf|eot|svg|png|gif|ico|jpg)($|\?)/,
  113. loader: 'file-loader?name=' + '[name].[ext]'
  114. }
  115. ],
  116. noParse: [
  117. // don't parse known, pre-built javascript files (improves webpack perf)
  118. path.join(__dirname, 'node_modules', 'jquery', 'dist', 'jquery.js'),
  119. path.join(__dirname, 'node_modules', 'jed', 'jed.js'),
  120. path.join(__dirname, 'node_modules', 'marked', 'lib', 'marked.js')
  121. ],
  122. },
  123. plugins: [
  124. new webpack.optimize.CommonsChunkPlugin({
  125. names: localeEntries.concat(['vendor']) // 'vendor' must be last entry
  126. }),
  127. new webpack.optimize.DedupePlugin(),
  128. new webpack.ProvidePlugin({
  129. $: 'jquery',
  130. jQuery: 'jquery',
  131. 'window.jQuery': 'jquery',
  132. 'root.jQuery': 'jquery',
  133. Raven: 'raven-js'
  134. }),
  135. new ExtractTextPlugin('[name].css'),
  136. new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // ignore moment.js locale files
  137. new webpack.DefinePlugin({
  138. 'process.env': {
  139. NODE_ENV: JSON.stringify(process.env.NODE_ENV)
  140. }
  141. }),
  142. // restrict translation files pulled into dist/app.js to only those specified
  143. // in locale/catalogs.json
  144. new webpack.ContextReplacementPlugin(
  145. /locale$/,
  146. path.join(__dirname, 'src', 'sentry', 'locale', path.sep),
  147. true,
  148. new RegExp('(' + localeCatalog.supported_locales.join('|') + ')\/.*\\.po$')
  149. )
  150. ],
  151. resolve: {
  152. alias: {
  153. 'flot': path.join(__dirname, staticPrefix, 'vendor', 'jquery-flot'),
  154. 'flot-tooltip': path.join(__dirname, staticPrefix, 'vendor', 'jquery-flot-tooltip'),
  155. 'sentry-locale': path.join(__dirname, 'src', 'sentry', 'locale')
  156. },
  157. modulesDirectories: [path.join(__dirname, staticPrefix), 'node_modules'],
  158. extensions: ['', '.jsx', '.js', '.json']
  159. },
  160. output: {
  161. path: distPath,
  162. filename: '[name].js',
  163. libraryTarget: 'var',
  164. library: 'exports',
  165. sourceMapFilename: '[name].js.map',
  166. },
  167. devtool: IS_PRODUCTION ?
  168. '#source-map' :
  169. '#cheap-module-eval-source-map'
  170. };
  171. // This compression-webpack-plugin generates pre-compressed files
  172. // ending in .gz, to be picked up and served by our internal static media
  173. // server as well as nginx when paired with the gzip_static module.
  174. if (IS_PRODUCTION) {
  175. config.plugins.push(new (require('compression-webpack-plugin'))({
  176. // zopfli gives us a better gzip compression
  177. // See: http://googledevelopers.blogspot.com/2013/02/compress-data-more-densely-with-zopfli.html
  178. algorithm: 'zopfli',
  179. regExp: /\.(js|map|css|svg|html|txt|ico|eot|ttf)$/,
  180. }));
  181. }
  182. module.exports = config;