webpack.config.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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 = path.join(__dirname, 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 REFRESH = process.env.WEBPACK_LIVERELOAD === '1';
  15. var babelConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '.babelrc')));
  16. babelConfig.cacheDirectory = true;
  17. // only extract po files if we need to
  18. if (process.env.SENTRY_EXTRACT_TRANSLATIONS === '1') {
  19. babelConfig.plugins.push('babel-gettext-extractor', {
  20. fileName: 'build/javascript.po',
  21. baseDirectory: path.join(__dirname, 'src/sentry'),
  22. functionNames: {
  23. gettext: ['msgid'],
  24. ngettext: ['msgid', 'msgid_plural', 'count'],
  25. gettextComponentTemplate: ['msgid'],
  26. t: ['msgid'],
  27. tn: ['msgid', 'msgid_plural', 'count'],
  28. tct: ['msgid']
  29. }
  30. });
  31. }
  32. var entry = {
  33. // js
  34. app: 'app',
  35. vendor: [
  36. 'babel-polyfill',
  37. 'bootstrap/js/dropdown',
  38. 'bootstrap/js/tab',
  39. 'bootstrap/js/tooltip',
  40. 'bootstrap/js/alert',
  41. 'crypto-js/md5',
  42. 'jed',
  43. 'jquery',
  44. 'marked',
  45. 'moment',
  46. 'moment-timezone',
  47. 'raven-js',
  48. 'react',
  49. 'react-dom',
  50. 'react-dom/server',
  51. 'react-document-title',
  52. 'react-router',
  53. 'react-bootstrap/lib/Modal',
  54. 'react-sparklines',
  55. 'reflux',
  56. 'select2',
  57. 'flot/jquery.flot',
  58. 'flot/jquery.flot.stack',
  59. 'flot/jquery.flot.time',
  60. 'flot-tooltip/jquery.flot.tooltip',
  61. 'vendor/simple-slider/simple-slider',
  62. 'underscore',
  63. 'ios-device-list'
  64. ],
  65. // css
  66. // NOTE: this will also create an empty 'sentry.js' file
  67. // TODO: figure out how to not generate this
  68. sentry: 'less/sentry.less',
  69. // debug toolbar
  70. debugger: 'less/debugger.less'
  71. };
  72. // dynamically iterate over locale files and add to `entry` config
  73. var localeCatalogPath = path.join(__dirname, 'src', 'sentry', 'locale', 'catalogs.json');
  74. var localeCatalog = JSON.parse(fs.readFileSync(localeCatalogPath, 'utf8'));
  75. var localeEntries = [];
  76. localeCatalog.supported_locales.forEach(function(locale) {
  77. if (locale === 'en') return;
  78. // Django locale names are "zh_CN", moment's are "zh-cn"
  79. var normalizedLocale = locale.toLowerCase().replace('_', '-');
  80. entry['locale/' + normalizedLocale] = [
  81. 'moment/locale/' + normalizedLocale,
  82. 'sentry-locale/' + locale + '/LC_MESSAGES/django.po' // relative to static/sentry
  83. ];
  84. localeEntries.push('locale/' + normalizedLocale);
  85. });
  86. var config = {
  87. entry: entry,
  88. context: path.join(__dirname, staticPrefix),
  89. module: {
  90. rules: [
  91. {
  92. test: /\.jsx?$/,
  93. loader: 'babel-loader',
  94. include: path.join(__dirname, staticPrefix),
  95. exclude: /(vendor|node_modules|dist)/,
  96. query: babelConfig
  97. },
  98. {
  99. test: /\.po$/,
  100. loader: 'po-catalog-loader',
  101. query: {
  102. referenceExtensions: ['.js', '.jsx'],
  103. domain: 'sentry'
  104. }
  105. },
  106. {
  107. test: /\.json$/,
  108. loader: 'json-loader'
  109. },
  110. {
  111. test: /\.less$/,
  112. include: path.join(__dirname, staticPrefix),
  113. loader: ExtractTextPlugin.extract({
  114. fallbackLoader: 'style-loader?sourceMap=false',
  115. loader: 'css-loader' + (IS_PRODUCTION ? '?minimize=true' : '') + '!less-loader'
  116. })
  117. },
  118. {
  119. test: /\.(woff|woff2|ttf|eot|svg|png|gif|ico|jpg)($|\?)/,
  120. loader: 'file-loader?name=' + '[name].[ext]'
  121. }
  122. ],
  123. noParse: [
  124. // don't parse known, pre-built javascript files (improves webpack perf)
  125. /dist\/jquery\.js/,
  126. /jed\/jed\.js/,
  127. /marked\/lib\/marked\.js/
  128. ]
  129. },
  130. plugins: [
  131. new webpack.optimize.CommonsChunkPlugin({
  132. names: localeEntries.concat(['vendor']) // 'vendor' must be last entry
  133. }),
  134. new webpack.ProvidePlugin({
  135. $: 'jquery',
  136. jQuery: 'jquery',
  137. 'window.jQuery': 'jquery',
  138. 'root.jQuery': 'jquery',
  139. Raven: 'raven-js',
  140. ReactDOM: 'react-dom',
  141. underscore: 'underscore',
  142. _: 'underscore'
  143. }),
  144. new ExtractTextPlugin('[name].css'),
  145. new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // ignore moment.js locale files
  146. new webpack.DefinePlugin({
  147. 'process.env': {
  148. NODE_ENV: JSON.stringify(process.env.NODE_ENV)
  149. }
  150. }),
  151. // restrict translation files pulled into dist/app.js to only those specified
  152. // in locale/catalogs.json
  153. new webpack.ContextReplacementPlugin(
  154. /locale$/,
  155. path.join(__dirname, 'src', 'sentry', 'locale', path.sep),
  156. true,
  157. new RegExp('(' + localeCatalog.supported_locales.join('|') + ')\/.*\\.po$')
  158. )
  159. ],
  160. resolve: {
  161. alias: {
  162. flot: path.join(__dirname, staticPrefix, 'vendor', 'jquery-flot'),
  163. 'flot-tooltip': path.join(__dirname, staticPrefix, 'vendor', 'jquery-flot-tooltip'),
  164. 'sentry-locale': path.join(__dirname, 'src', 'sentry', 'locale')
  165. },
  166. modules: [path.join(__dirname, staticPrefix), 'node_modules'],
  167. extensions: ['*', '.jsx', '.js', '.json']
  168. },
  169. output: {
  170. path: distPath,
  171. filename: '[name].js',
  172. libraryTarget: 'var',
  173. library: 'exports',
  174. sourceMapFilename: '[name].js.map'
  175. },
  176. devtool: IS_PRODUCTION ? '#source-map' : '#cheap-source-map'
  177. };
  178. // We only need to use the webpack-livereload-plugin for development builds. Production
  179. // builds don't have this module.
  180. if (!IS_PRODUCTION && REFRESH) {
  181. config.plugins.push(
  182. new (require('webpack-livereload-plugin'))({appendScriptTag: true})
  183. );
  184. }
  185. if (IS_PRODUCTION) {
  186. // This compression-webpack-plugin generates pre-compressed files
  187. // ending in .gz, to be picked up and served by our internal static media
  188. // server as well as nginx when paired with the gzip_static module.
  189. config.plugins.push(
  190. new (require('compression-webpack-plugin'))({
  191. algorithm: function(buffer, options, callback) {
  192. require('zlib').gzip(buffer, callback);
  193. },
  194. regExp: /\.(js|map|css|svg|html|txt|ico|eot|ttf)$/
  195. })
  196. );
  197. // Disable annoying UglifyJS warnings that pollute Travis log output
  198. // NOTE: This breaks -p in webpack 2. Must call webpack w/ NODE_ENV=production for minification.
  199. config.plugins.push(
  200. new webpack.optimize.UglifyJsPlugin({
  201. compress: {
  202. warnings: false
  203. },
  204. // https://github.com/webpack/webpack/blob/951a7603d279c93c936e4b8b801a355dc3e26292/bin/convert-argv.js#L442
  205. sourceMap: config.devtool &&
  206. (config.devtool.indexOf('sourcemap') >= 0 ||
  207. config.devtool.indexOf('source-map') >= 0)
  208. })
  209. );
  210. }
  211. module.exports = config;