webpack.config.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. const path = require('path');
  2. const webpack = require('webpack');
  3. const CopyPlugin = require('copy-webpack-plugin');
  4. const ManifestPlugin = require('webpack-manifest-plugin');
  5. const VersionPlugin = require('./build/version_plugin');
  6. const ExtractTextPlugin = require('extract-text-webpack-plugin');
  7. const webJsOptions = {
  8. babelrc: false,
  9. presets: [
  10. [
  11. '@babel/preset-env',
  12. {
  13. modules: false,
  14. useBuiltIns: 'entry'
  15. }
  16. ]
  17. ],
  18. // yo-yoify converts html template strings to direct dom api calls
  19. plugins: [
  20. 'yo-yoify',
  21. ['@babel/plugin-proposal-class-properties', { loose: false }]
  22. ]
  23. };
  24. const serviceWorker = {
  25. target: 'webworker',
  26. entry: {
  27. serviceWorker: './app/serviceWorker.js'
  28. },
  29. output: {
  30. filename: '[name].js',
  31. path: path.resolve(__dirname, 'dist'),
  32. publicPath: '/'
  33. },
  34. devtool: 'source-map'
  35. };
  36. const web = {
  37. target: 'web',
  38. entry: {
  39. // babel-polyfill and fluent are directly included in vendor
  40. // because they are not explicitly referenced by app
  41. vendor: ['@babel/polyfill', 'fluent'], //TODO: remove @babel/polyfill
  42. app: ['./app/main.js'],
  43. android: ['./android/android.js'],
  44. ios: ['./ios/ios.js']
  45. },
  46. output: {
  47. filename: '[name].[hash:8].js',
  48. path: path.resolve(__dirname, 'dist'),
  49. publicPath: '/'
  50. },
  51. module: {
  52. rules: [
  53. {
  54. test: /\.js$/,
  55. oneOf: [
  56. {
  57. include: [require.resolve('./assets/cryptofill')],
  58. use: [
  59. {
  60. loader: 'file-loader',
  61. options: {
  62. name: '[name].[hash:8].[ext]'
  63. }
  64. }
  65. ]
  66. },
  67. {
  68. // fluent gets exposed as a global so that each language script
  69. // can load independently and share it.
  70. include: [path.dirname(require.resolve('fluent'))],
  71. use: [
  72. {
  73. loader: 'expose-loader',
  74. options: 'fluent'
  75. },
  76. {
  77. loader: 'babel-loader',
  78. options: webJsOptions
  79. }
  80. ]
  81. },
  82. {
  83. loader: 'babel-loader',
  84. include: [
  85. path.resolve(__dirname, 'app'),
  86. path.resolve(__dirname, 'common'),
  87. // some dependencies need to get re-babeled because we
  88. // have different targets than their default configs
  89. path.resolve(__dirname, 'node_modules/testpilot-ga/src'),
  90. path.resolve(__dirname, 'node_modules/fluent-intl-polyfill'),
  91. path.resolve(__dirname, 'node_modules/intl-pluralrules')
  92. ],
  93. options: webJsOptions
  94. },
  95. {
  96. // Strip asserts from our deps, mainly choojs family
  97. include: [path.resolve(__dirname, 'node_modules')],
  98. exclude: [path.resolve(__dirname, 'node_modules/crc')],
  99. loader: 'webpack-unassert-loader'
  100. }
  101. ]
  102. },
  103. {
  104. test: /\.(png|jpg)$/,
  105. loader: 'file-loader',
  106. options: {
  107. name: '[name].[hash:8].[ext]'
  108. }
  109. },
  110. {
  111. test: /\.svg$/,
  112. use: [
  113. {
  114. loader: 'file-loader',
  115. options: {
  116. name: '[name].[hash:8].[ext]'
  117. }
  118. },
  119. {
  120. loader: 'svgo-loader',
  121. options: {
  122. plugins: [
  123. { removeViewBox: false }, // true causes stretched images
  124. { convertStyleToAttrs: true }, // for CSP, no unsafe-eval
  125. { removeTitle: true } // for smallness
  126. ]
  127. }
  128. }
  129. ]
  130. },
  131. {
  132. // creates style.css with all styles
  133. test: /\.css$/,
  134. use: ExtractTextPlugin.extract({
  135. use: [
  136. {
  137. loader: 'css-loader',
  138. options: {
  139. importLoaders: 1
  140. }
  141. },
  142. 'postcss-loader'
  143. ]
  144. })
  145. },
  146. {
  147. // creates a js script for each ftl
  148. test: /\.ftl$/,
  149. use: [
  150. {
  151. loader: 'file-loader',
  152. options: {
  153. name: '[path][name].[hash:8].js'
  154. }
  155. },
  156. 'extract-loader',
  157. './build/fluent_loader'
  158. ]
  159. },
  160. {
  161. // creates test.js for /test
  162. test: require.resolve('./test/frontend/index.js'),
  163. use: ['babel-loader', 'val-loader']
  164. },
  165. {
  166. // loads all assets from assets/ for use by common/assets.js
  167. test: require.resolve('./build/generate_asset_map.js'),
  168. use: ['babel-loader', 'val-loader']
  169. },
  170. {
  171. // loads all the ftl from public/locales for use by common/locales.js
  172. test: require.resolve('./build/generate_l10n_map.js'),
  173. use: ['babel-loader', 'val-loader']
  174. }
  175. ]
  176. },
  177. plugins: [
  178. new CopyPlugin([
  179. {
  180. context: 'public',
  181. from: '*.*'
  182. },
  183. {
  184. context: 'android/app/src/main/assets',
  185. from: '*.*',
  186. to: 'android_asset'
  187. }
  188. ]),
  189. new webpack.IgnorePlugin(/\.\.\/dist/), // used in common/*.js
  190. new webpack.IgnorePlugin(/require-from-string/), // used in common/locales.js
  191. new webpack.HashedModuleIdsPlugin(),
  192. new ExtractTextPlugin({
  193. filename: '[name].[hash:8].css'
  194. }),
  195. new VersionPlugin(),
  196. new ManifestPlugin() // used by server side to resolve hashed assets
  197. ],
  198. devtool: 'source-map',
  199. devServer: {
  200. before:
  201. process.env.NODE_ENV === 'development' && require('./server/bin/dev'),
  202. compress: true,
  203. hot: false,
  204. host: '0.0.0.0',
  205. proxy: {
  206. '/api/ws': {
  207. target: 'ws://localhost:8081',
  208. ws: true,
  209. secure: false
  210. }
  211. }
  212. }
  213. };
  214. module.exports = (env, argv) => {
  215. const mode = argv.mode || 'production';
  216. console.error(`mode: ${mode}`);
  217. process.env.NODE_ENV = web.mode = serviceWorker.mode = mode;
  218. if (mode === 'development') {
  219. // istanbul instruments the source for code coverage
  220. webJsOptions.plugins.push('istanbul');
  221. web.entry.tests = ['./test/frontend/index.js'];
  222. }
  223. return [web, serviceWorker];
  224. };