webpack.conf.js 5.5 KB

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