webpack.config.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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 AndroidIndexPlugin = require('./build/android_index_plugin');
  7. const ExtractTextPlugin = require('extract-text-webpack-plugin');
  8. const webJsOptions = {
  9. babelrc: false,
  10. presets: [
  11. [
  12. '@babel/preset-env',
  13. {
  14. useBuiltIns: 'entry',
  15. corejs: 3
  16. }
  17. ]
  18. ],
  19. plugins: [
  20. '@babel/plugin-syntax-dynamic-import',
  21. 'module:nanohtml',
  22. ['@babel/plugin-proposal-class-properties', { loose: false }]
  23. ]
  24. };
  25. const serviceWorker = {
  26. target: 'webworker',
  27. entry: {
  28. serviceWorker: './app/serviceWorker.js'
  29. },
  30. output: {
  31. filename: '[name].js',
  32. path: path.resolve(__dirname, 'dist'),
  33. publicPath: '/'
  34. },
  35. devtool: 'source-map',
  36. module: {
  37. rules: [
  38. {
  39. test: /\.(png|jpg)$/,
  40. loader: 'file-loader',
  41. options: {
  42. name: '[name].[contenthash:8].[ext]'
  43. }
  44. },
  45. {
  46. test: /\.svg$/,
  47. use: [
  48. {
  49. loader: 'file-loader',
  50. options: {
  51. name: '[name].[contenthash:8].[ext]'
  52. }
  53. },
  54. {
  55. loader: 'svgo-loader',
  56. options: {
  57. plugins: [
  58. { removeViewBox: false }, // true causes stretched images
  59. { convertStyleToAttrs: true }, // for CSP, no unsafe-eval
  60. { removeTitle: true } // for smallness
  61. ]
  62. }
  63. }
  64. ]
  65. },
  66. {
  67. // loads all assets from assets/ for use by common/assets.js
  68. test: require.resolve('./common/generate_asset_map.js'),
  69. use: ['babel-loader', 'val-loader']
  70. }
  71. ]
  72. },
  73. plugins: [new webpack.IgnorePlugin(/\.\.\/dist/)]
  74. };
  75. const web = {
  76. target: 'web',
  77. entry: {
  78. app: ['./app/main.js'],
  79. android: ['./android/android.js'],
  80. ios: ['./ios/ios.js']
  81. },
  82. output: {
  83. chunkFilename: '[name].[contenthash:8].js',
  84. filename: '[name].[contenthash:8].js',
  85. path: path.resolve(__dirname, 'dist')
  86. },
  87. module: {
  88. rules: [
  89. {
  90. test: /\.js$/,
  91. oneOf: [
  92. {
  93. loader: 'babel-loader',
  94. include: [
  95. path.resolve(__dirname, 'app'),
  96. path.resolve(__dirname, 'common'),
  97. // some dependencies need to get re-babeled because we
  98. // have different targets than their default configs
  99. path.resolve(
  100. __dirname,
  101. 'node_modules/@dannycoates/webcrypto-liner'
  102. ),
  103. path.resolve(__dirname, 'node_modules/@fluent'),
  104. path.resolve(__dirname, 'node_modules/intl-pluralrules')
  105. ],
  106. options: webJsOptions
  107. },
  108. {
  109. // Strip asserts from our deps, mainly choojs family
  110. include: [path.resolve(__dirname, 'node_modules')],
  111. exclude: [
  112. path.resolve(__dirname, 'node_modules/crc'),
  113. path.resolve(__dirname, 'node_modules/@fluent'),
  114. path.resolve(__dirname, 'node_modules/@sentry'),
  115. path.resolve(__dirname, 'node_modules/tslib'),
  116. path.resolve(__dirname, 'node_modules/webcrypto-core')
  117. ],
  118. loader: 'webpack-unassert-loader'
  119. }
  120. ]
  121. },
  122. {
  123. test: /\.(png|jpg)$/,
  124. loader: 'file-loader',
  125. options: {
  126. name: '[name].[contenthash:8].[ext]'
  127. }
  128. },
  129. {
  130. test: /\.svg$/,
  131. use: [
  132. {
  133. loader: 'file-loader',
  134. options: {
  135. name: '[name].[contenthash:8].[ext]'
  136. }
  137. },
  138. {
  139. loader: 'svgo-loader',
  140. options: {
  141. plugins: [
  142. { cleanupIDs: false },
  143. { removeViewBox: false }, // true causes stretched images
  144. { convertStyleToAttrs: true }, // for CSP, no unsafe-eval
  145. { removeTitle: true } // for smallness
  146. ]
  147. }
  148. }
  149. ]
  150. },
  151. {
  152. // creates style.css with all styles
  153. test: /\.css$/,
  154. use: ExtractTextPlugin.extract({
  155. use: [
  156. {
  157. loader: 'css-loader',
  158. options: {
  159. importLoaders: 1
  160. }
  161. },
  162. 'postcss-loader'
  163. ]
  164. })
  165. },
  166. {
  167. test: /\.ftl$/,
  168. use: 'raw-loader'
  169. },
  170. {
  171. // creates test.js for /test
  172. test: require.resolve('./test/frontend/index.js'),
  173. use: ['babel-loader', 'val-loader']
  174. },
  175. {
  176. // loads all assets from assets/ for use by common/assets.js
  177. test: require.resolve('./common/generate_asset_map.js'),
  178. use: ['babel-loader', 'val-loader']
  179. }
  180. ]
  181. },
  182. plugins: [
  183. new CopyPlugin([
  184. {
  185. context: 'public',
  186. from: '*.*'
  187. }
  188. ]),
  189. new webpack.EnvironmentPlugin(['NODE_ENV']),
  190. new webpack.IgnorePlugin(/\.\.\/dist/), // used in common/*.js
  191. new ExtractTextPlugin({
  192. filename: '[name].[md5:contenthash:8].css'
  193. }),
  194. new VersionPlugin(), // used for the /__version__ route
  195. new AndroidIndexPlugin(),
  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. // eslint-disable-next-line no-console
  217. console.error(`mode: ${mode}`);
  218. process.env.NODE_ENV = web.mode = serviceWorker.mode = mode;
  219. if (mode === 'development') {
  220. // istanbul instruments the source for code coverage
  221. webJsOptions.plugins.push('istanbul');
  222. web.entry.tests = ['./test/frontend/index.js'];
  223. }
  224. return [web, serviceWorker];
  225. };