1
0

webpack-protect-bootstrap-plugin.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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. ], [
  44. new RegExp(`(${[
  45. `${G.definePropertyGetters}\\(${G.exports}, {`,
  46. `var ${G.exports} = {`,
  47. `var __webpack_modules__ = \\({`,
  48. ].join('|')})(?!__proto__:)\\s*(.)`, 'g'),
  49. OBJ_RULE[1],
  50. ],
  51. ];
  52. /**
  53. * WARNING! The following globals must be correctly assigned using wrapper-webpack-plugin.
  54. * toStringTagSym = Symbol.toStringTag
  55. * defineProperty = Object.defineProperty
  56. * hasOwnProperty = Object.prototype.hasOwnProperty
  57. * safeCall = Function.prototype.call.bind(Function.prototype.call)
  58. */
  59. class WebpackProtectBootstrapPlugin {
  60. apply(compiler) {
  61. const NAME = WebpackProtectBootstrapPlugin.name;
  62. compiler.hooks.compilation.tap(NAME, (compilation) => {
  63. const hooks = webpack.javascript.JavascriptModulesPlugin.getCompilationHooks(compilation);
  64. hooks.renderMain.tap(NAME, replace.bind(null, MAIN_RULES));
  65. });
  66. }
  67. }
  68. function patchBootstrap(src, moduleCache) {
  69. if (!moduleCache) return ' ' + src; // everything was concatenated
  70. const props = src.match(new RegExp(`(?<=\\b${G.require}\\.)(\\w+)(?= = )`, 'g'));
  71. const guard = props
  72. ? `for (let i = 0, props="${[...new Set(props)].join('')}"; i < props.length; i++)
  73. defineProperty(${G.require}, props[i], {__proto__: null, value: 0, writable: 1});\n`
  74. : '';
  75. const res = replace(BOOTSTRAP_RULES, src, this);
  76. // splicing the guard after the first line to handle `require = {}`
  77. const i = res.indexOf('\n');
  78. return res.slice(0, i) + guard + res.slice(i);
  79. }
  80. function replace(rules, src, info) {
  81. src = src.source?.() || src;
  82. let res = src;
  83. for (const rule of rules) {
  84. const [from, to, test = true] = rule;
  85. const fromRe = typeof from === 'string'
  86. ? new RegExp(escapeStringRegexp(from), 'g')
  87. : from;
  88. const dst = res.replace(fromRe, to.bind?.(info) || to);
  89. const mandatory = test === true
  90. || test.test?.(src)
  91. || typeof test === 'string' && src.includes(test);
  92. if (dst === res && mandatory) {
  93. const err = `[${WebpackProtectBootstrapPlugin.name}] `
  94. + `"${from}" not found in ${info.chunk.name || 'bootstrap'}`;
  95. console.warn(`${err}:\n${src.slice(0, 500)}`); // this prints immediately
  96. throw new Error(err); // this prints at the end of build
  97. }
  98. res = dst;
  99. }
  100. return res;
  101. }
  102. module.exports = WebpackProtectBootstrapPlugin;