webpack-protect-bootstrap-plugin.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. const escapeStringRegexp = require('escape-string-regexp');
  2. const webpack = require('webpack');
  3. const G = webpack.RuntimeGlobals;
  4. const OBJ_RULE = [
  5. /([[(,=:]\s*{)(?!__proto__:)\s*(.)/g,
  6. (_, str, next) => `${str}__proto__: null${next === '}' ? '' : ','}${next}`,
  7. ];
  8. const BOOTSTRAP_RULES = [
  9. OBJ_RULE,
  10. [
  11. "typeof Symbol !== 'undefined' && Symbol.toStringTag",
  12. 'true',
  13. G.makeNamespaceObject,
  14. ], [
  15. 'Symbol.toStringTag',
  16. 'toStringTagSym',
  17. G.makeNamespaceObject,
  18. ], [
  19. 'Object.defineProperty(',
  20. 'defineProperty(',
  21. G.definePropertyGetters,
  22. ], [
  23. `${G.hasOwnProperty}(definition, key) && !${G.hasOwnProperty}(exports, key)`,
  24. '!(key in exports)',
  25. G.definePropertyGetters,
  26. ], [
  27. 'Object.prototype.hasOwnProperty.call(',
  28. 'hasOwnProperty(',
  29. G.hasOwnProperty,
  30. ],
  31. ];
  32. const MAIN_RULES = [
  33. [
  34. /(__webpack_modules__\[moduleId])\.call\(/g,
  35. 'safeCall($1, ',
  36. false,
  37. ], [
  38. new RegExp(`(?:${[
  39. '// webpackBootstrap\\s+(?:/\\*+/\\s*)?"use strict";\\s+',
  40. `var (__webpack_module_cache__|${G.require}) = {};.*?`,
  41. ].join('|')})var ${G.exports} =`, 's'),
  42. patchBootstrap,
  43. false,
  44. ], [
  45. new RegExp(`(${[
  46. `${G.definePropertyGetters}\\(${G.exports}, {`,
  47. `var ${G.exports} = {`,
  48. `var __webpack_modules__ = \\({`,
  49. ].join('|')})(?!__proto__:)\\s*(.)`, 'g'),
  50. OBJ_RULE[1],
  51. false,
  52. ],
  53. ];
  54. /**
  55. * WARNING! The following globals must be correctly assigned using wrapper-webpack-plugin.
  56. * toStringTagSym = Symbol.toStringTag
  57. * defineProperty = Object.defineProperty
  58. * hasOwnProperty = Object.prototype.hasOwnProperty
  59. * safeCall = Function.prototype.call.bind(Function.prototype.call)
  60. */
  61. class WebpackProtectBootstrapPlugin {
  62. apply(compiler) {
  63. const NAME = WebpackProtectBootstrapPlugin.name;
  64. compiler.hooks.compilation.tap(NAME, (compilation) => {
  65. const hooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
  66. hooks.renderMain.tap(NAME, replace.bind(null, MAIN_RULES));
  67. });
  68. }
  69. }
  70. function patchBootstrap(src, moduleCache) {
  71. if (!moduleCache) return ' ' + src; // everything was concatenated
  72. const props = src.match(new RegExp(`(?<=\\b${G.require}\\.)(\\w+)(?= = )`, 'g'));
  73. const guard = props
  74. ? `for (let i = 0, props="${[...new Set(props)].join('')}"; i < props.length; i++)
  75. defineProperty(${G.require}, props[i], {__proto__: null, value: 0, writable: 1});\n`
  76. : '';
  77. const res = replace(BOOTSTRAP_RULES, src, this);
  78. // splicing the guard after the first line to handle `require = {}`
  79. const i = res.indexOf('\n');
  80. return res.slice(0, i) + guard + res.slice(i);
  81. }
  82. function replace(rules, src, info) {
  83. src = src.source?.() || src;
  84. let res = src;
  85. for (const rule of rules) {
  86. const [from, to, test = true] = rule;
  87. const fromRe = typeof from === 'string'
  88. ? new RegExp(escapeStringRegexp(from), 'g')
  89. : from;
  90. const dst = res.replace(fromRe, to.bind?.(info) || to);
  91. const mandatory = test === true
  92. || test.test?.(src)
  93. || typeof test === 'string' && src.includes(test);
  94. if (dst === res && mandatory) {
  95. const err = `[${WebpackProtectBootstrapPlugin.name}] `
  96. + `"${from}" not found in ${info.chunk.name || 'bootstrap'}`;
  97. console.warn(`${err}:\n${src.slice(0, 500)}`); // this prints immediately
  98. throw new Error(err); // this prints at the end of build
  99. }
  100. res = dst;
  101. }
  102. return res;
  103. }
  104. module.exports = WebpackProtectBootstrapPlugin;