webpack.conf.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. const { modifyWebpackConfig, shallowMerge, defaultOptions } = require('@gera2ld/plaid');
  2. const { isProd } = require('@gera2ld/plaid/util');
  3. const webpack = require('webpack');
  4. const TerserPlugin = isProd && require('terser-webpack-plugin');
  5. const deepmerge = isProd && require('deepmerge');
  6. const { ListBackgroundScriptsPlugin } = require('./manifest-helper');
  7. const { addWrapperWithGlobals, getCodeMirrorThemes } = require('./webpack-util');
  8. const ProtectWebpackBootstrapPlugin = require('./webpack-protect-bootstrap-plugin');
  9. const projectConfig = require('./plaid.conf');
  10. const { getVersion } = require('./version-helper');
  11. const { configLoader } = require('./config-helper');
  12. const mergedConfig = shallowMerge(defaultOptions, projectConfig);
  13. // Avoiding collisions with globals of a content-mode userscript
  14. const INIT_FUNC_NAME = '**VMInitInjection**';
  15. const VAULT_ID = 'VAULT_ID';
  16. const PAGE_MODE_HANDSHAKE = 'PAGE_MODE_HANDSHAKE';
  17. const VM_VER = getVersion();
  18. const WEBPACK_OPTS = {
  19. node: {
  20. global: false,
  21. },
  22. performance: {
  23. maxEntrypointSize: 1e6,
  24. maxAssetSize: 0.5e6,
  25. },
  26. };
  27. const MIN_OPTS = {
  28. extractComments: false,
  29. parallel: true,
  30. terserOptions: {
  31. compress: {
  32. // `terser` often inlines big one-time functions inside a small "hot" function
  33. reduce_funcs: false,
  34. },
  35. output: {
  36. ascii_only: true,
  37. comments: false,
  38. wrap_func_args: false, // disabling a premature optimization designed for old browsers
  39. },
  40. },
  41. };
  42. const MIN_OPTS_PUBLIC = isProd && {
  43. include: 'public/',
  44. ...MIN_OPTS,
  45. };
  46. const MIN_OPTS_MAIN = isProd && deepmerge.all([{}, MIN_OPTS, {
  47. exclude: 'public/',
  48. terserOptions: {
  49. compress: {
  50. ecma: 8, // ES2017 Object.entries and so on
  51. passes: 2, // necessary now since we removed plaid's minimizer
  52. unsafe_arrows: true, // it's 'safe' since we don't rely on function prototypes
  53. },
  54. },
  55. }]);
  56. configLoader
  57. // Default values
  58. .add({
  59. DEBUG: false,
  60. })
  61. // Load from `./.env`
  62. .envFile()
  63. // Load from `process.env`
  64. .env()
  65. // Override values
  66. .add({
  67. VM_VER,
  68. });
  69. const pickEnvs = (items) => {
  70. return Object.assign({}, ...items.map(key => ({
  71. [`process.env.${key}`]: JSON.stringify(configLoader.get(key)),
  72. })));
  73. };
  74. const defsObj = {
  75. ...pickEnvs([
  76. 'DEBUG',
  77. 'VM_VER',
  78. 'SYNC_GOOGLE_CLIENT_ID',
  79. 'SYNC_GOOGLE_CLIENT_SECRET',
  80. 'SYNC_GOOGLE_DESKTOP_ID',
  81. 'SYNC_GOOGLE_DESKTOP_SECRET',
  82. 'SYNC_ONEDRIVE_CLIENT_ID',
  83. 'SYNC_ONEDRIVE_CLIENT_SECRET',
  84. 'SYNC_DROPBOX_CLIENT_ID',
  85. ]),
  86. 'process.env.INIT_FUNC_NAME': JSON.stringify(INIT_FUNC_NAME),
  87. 'process.env.CODEMIRROR_THEMES': JSON.stringify(getCodeMirrorThemes()),
  88. 'process.env.DEV': JSON.stringify(!isProd),
  89. 'process.env.TEST': JSON.stringify(process.env.BABEL_ENV === 'test'),
  90. };
  91. // avoid running webpack bootstrap in a potentially hacked environment
  92. // after documentElement was replaced which triggered reinjection of content scripts
  93. const skipReinjectionHeader = `{
  94. const INIT_FUNC_NAME = '${INIT_FUNC_NAME}';
  95. if (window[INIT_FUNC_NAME] !== 1)`;
  96. const modify = (page, entry, init) => modifyWebpackConfig(
  97. (config) => {
  98. Object.assign(config, WEBPACK_OPTS);
  99. config.output.publicPath = '/';
  100. config.plugins.push(new webpack.DefinePlugin({
  101. ...defsObj,
  102. // Conditional compilation to remove unsafe and unused stuff from `injected`
  103. 'process.env.IS_INJECTED': JSON.stringify(/injected/.test(page) && page),
  104. }));
  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. config.module.rules.find(rule => {
  113. if (typeof rule.test?.test === 'function' && rule.test.test('file.js')) {
  114. rule.exclude = file => /node_modules/.test(file) && !/vueleton/.test(file);
  115. }
  116. });
  117. if (!entry) init = page;
  118. if (init) init(config);
  119. return config;
  120. }, {
  121. projectConfig: {
  122. ...mergedConfig,
  123. ...entry && { pages: { [page]: { entry } } },
  124. },
  125. },
  126. );
  127. module.exports = Promise.all([
  128. modify((config) => {
  129. addWrapperWithGlobals('common', config, defsObj, getGlobals => ({
  130. header: () => `{ ${getGlobals()}`,
  131. footer: '}',
  132. test: /^(?!injected|public).*\.js$/,
  133. }));
  134. config.plugins.push(new ListBackgroundScriptsPlugin({
  135. minify: false, // keeping readable
  136. }));
  137. }),
  138. modify('injected', './src/injected', (config) => {
  139. config.plugins.push(new ProtectWebpackBootstrapPlugin());
  140. addWrapperWithGlobals('injected/content', config, defsObj, getGlobals => ({
  141. header: () => `${skipReinjectionHeader} { ${getGlobals()}`,
  142. footer: '}}',
  143. }));
  144. }),
  145. modify('injected-web', './src/injected/web', (config) => {
  146. config.output.libraryTarget = 'commonjs2';
  147. config.plugins.push(new ProtectWebpackBootstrapPlugin());
  148. addWrapperWithGlobals('injected/web', config, defsObj, getGlobals => ({
  149. header: () => `${skipReinjectionHeader}
  150. window[INIT_FUNC_NAME] = function (IS_FIREFOX,${PAGE_MODE_HANDSHAKE},${VAULT_ID}) {
  151. const module = { __proto__: null };
  152. ${getGlobals()}`,
  153. footer: `
  154. const { exports } = module;
  155. return exports.__esModule ? exports.default : exports;
  156. }};0;`,
  157. }));
  158. }),
  159. ]);