webpack.conf.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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, getUniqIdB64 } = 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 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 = getVersion();
  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. extractComments: false,
  32. parallel: true,
  33. sourceMap: true,
  34. terserOptions: {
  35. compress: {
  36. // `terser` often inlines big one-time functions inside a small "hot" function
  37. reduce_funcs: false,
  38. reduce_vars: false,
  39. },
  40. output: {
  41. ascii_only: true,
  42. comments: false,
  43. wrap_func_args: false, // disabling a premature optimization designed for old browsers
  44. },
  45. },
  46. };
  47. const MIN_OPTS_PUBLIC = isProd && {
  48. include: 'public/',
  49. ...MIN_OPTS,
  50. };
  51. const MIN_OPTS_MAIN = isProd && deepmerge.all([{}, MIN_OPTS, {
  52. exclude: 'public/',
  53. terserOptions: {
  54. compress: {
  55. ecma: 8, // ES2017 Object.entries and so on
  56. passes: 2, // necessary now since we removed plaid's minimizer
  57. unsafe_arrows: true, // it's 'safe' since we don't rely on function prototypes
  58. },
  59. },
  60. }]);
  61. const pickEnvs = (items) => {
  62. return Object.assign({}, ...items.map(x => ({
  63. [`process.env.${x.key}`]: JSON.stringify(
  64. 'val' in x ? x.val
  65. : process.env[x.key] ?? x.def,
  66. ),
  67. })));
  68. };
  69. const defsObj = {
  70. ...pickEnvs([
  71. { key: 'DEBUG', def: false },
  72. { key: 'VM_VER', val: VM_VER },
  73. { key: 'SYNC_GOOGLE_CLIENT_ID' },
  74. { key: 'SYNC_GOOGLE_CLIENT_SECRET' },
  75. { key: 'SYNC_GOOGLE_DESKTOP_ID' },
  76. { key: 'SYNC_GOOGLE_DESKTOP_SECRET' },
  77. { key: 'SYNC_ONEDRIVE_CLIENT_ID' },
  78. { key: 'SYNC_ONEDRIVE_CLIENT_SECRET' },
  79. { key: 'SYNC_DROPBOX_CLIENT_ID' },
  80. ]),
  81. 'process.env.INIT_FUNC_NAME': JSON.stringify(INIT_FUNC_NAME),
  82. 'process.env.VAULT_ID': VAULT_ID,
  83. 'process.env.HANDSHAKE_ID': HANDSHAKE_ID,
  84. 'process.env.HANDSHAKE_ACK': '1',
  85. 'process.env.CODEMIRROR_THEMES': JSON.stringify(getCodeMirrorThemes()),
  86. 'process.env.DEV': JSON.stringify(process.env.NODE_ENV === 'development'),
  87. };
  88. // avoid running webpack bootstrap in a potentially hacked environment
  89. // after documentElement was replaced which triggered reinjection of content scripts
  90. const skipReinjectionHeader = `{
  91. const INIT_FUNC_NAME = '${INIT_FUNC_NAME}';
  92. if (window[INIT_FUNC_NAME] !== 1)`;
  93. const modify = (page, entry, init) => modifyWebpackConfig(
  94. (config) => {
  95. Object.assign(config, WEBPACK_OPTS);
  96. config.plugins.push(new webpack.DefinePlugin({
  97. ...defsObj,
  98. // Conditional compilation to remove unsafe and unused stuff from `injected`
  99. 'process.env.IS_INJECTED': JSON.stringify(/injected/.test(page) && page),
  100. }));
  101. config.optimization.minimizer.find((m, i, arr) => (
  102. m.constructor.name === 'TerserPlugin' && arr.splice(i, 1)
  103. ));
  104. config.optimization.minimizer.push(...!isProd ? [] : [
  105. new TerserPlugin(MIN_OPTS_PUBLIC),
  106. new TerserPlugin(MIN_OPTS_MAIN),
  107. ]);
  108. if (!entry) init = page;
  109. if (init) init(config);
  110. return config;
  111. }, {
  112. projectConfig: {
  113. ...mergedConfig,
  114. ...entry && { pages: { [page]: { entry } } },
  115. },
  116. },
  117. );
  118. module.exports = Promise.all([
  119. modify((config) => {
  120. addWrapperWithGlobals('common', config, defsObj, getGlobals => ({
  121. header: () => `{ ${getGlobals()}`,
  122. footer: '}',
  123. test: /^(?!injected|public).*\.js$/,
  124. }));
  125. config.plugins.push(new ListBackgroundScriptsPlugin({
  126. minify: false, // keeping readable
  127. }));
  128. config.module.rules.find(r => r.loader === 'vue-loader')
  129. .options.compiler = require('vue-template-babel-compiler');
  130. }),
  131. modify('injected', './src/injected', (config) => {
  132. config.plugins.push(new ProtectWebpackBootstrapPlugin());
  133. addWrapperWithGlobals('injected/content', config, defsObj, getGlobals => ({
  134. header: () => `${skipReinjectionHeader} { ${getGlobals()}`,
  135. footer: '}}',
  136. }));
  137. }),
  138. modify('injected-web', './src/injected/web', (config) => {
  139. // TODO: replace WebPack's Object.*, .call(), .apply() with safe calls
  140. config.output.libraryTarget = 'commonjs2';
  141. config.plugins.push(new ProtectWebpackBootstrapPlugin());
  142. addWrapperWithGlobals('injected/web', config, defsObj, getGlobals => ({
  143. header: () => `${skipReinjectionHeader}
  144. window[INIT_FUNC_NAME] = function (IS_FIREFOX,${HANDSHAKE_ID},${VAULT_ID}) {
  145. const module = { __proto__: null };
  146. ${getGlobals()}`,
  147. footer: `
  148. const { exports } = module;
  149. return exports.__esModule ? exports.default : exports;
  150. }};0;`,
  151. }));
  152. }),
  153. ]);