CompatibilityPlugin.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const {
  7. JAVASCRIPT_MODULE_TYPE_AUTO,
  8. JAVASCRIPT_MODULE_TYPE_DYNAMIC,
  9. JAVASCRIPT_MODULE_TYPE_ESM
  10. } = require("./ModuleTypeConstants");
  11. const RuntimeGlobals = require("./RuntimeGlobals");
  12. const ConstDependency = require("./dependencies/ConstDependency");
  13. /** @typedef {import("estree").CallExpression} CallExpression */
  14. /** @typedef {import("./Compiler")} Compiler */
  15. /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
  16. /** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
  17. /** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */
  18. /** @typedef {import("./javascript/JavascriptParser").Range} Range */
  19. /**
  20. * Captures the source range of a renamed compatibility binding so it can be
  21. * rewritten exactly once.
  22. * @typedef {object} CompatibilitySettingsDeclaration
  23. * @property {boolean} updated
  24. * @property {DependencyLocation} loc
  25. * @property {Range} range
  26. */
  27. /**
  28. * Stores the replacement variable name and the declaration metadata tracked
  29. * for a compatibility rewrite.
  30. * @typedef {object} CompatibilitySettings
  31. * @property {string} name
  32. * @property {CompatibilitySettingsDeclaration} declaration
  33. */
  34. const nestedWebpackIdentifierTag = Symbol("nested webpack identifier");
  35. const PLUGIN_NAME = "CompatibilityPlugin";
  36. /**
  37. * Adds parser-time compatibility rewrites for legacy runtime patterns that
  38. * webpack still needs to recognize in user and generated code.
  39. */
  40. class CompatibilityPlugin {
  41. /**
  42. * Installs parser hooks that preserve compatibility with legacy patterns
  43. * such as nested `__webpack_require__` bindings and hashbang handling.
  44. * @param {Compiler} compiler the compiler instance
  45. * @returns {void}
  46. */
  47. apply(compiler) {
  48. compiler.hooks.compilation.tap(
  49. PLUGIN_NAME,
  50. (compilation, { normalModuleFactory }) => {
  51. compilation.dependencyTemplates.set(
  52. ConstDependency,
  53. new ConstDependency.Template()
  54. );
  55. normalModuleFactory.hooks.parser
  56. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  57. .tap(PLUGIN_NAME, (parser, parserOptions) => {
  58. if (
  59. parserOptions.browserify !== undefined &&
  60. !parserOptions.browserify
  61. ) {
  62. return;
  63. }
  64. parser.hooks.call.for("require").tap(
  65. PLUGIN_NAME,
  66. /**
  67. * Rewrites browserify-style delegated `require` calls into a
  68. * plain webpack require reference and removes the synthetic
  69. * context dependency created for the delegator pattern.
  70. * @param {CallExpression} expr call expression
  71. * @returns {boolean | void} true when need to handle
  72. */
  73. (expr) => {
  74. // support for browserify style require delegator: "require(o, !0)"
  75. if (expr.arguments.length !== 2) return;
  76. const second = parser.evaluateExpression(expr.arguments[1]);
  77. if (!second.isBoolean()) return;
  78. if (second.asBool() !== true) return;
  79. const dep = new ConstDependency(
  80. "require",
  81. /** @type {Range} */ (expr.callee.range)
  82. );
  83. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  84. if (parser.state.current.dependencies.length > 0) {
  85. const last =
  86. /** @type {ContextDependency} */
  87. (
  88. parser.state.current.dependencies[
  89. parser.state.current.dependencies.length - 1
  90. ]
  91. );
  92. if (
  93. last.critical &&
  94. last.options &&
  95. last.options.request === "." &&
  96. last.userRequest === "." &&
  97. last.options.recursive
  98. ) {
  99. parser.state.current.dependencies.pop();
  100. }
  101. }
  102. parser.state.module.addPresentationalDependency(dep);
  103. return true;
  104. }
  105. );
  106. });
  107. /**
  108. * Attaches the compatibility rewrites for a JavaScript parser
  109. * instance.
  110. * @param {JavascriptParser} parser the parser
  111. * @returns {void}
  112. */
  113. const handler = (parser) => {
  114. // Handle nested requires
  115. parser.hooks.preStatement.tap(PLUGIN_NAME, (statement) => {
  116. if (
  117. statement.type === "FunctionDeclaration" &&
  118. statement.id &&
  119. statement.id.name === RuntimeGlobals.require
  120. ) {
  121. const newName = `__nested_webpack_require_${
  122. /** @type {Range} */
  123. (statement.range)[0]
  124. }__`;
  125. parser.tagVariable(
  126. statement.id.name,
  127. nestedWebpackIdentifierTag,
  128. {
  129. name: newName,
  130. declaration: {
  131. updated: false,
  132. loc: /** @type {DependencyLocation} */ (statement.id.loc),
  133. range: /** @type {Range} */ (statement.id.range)
  134. }
  135. }
  136. );
  137. return true;
  138. }
  139. });
  140. parser.hooks.pattern
  141. .for(RuntimeGlobals.require)
  142. .tap(PLUGIN_NAME, (pattern) => {
  143. const newName = `__nested_webpack_require_${
  144. /** @type {Range} */ (pattern.range)[0]
  145. }__`;
  146. parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, {
  147. name: newName,
  148. declaration: {
  149. updated: false,
  150. loc: /** @type {DependencyLocation} */ (pattern.loc),
  151. range: /** @type {Range} */ (pattern.range)
  152. }
  153. });
  154. if (parser.scope.topLevelScope !== true) {
  155. return true;
  156. }
  157. });
  158. parser.hooks.pattern
  159. .for(RuntimeGlobals.exports)
  160. .tap(PLUGIN_NAME, (pattern) => {
  161. const newName = "__nested_webpack_exports__";
  162. parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, {
  163. name: newName,
  164. declaration: {
  165. updated: false,
  166. loc: /** @type {DependencyLocation} */ (pattern.loc),
  167. range: /** @type {Range} */ (pattern.range)
  168. }
  169. });
  170. return true;
  171. });
  172. // Update single `var __webpack_require__ = {};` and `var __webpack_exports__ = {};` without expression
  173. parser.hooks.declarator.tap(PLUGIN_NAME, (declarator) => {
  174. if (
  175. declarator.id.type === "Identifier" &&
  176. (declarator.id.name === RuntimeGlobals.exports ||
  177. declarator.id.name === RuntimeGlobals.require)
  178. ) {
  179. const { name, declaration } =
  180. /** @type {CompatibilitySettings} */ (
  181. parser.getTagData(
  182. declarator.id.name,
  183. nestedWebpackIdentifierTag
  184. )
  185. );
  186. if (!declaration.updated) {
  187. const dep = new ConstDependency(name, declaration.range);
  188. dep.loc = declaration.loc;
  189. parser.state.module.addPresentationalDependency(dep);
  190. declaration.updated = true;
  191. }
  192. }
  193. });
  194. parser.hooks.expression
  195. .for(nestedWebpackIdentifierTag)
  196. .tap(PLUGIN_NAME, (expr) => {
  197. const { name, declaration } =
  198. /** @type {CompatibilitySettings} */
  199. (parser.currentTagData);
  200. if (!declaration.updated) {
  201. const dep = new ConstDependency(name, declaration.range);
  202. dep.loc = declaration.loc;
  203. parser.state.module.addPresentationalDependency(dep);
  204. declaration.updated = true;
  205. }
  206. const dep = new ConstDependency(
  207. name,
  208. /** @type {Range} */ (expr.range)
  209. );
  210. dep.loc = /** @type {DependencyLocation} */ (expr.loc);
  211. parser.state.module.addPresentationalDependency(dep);
  212. return true;
  213. });
  214. // Handle hashbang
  215. parser.hooks.program.tap(PLUGIN_NAME, (program, comments) => {
  216. if (comments.length === 0) return;
  217. const c = comments[0];
  218. if (c.type === "Line" && /** @type {Range} */ (c.range)[0] === 0) {
  219. if (parser.state.source.slice(0, 2).toString() !== "#!") return;
  220. // this is a hashbang comment
  221. const dep = new ConstDependency("//", 0);
  222. dep.loc = /** @type {DependencyLocation} */ (c.loc);
  223. parser.state.module.addPresentationalDependency(dep);
  224. }
  225. });
  226. };
  227. normalModuleFactory.hooks.parser
  228. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  229. .tap(PLUGIN_NAME, handler);
  230. normalModuleFactory.hooks.parser
  231. .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
  232. .tap(PLUGIN_NAME, handler);
  233. normalModuleFactory.hooks.parser
  234. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  235. .tap(PLUGIN_NAME, handler);
  236. }
  237. );
  238. }
  239. }
  240. module.exports = CompatibilityPlugin;
  241. module.exports.nestedWebpackIdentifierTag = nestedWebpackIdentifierTag;