webpack-protect-bootstrap-plugin.js 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. const escapeStringRegexp = require('escape-string-regexp');
  2. /**
  3. * WARNING! The following globals must be correctly assigned using wrapper-webpack-plugin.
  4. * toStringTag = Symbol.toStringTag
  5. * defineProperty = Object.defineProperty
  6. * hasOwnProperty = Object.prototype.hasOwnProperty
  7. * safeCall = Function.prototype.call.bind(Function.prototype.call)
  8. */
  9. class WebpackProtectBootstrapPlugin {
  10. apply(compiler) {
  11. const NAME = this.constructor.name;
  12. const NULL_PROTO = '__proto__: null';
  13. const NULL_OBJ = `{ ${NULL_PROTO} }`;
  14. compiler.hooks.compilation.tap(NAME, (compilation) => {
  15. const { hooks, requireFn } = compilation.mainTemplate;
  16. hooks.localVars.tap(NAME, src => replace(src, [[
  17. 'installedModules = {};',
  18. `installedModules = ${NULL_OBJ}; \
  19. for (let i = 0, c, str = "cdmnoprt"; i < str.length && (c = str[i++]);) \
  20. defineProperty(${requireFn}, c, { value: undefined, writable: true });`,
  21. ]]));
  22. hooks.moduleObj.tap(NAME, src => replace(src, [[
  23. 'exports: {}',
  24. `exports: ${NULL_OBJ}, ${NULL_PROTO}`,
  25. ]]));
  26. hooks.require.tap(NAME, src => replace(src, [[
  27. 'modules[moduleId].call(',
  28. 'safeCall(modules[moduleId], ',
  29. ]]));
  30. hooks.requireExtensions.tap(NAME, src => replace(src, [
  31. ["(typeof Symbol !== 'undefined' && Symbol.toStringTag)", '(true)'],
  32. ['Symbol.toStringTag', 'toStringTag'],
  33. ['Object.defineProperty', 'defineProperty'],
  34. ['Object.create(null)', NULL_OBJ],
  35. ['for(var key in value)', 'for(const key in value)'],
  36. ['function(key) { return value[key]; }.bind(null, key)',
  37. '() => value[key]'],
  38. [/function[^{]+{[^}]+?hasOwnProperty\.call[^}]+}/g,
  39. '(obj, key) => safeCall(hasOwnProperty, obj, key)'],
  40. ]));
  41. });
  42. }
  43. }
  44. function replace(src, fromTo) {
  45. const origSrc = src;
  46. for (const [from, to] of fromTo) {
  47. const fromRe = typeof from === 'string'
  48. ? new RegExp(escapeStringRegexp(from), 'g')
  49. : from;
  50. const dst = src.replace(fromRe, to);
  51. if (dst === src) {
  52. throw new Error(`${WebpackProtectBootstrapPlugin.constructor.name}: `
  53. + `"${from}" not found in "${origSrc}"`);
  54. }
  55. src = dst;
  56. }
  57. return src;
  58. }
  59. module.exports = WebpackProtectBootstrapPlugin;