webpack.config.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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. 'underscore',
  62. 'ios-device-list'
  63. ],
  64. // css
  65. // NOTE: this will also create an empty 'sentry.js' file
  66. // TODO: figure out how to not generate this
  67. 'sentry': 'less/sentry.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')
  75. return;
  76. // Django locale names are "zh_CN", moment's are "zh-cn"
  77. var normalizedLocale = locale.toLowerCase().replace('_', '-');
  78. entry['locale/' + normalizedLocale] = [
  79. 'moment/locale/' + normalizedLocale,
  80. 'sentry-locale/' + locale + '/LC_MESSAGES/django.po' // relative to static/sentry
  81. ];
  82. localeEntries.push('locale/' + normalizedLocale);
  83. });
  84. var config = {
  85. entry: entry,
  86. context: path.join(__dirname, staticPrefix),
  87. module: {
  88. loaders: [
  89. {
  90. test: /\.jsx?$/,
  91. loader: 'babel-loader',
  92. include: path.join(__dirname, staticPrefix),
  93. exclude: /(vendor|node_modules|dist)/,
  94. query: babelQuery
  95. },
  96. {
  97. test: /\.po$/,
  98. loader: 'po-catalog-loader',
  99. query: {
  100. referenceExtensions: ['.js', '.jsx'],
  101. domain: 'sentry'
  102. }
  103. },
  104. {
  105. test: /\.json$/,
  106. loader: 'json-loader'
  107. },
  108. {
  109. test: /\.less$/,
  110. include: path.join(__dirname, staticPrefix),
  111. loader: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader')
  112. },
  113. {
  114. test: /\.(woff|woff2|ttf|eot|svg|png|gif|ico|jpg)($|\?)/,
  115. loader: 'file-loader?name=' + '[name].[ext]'
  116. }
  117. ],
  118. noParse: [
  119. // don't parse known, pre-built javascript files (improves webpack perf)
  120. path.join(__dirname, 'node_modules', 'jquery', 'dist', 'jquery.js'),
  121. path.join(__dirname, 'node_modules', 'jed', 'jed.js'),
  122. path.join(__dirname, 'node_modules', 'marked', 'lib', 'marked.js')
  123. ],
  124. },
  125. plugins: [
  126. new webpack.optimize.CommonsChunkPlugin({
  127. names: localeEntries.concat(['vendor']) // 'vendor' must be last entry
  128. }),
  129. new webpack.optimize.DedupePlugin(),
  130. new webpack.ProvidePlugin({
  131. $: 'jquery',
  132. jQuery: 'jquery',
  133. 'window.jQuery': 'jquery',
  134. 'root.jQuery': 'jquery',
  135. Raven: 'raven-js',
  136. underscore: 'underscore',
  137. _: 'underscore'
  138. }),
  139. new ExtractTextPlugin('[name].css'),
  140. new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // ignore moment.js locale files
  141. new webpack.DefinePlugin({
  142. 'process.env': {
  143. NODE_ENV: JSON.stringify(process.env.NODE_ENV)
  144. }
  145. }),
  146. // restrict translation files pulled into dist/app.js to only those specified
  147. // in locale/catalogs.json
  148. new webpack.ContextReplacementPlugin(
  149. /locale$/,
  150. path.join(__dirname, 'src', 'sentry', 'locale', path.sep),
  151. true,
  152. new RegExp('(' + localeCatalog.supported_locales.join('|') + ')\/.*\\.po$')
  153. )
  154. ],
  155. resolve: {
  156. alias: {
  157. 'flot': path.join(__dirname, staticPrefix, 'vendor', 'jquery-flot'),
  158. 'flot-tooltip': path.join(__dirname, staticPrefix, 'vendor', 'jquery-flot-tooltip'),
  159. 'sentry-locale': path.join(__dirname, 'src', 'sentry', 'locale')
  160. },
  161. modulesDirectories: [path.join(__dirname, staticPrefix), 'node_modules'],
  162. extensions: ['', '.jsx', '.js', '.json']
  163. },
  164. output: {
  165. path: distPath,
  166. filename: '[name].js',
  167. libraryTarget: 'var',
  168. library: 'exports',
  169. sourceMapFilename: '[name].js.map',
  170. },
  171. devtool: IS_PRODUCTION ?
  172. '#source-map' :
  173. '#cheap-module-eval-source-map'
  174. };
  175. if (IS_PRODUCTION) {
  176. // This compression-webpack-plugin generates pre-compressed files
  177. // ending in .gz, to be picked up and served by our internal static media
  178. // server as well as nginx when paired with the gzip_static module.
  179. config.plugins.push(new (require('compression-webpack-plugin'))({
  180. algorithm: function(buffer, options, callback) {
  181. require('zlib').gzip(buffer, callback);
  182. },
  183. regExp: /\.(js|map|css|svg|html|txt|ico|eot|ttf)$/,
  184. }));
  185. // Disable annoying UglifyJS warnings that pollute Travis log output
  186. config.plugins.push(new webpack.optimize.UglifyJsPlugin({
  187. compress: {
  188. warnings: false
  189. }
  190. }));
  191. }
  192. module.exports = config;