webpack.config.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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. LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
  8. var staticPrefix = 'src/sentry/static/sentry',
  9. distPath = path.join(__dirname, staticPrefix, 'dist');
  10. // this is set by setup.py sdist
  11. if (process.env.SENTRY_STATIC_DIST_PATH) {
  12. distPath = process.env.SENTRY_STATIC_DIST_PATH;
  13. }
  14. var IS_PRODUCTION = process.env.NODE_ENV === 'production';
  15. var IS_TEST = process.env.NODE_ENV === 'TEST' || process.env.TEST_SUITE;
  16. var REFRESH = process.env.WEBPACK_LIVERELOAD === '1';
  17. var babelConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '.babelrc')));
  18. babelConfig.cacheDirectory = true;
  19. // only extract po files if we need to
  20. if (process.env.SENTRY_EXTRACT_TRANSLATIONS === '1') {
  21. babelConfig.plugins.push('babel-gettext-extractor', {
  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-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',
  51. 'react-dom',
  52. 'react-dom/server',
  53. 'react-document-title',
  54. 'react-router',
  55. 'react-bootstrap/lib/Modal',
  56. 'react-sparklines',
  57. 'reflux',
  58. 'select2',
  59. 'vendor/simple-slider/simple-slider',
  60. 'ios-device-list'
  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. // debug toolbar
  67. debugger: 'less/debugger.less'
  68. };
  69. // dynamically iterate over locale files and add to `entry` config
  70. var localeCatalogPath = path.join(__dirname, 'src', 'sentry', 'locale', 'catalogs.json');
  71. var localeCatalog = JSON.parse(fs.readFileSync(localeCatalogPath, 'utf8'));
  72. var localeEntries = [];
  73. localeCatalog.supported_locales.forEach(function(locale) {
  74. if (locale === 'en') return;
  75. // Django locale names are "zh_CN", moment's are "zh-cn"
  76. var normalizedLocale = locale.toLowerCase().replace('_', '-');
  77. entry['locale/' + normalizedLocale] = [
  78. 'moment/locale/' + normalizedLocale,
  79. 'sentry-locale/' + locale + '/LC_MESSAGES/django.po' // relative to static/sentry
  80. ];
  81. localeEntries.push('locale/' + normalizedLocale);
  82. });
  83. var config = {
  84. entry: entry,
  85. context: path.join(__dirname, staticPrefix),
  86. module: {
  87. rules: [
  88. {
  89. test: /\.jsx?$/,
  90. loader: 'babel-loader',
  91. include: path.join(__dirname, staticPrefix),
  92. exclude: /(vendor|node_modules|dist)/,
  93. query: babelConfig
  94. },
  95. {
  96. test: /\.po$/,
  97. loader: 'po-catalog-loader',
  98. query: {
  99. referenceExtensions: ['.js', '.jsx'],
  100. domain: 'sentry'
  101. }
  102. },
  103. {
  104. test: /\.json$/,
  105. loader: 'json-loader'
  106. },
  107. {
  108. test: /\.less$/,
  109. include: path.join(__dirname, staticPrefix),
  110. loader: ExtractTextPlugin.extract({
  111. fallbackLoader: 'style-loader?sourceMap=false',
  112. loader: 'css-loader' + (IS_PRODUCTION ? '?minimize=true' : '') + '!less-loader'
  113. })
  114. },
  115. {
  116. test: /\.(woff|woff2|ttf|eot|svg|png|gif|ico|jpg)($|\?)/,
  117. loader: 'file-loader?name=' + '[name].[ext]'
  118. }
  119. ],
  120. noParse: [
  121. // don't parse known, pre-built javascript files (improves webpack perf)
  122. /dist\/jquery\.js/,
  123. /jed\/jed\.js/,
  124. /marked\/lib\/marked\.js/
  125. ]
  126. },
  127. plugins: [
  128. new LodashModuleReplacementPlugin({
  129. collections: true,
  130. currying: true, // these are enabled to support lodash/fp/ features
  131. flattening: true // used by a dependency of react-mentions
  132. }),
  133. new webpack.optimize.CommonsChunkPlugin({
  134. names: localeEntries.concat(['vendor']) // 'vendor' must be last entry
  135. }),
  136. new webpack.ProvidePlugin({
  137. $: 'jquery',
  138. jQuery: 'jquery',
  139. 'window.jQuery': 'jquery',
  140. 'root.jQuery': 'jquery',
  141. Raven: 'raven-js'
  142. }),
  143. new ExtractTextPlugin('[name].css'),
  144. new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // ignore moment.js locale files
  145. new webpack.DefinePlugin({
  146. 'process.env': {
  147. NODE_ENV: JSON.stringify(process.env.NODE_ENV)
  148. }
  149. }),
  150. // restrict translation files pulled into dist/app.js to only those specified
  151. // in locale/catalogs.json
  152. new webpack.ContextReplacementPlugin(
  153. /locale$/,
  154. path.join(__dirname, 'src', 'sentry', 'locale', path.sep),
  155. true,
  156. new RegExp('(' + localeCatalog.supported_locales.join('|') + ')\/.*\\.po$')
  157. )
  158. ],
  159. resolve: {
  160. alias: {
  161. 'sentry-locale': path.join(__dirname, 'src', 'sentry', 'locale'),
  162. 'integration-docs-platforms': IS_TEST
  163. ? path.join(__dirname, 'tests/fixtures/_platforms.json')
  164. : path.join(__dirname, 'src/sentry/integration-docs/_platforms.json')
  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;