webpack-protect-bootstrap-plugin.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. const escapeStringRegexp = require('escape-string-regexp');
  2. const webpack = require('webpack');
  3. const G = webpack.RuntimeGlobals;
  4. const OPTIONAL = false;
  5. const OBJ_RULE = [
  6. /([[(,=:]\s*{)(?!__proto__:)\s*(.)/g,
  7. (_, str, next) => `${str}__proto__: null${next === '}' ? '' : ','}${next}`
  8. ];
  9. const BOOTSTRAP_RULES = [
  10. OBJ_RULE,
  11. ["typeof Symbol !== 'undefined' && Symbol.toStringTag",
  12. 'true'],
  13. ['Symbol.toStringTag',
  14. 'toStringTagSym'],
  15. ['Object.defineProperty(',
  16. 'defineProperty('],
  17. ['Object.prototype.hasOwnProperty.call(',
  18. 'safeCall(hasOwnProperty, '],
  19. [`${G.hasOwnProperty}(definition, key) && !${G.hasOwnProperty}(exports, key)`,
  20. '!(key in exports)'], // these objects have null proto
  21. ];
  22. const MAIN_RULES = [
  23. [
  24. /(__webpack_modules__\[moduleId])\.call\(/g,
  25. 'safeCall($1, ',
  26. OPTIONAL,
  27. ], [
  28. new RegExp(`var (__webpack_module_cache__|${G.require}) = {};.*?var ${G.exports} =`, 's'),
  29. function (src, group1) {
  30. let guard = '';
  31. if (group1 !== G.require) {
  32. // webpack didn't concatenate all modules into one, let's patch the machinery
  33. const props = src.match(new RegExp(`(?<=\\b${G.require}\\.)(\\w+)`, 'g'));
  34. const uniq = [...new Set(props)].join('');
  35. if (uniq) guard = `for (let i = 0, props=${JSON.stringify(uniq)}; i < props.length; i++)
  36. defineProperty(${G.require}, props[i], {__proto__: null, value: 0, writable: 1});\n`;
  37. }
  38. return guard + replace(guard ? BOOTSTRAP_RULES : [OBJ_RULE], src, this);
  39. },
  40. ], [
  41. new RegExp(`(${[
  42. `${G.definePropertyGetters}\\(${G.exports}, {`,
  43. `var ${G.exports} = {`,
  44. `var __webpack_modules__ = \\({`,
  45. ].join('|')})(?!__proto__:)\\s*(.)`, 'g'),
  46. OBJ_RULE[1],
  47. ],
  48. ];
  49. /**
  50. * WARNING! The following globals must be correctly assigned using wrapper-webpack-plugin.
  51. * toStringTagSym = Symbol.toStringTag
  52. * defineProperty = Object.defineProperty
  53. * hasOwnProperty = Object.prototype.hasOwnProperty
  54. * safeCall = Function.prototype.call.bind(Function.prototype.call)
  55. */
  56. class WebpackProtectBootstrapPlugin {
  57. apply(compiler) {
  58. const NAME = WebpackProtectBootstrapPlugin.name;
  59. compiler.hooks.compilation.tap(NAME, (compilation) => {
  60. const hooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
  61. hooks.renderMain.tap(NAME, replace.bind(null, MAIN_RULES));
  62. });
  63. }
  64. }
  65. function replace(rules, src, info) {
  66. if (!src) return src;
  67. if (src.source) src = src.source();
  68. let res = src;
  69. for (const rule of rules) {
  70. if (typeof rule === 'function') {
  71. res = rule(res);
  72. } else {
  73. const [from, to, mandatory = true] = rule;
  74. const fromRe = typeof from === 'string'
  75. ? new RegExp(escapeStringRegexp(from), 'g')
  76. : from;
  77. const dst = res.replace(fromRe, typeof to === 'function' ? to.bind(info) : to);
  78. if (dst === res && mandatory) {
  79. const err = `[${WebpackProtectBootstrapPlugin.name}] `
  80. + `"${from}" not found in ${info?.chunk.name || 'bootstrap'}`;
  81. console.log(`${err}:\n${src}`);
  82. throw new Error(err);
  83. }
  84. res = dst;
  85. }
  86. }
  87. return res;
  88. }
  89. module.exports = WebpackProtectBootstrapPlugin;