webpack.conf.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. const { modifyWebpackConfig, shallowMerge, defaultOptions } = require('@gera2ld/plaid');
  2. const { isProd } = require('@gera2ld/plaid/util');
  3. const fs = require('fs');
  4. const webpack = require('webpack');
  5. const WrapperWebpackPlugin = require('wrapper-webpack-plugin');
  6. const HTMLInlineCSSWebpackPlugin = isProd && require('html-inline-css-webpack-plugin').default;
  7. const TerserPlugin = isProd && require('terser-webpack-plugin');
  8. const deepmerge = isProd && require('deepmerge');
  9. const { ListBackgroundScriptsPlugin } = require('./manifest-helper');
  10. const projectConfig = require('./plaid.conf');
  11. const mergedConfig = shallowMerge(defaultOptions, projectConfig);
  12. const INIT_FUNC_NAME = 'VMInitInjection';
  13. // eslint-disable-next-line import/no-dynamic-require
  14. const VM_VER = require(`${defaultOptions.distDir}/manifest.json`).version;
  15. const WEBPACK_OPTS = {
  16. node: {
  17. global: false,
  18. process: false,
  19. setImmediate: false,
  20. },
  21. performance: {
  22. maxEntrypointSize: 1e6,
  23. maxAssetSize: 0.5e6,
  24. },
  25. };
  26. const MIN_OPTS = {
  27. cache: true,
  28. parallel: true,
  29. sourceMap: true,
  30. terserOptions: {
  31. output: {
  32. ascii_only: true,
  33. },
  34. },
  35. };
  36. const MIN_OPTS_PUBLIC = isProd && {
  37. chunkFilter: ({ name }) => name.startsWith('public/'),
  38. ...MIN_OPTS,
  39. };
  40. const MIN_OPTS_MAIN = isProd && deepmerge.all([{}, MIN_OPTS, {
  41. chunkFilter: ({ name }) => !name.startsWith('public/'),
  42. terserOptions: {
  43. compress: {
  44. ecma: 8, // ES2017 Object.entries and so on
  45. passes: 2, // necessary now since we removed plaid's minimizer
  46. unsafe_arrows: true, // it's 'safe' since we don't rely on function prototypes
  47. },
  48. },
  49. }]);
  50. const pickEnvs = (items) => {
  51. return Object.assign({}, ...items.map(x => ({
  52. [`process.env.${x.key}`]: JSON.stringify(
  53. 'val' in x ? x.val
  54. : process.env[x.key] ?? x.def,
  55. ),
  56. })));
  57. };
  58. const definitions = new webpack.DefinePlugin({
  59. ...pickEnvs([
  60. { key: 'DEBUG', def: false },
  61. { key: 'VM_VER', val: VM_VER },
  62. { key: 'SYNC_GOOGLE_CLIENT_ID' },
  63. { key: 'SYNC_GOOGLE_CLIENT_SECRET' },
  64. { key: 'SYNC_ONEDRIVE_CLIENT_ID' },
  65. { key: 'SYNC_ONEDRIVE_CLIENT_SECRET' },
  66. ]),
  67. 'process.env.INIT_FUNC_NAME': JSON.stringify(INIT_FUNC_NAME),
  68. });
  69. // avoid running webpack bootstrap in a potentially hacked environment
  70. // after documentElement was replaced which triggered reinjection of content scripts
  71. const skipReinjectionHeader = `if (window['${INIT_FUNC_NAME}'] !== 1)`;
  72. // {entryName: path}
  73. const entryGlobals = {
  74. common: './src/common/safe-globals.js',
  75. injected: './src/injected/safe-injected-globals.js',
  76. };
  77. /**
  78. * Adds a watcher for files in entryGlobals to properly recompile the project on changes.
  79. */
  80. const addWrapper = (config, name, callback) => {
  81. if (!callback) { callback = name; name = ''; }
  82. const globals = Object.entries(entryGlobals).filter(([key]) => name === key || !name);
  83. const dirs = globals.map(([key]) => key).join('|');
  84. config.module.rules.push({
  85. test: new RegExp(`/(${dirs})/index\\.js$`.replace(/\//g, /[/\\]/.source)),
  86. use: [{
  87. loader: './scripts/fake-dep-loader.js',
  88. options: {
  89. files: globals.map(([, path]) => path),
  90. },
  91. }],
  92. });
  93. const reader = () => (
  94. globals.map(([, path]) => (
  95. fs.readFileSync(path, { encoding: 'utf8' })
  96. .replace(/export\s+(?=const\s)/g, '')
  97. ))
  98. ).join('\n');
  99. config.plugins.push(new WrapperWebpackPlugin(callback(reader)));
  100. };
  101. const modify = (page, entry, init) => modifyWebpackConfig(
  102. (config) => {
  103. Object.assign(config, WEBPACK_OPTS);
  104. config.plugins.push(definitions);
  105. config.optimization.minimizer.find((m, i, arr) => (
  106. m.constructor.name === 'TerserPlugin' && arr.splice(i, 1)
  107. ));
  108. config.optimization.minimizer.push(...!isProd ? [] : [
  109. new TerserPlugin(MIN_OPTS_PUBLIC),
  110. new TerserPlugin(MIN_OPTS_MAIN),
  111. ]);
  112. if (!entry) init = page;
  113. if (init) init(config);
  114. return config;
  115. }, {
  116. projectConfig: {
  117. ...mergedConfig,
  118. ...entry && { pages: { [page]: { entry } } },
  119. },
  120. },
  121. );
  122. module.exports = Promise.all([
  123. modify((config) => {
  124. addWrapper(config, 'common', getGlobals => ({
  125. header: () => `{ ${getGlobals()}`,
  126. footer: '}',
  127. test: /^(?!injected|public).*\.js$/,
  128. }));
  129. /* Embedding as <style> to ensure uiTheme option doesn't cause FOUC.
  130. * Note that in production build there's no <head> in html but document.head is still
  131. * auto-created per the specification so our styles will be placed correctly anyway. */
  132. if (isProd) {
  133. config.plugins.push(new HTMLInlineCSSWebpackPlugin({
  134. replace: {
  135. target: '<body>',
  136. position: 'before',
  137. },
  138. }));
  139. config.plugins.find(p => (
  140. p.constructor.name === 'MiniCssExtractPlugin'
  141. && Object.assign(p.options, { ignoreOrder: true })
  142. ));
  143. }
  144. config.plugins.push(new ListBackgroundScriptsPlugin());
  145. }),
  146. modify('injected', './src/injected', (config) => {
  147. addWrapper(config, getGlobals => ({
  148. header: () => `${skipReinjectionHeader} { ${getGlobals()}`,
  149. footer: '}',
  150. }));
  151. }),
  152. modify('injected-web', './src/injected/web', (config) => {
  153. config.output.libraryTarget = 'commonjs2';
  154. addWrapper(config, getGlobals => ({
  155. header: () => `${skipReinjectionHeader}
  156. window['${INIT_FUNC_NAME}'] = function () {
  157. var module = { exports: {} };
  158. ${getGlobals()}`,
  159. footer: `
  160. module = module.exports;
  161. return module.__esModule ? module.default : module;
  162. };0;`,
  163. }));
  164. }),
  165. ]);