webpack.config.js 6.1 KB

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