APIPlugin.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const {
  7. getExternalModuleNodeCommonjsInitFragment
  8. } = require("./ExternalModule");
  9. const {
  10. JAVASCRIPT_MODULE_TYPE_AUTO,
  11. JAVASCRIPT_MODULE_TYPE_DYNAMIC,
  12. JAVASCRIPT_MODULE_TYPE_ESM
  13. } = require("./ModuleTypeConstants");
  14. const RuntimeGlobals = require("./RuntimeGlobals");
  15. const ConstDependency = require("./dependencies/ConstDependency");
  16. const ModuleInitFragmentDependency = require("./dependencies/ModuleInitFragmentDependency");
  17. const RuntimeRequirementsDependency = require("./dependencies/RuntimeRequirementsDependency");
  18. const WebpackError = require("./errors/WebpackError");
  19. const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression");
  20. const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin");
  21. const {
  22. evaluateToString,
  23. toConstantDependency
  24. } = require("./javascript/JavascriptParserHelpers");
  25. const ChunkNameRuntimeModule = require("./runtime/ChunkNameRuntimeModule");
  26. const GetFullHashRuntimeModule = require("./runtime/GetFullHashRuntimeModule");
  27. const memoize = require("./util/memoize");
  28. const { forEachRuntime } = require("./util/runtime");
  29. const getConcatenatedModule = memoize(() =>
  30. require("./optimize/ConcatenatedModule")
  31. );
  32. /** @import Compiler from "./Compiler" */
  33. /** @import Module, { BuildInfo } from "./Module" */
  34. /**
  35. * @import {
  36. * JavascriptModuleBuildInfo
  37. * } from "./javascript/JavascriptModule"
  38. */
  39. /** @import Compilation from "./Compilation" */
  40. /** @import ChunkGraph from "./ChunkGraph" */
  41. /** @import JavascriptParser, { Range } from "./javascript/JavascriptParser" */
  42. // Modules that reassign `__webpack_public_path__` at runtime, by compilation. A baked
  43. // analyzable specifier can't reflect that, so those forms fall back. Also recorded per
  44. // module in `buildInfo`, because a module restored from the persistent cache is never
  45. // re-parsed and would otherwise lose the flag.
  46. /** @type {WeakMap<Compilation, Set<Module>>} */
  47. const runtimePublicPathOverride = new WeakMap();
  48. /**
  49. * @param {Compilation} compilation compilation
  50. * @param {Module} module the module doing the reassigning
  51. * @returns {void}
  52. */
  53. const addRuntimePublicPathOverride = (compilation, module) => {
  54. const modules = runtimePublicPathOverride.get(compilation);
  55. if (modules === undefined) {
  56. runtimePublicPathOverride.set(compilation, new Set([module]));
  57. } else {
  58. modules.add(module);
  59. }
  60. };
  61. /**
  62. * @param {Compilation} compilation compilation
  63. * @returns {boolean} true when any module reassigns `__webpack_public_path__` at runtime
  64. */
  65. const usesRuntimePublicPathOverride = (compilation) =>
  66. runtimePublicPathOverride.has(compilation);
  67. // `__webpack_require__.p` belongs to a runtime, so a reassignment only reaches the
  68. // runtimes the reassigning module is instantiated in. Computed once the chunk graph
  69. // can answer that, which is any time code is being generated.
  70. /** @type {WeakMap<ChunkGraph, Set<string>>} */
  71. const overriddenRuntimes = new WeakMap();
  72. /**
  73. * Whether the public path is reassigned in any runtime `module` belongs to. Falls back
  74. * to the whole compilation when there is no chunk graph to place the module in.
  75. * @param {Compilation} compilation compilation
  76. * @param {ChunkGraph=} chunkGraph the chunk graph
  77. * @param {Module=} module the module a reference is emitted into
  78. * @returns {boolean} true when a reassignment can reach this module's runtimes
  79. */
  80. const runtimeUsesPublicPathOverride = (compilation, chunkGraph, module) => {
  81. const modules = runtimePublicPathOverride.get(compilation);
  82. if (modules === undefined) return false;
  83. if (chunkGraph === undefined || module === undefined) return true;
  84. // Keyed by the chunk graph, not the compilation: `executeModule` asks with a
  85. // throw-away one whose answer must not outlive it.
  86. let runtimes = overriddenRuntimes.get(chunkGraph);
  87. if (runtimes === undefined) {
  88. /** @type {Set<string>} */
  89. const collected = new Set();
  90. const ConcatenatedModule = getConcatenatedModule();
  91. for (const overriding of modules) {
  92. // Concatenation may have absorbed the reassigning module, and the chunk graph
  93. // places only the `ConcatenatedModule` that replaced it.
  94. for (const runtime of chunkGraph.getModuleRuntimes(
  95. ConcatenatedModule.getChunkGraphModule(compilation, overriding)
  96. )) {
  97. forEachRuntime(runtime, (key) => {
  98. collected.add(/** @type {string} */ (key));
  99. });
  100. }
  101. }
  102. runtimes = collected;
  103. overriddenRuntimes.set(chunkGraph, collected);
  104. }
  105. const overridden = runtimes;
  106. if (overridden.size === 0) return false;
  107. /** @type {Set<string>} */
  108. const own = new Set();
  109. for (const runtime of chunkGraph.getModuleRuntimes(module)) {
  110. forEachRuntime(runtime, (key) => {
  111. own.add(/** @type {string} */ (key));
  112. });
  113. }
  114. for (const key of own) {
  115. if (overridden.has(key)) return true;
  116. }
  117. return false;
  118. };
  119. /**
  120. * Returns the replacement definitions used for webpack API identifiers.
  121. * @returns {Record<string, { expr: string, req: string[] | null, type?: string, assign: boolean }>} replacements
  122. */
  123. function getReplacements() {
  124. return {
  125. __webpack_require__: {
  126. expr: RuntimeGlobals.require,
  127. req: [RuntimeGlobals.require],
  128. type: "function",
  129. assign: false
  130. },
  131. __webpack_global__: {
  132. expr: RuntimeGlobals.require,
  133. req: [RuntimeGlobals.require],
  134. type: "function",
  135. assign: false
  136. },
  137. __webpack_public_path__: {
  138. expr: RuntimeGlobals.publicPath,
  139. req: [RuntimeGlobals.publicPath],
  140. type: "string",
  141. assign: true
  142. },
  143. __webpack_base_uri__: {
  144. expr: RuntimeGlobals.baseURI,
  145. req: [RuntimeGlobals.baseURI],
  146. type: "string",
  147. assign: true
  148. },
  149. __webpack_modules__: {
  150. expr: RuntimeGlobals.moduleFactories,
  151. req: [RuntimeGlobals.moduleFactories],
  152. type: "object",
  153. assign: false
  154. },
  155. __webpack_chunk_load__: {
  156. expr: RuntimeGlobals.ensureChunk,
  157. req: [RuntimeGlobals.ensureChunk],
  158. type: "function",
  159. assign: true
  160. },
  161. __non_webpack_require__: {
  162. expr: "require",
  163. req: null,
  164. type: undefined, // type is not known, depends on environment
  165. assign: true
  166. },
  167. __webpack_nonce__: {
  168. expr: RuntimeGlobals.scriptNonce,
  169. req: [RuntimeGlobals.scriptNonce],
  170. type: "string",
  171. assign: true
  172. },
  173. __webpack_hash__: {
  174. expr: `${RuntimeGlobals.getFullHash}()`,
  175. req: [RuntimeGlobals.getFullHash],
  176. type: "string",
  177. assign: false
  178. },
  179. __webpack_css_server_styles__: {
  180. expr: `${RuntimeGlobals.getCssServerStyles}()`,
  181. req: [RuntimeGlobals.getCssServerStyles],
  182. type: "string",
  183. assign: false
  184. },
  185. __webpack_chunkname__: {
  186. expr: RuntimeGlobals.chunkName,
  187. req: [RuntimeGlobals.chunkName],
  188. type: "string",
  189. assign: false
  190. },
  191. __webpack_get_script_filename__: {
  192. expr: RuntimeGlobals.getChunkScriptFilename,
  193. req: [RuntimeGlobals.getChunkScriptFilename],
  194. type: "function",
  195. assign: true
  196. },
  197. __webpack_runtime_id__: {
  198. expr: RuntimeGlobals.runtimeId,
  199. req: [RuntimeGlobals.runtimeId],
  200. assign: false
  201. },
  202. "require.onError": {
  203. expr: RuntimeGlobals.uncaughtErrorHandler,
  204. req: [RuntimeGlobals.uncaughtErrorHandler],
  205. type: undefined, // type is not known, could be function or undefined
  206. assign: true // is never a pattern
  207. },
  208. __system_context__: {
  209. expr: RuntimeGlobals.systemContext,
  210. req: [RuntimeGlobals.systemContext],
  211. type: "object",
  212. assign: false
  213. },
  214. __webpack_share_scopes__: {
  215. expr: RuntimeGlobals.shareScopeMap,
  216. req: [RuntimeGlobals.shareScopeMap],
  217. type: "object",
  218. assign: false
  219. },
  220. __webpack_init_sharing__: {
  221. expr: RuntimeGlobals.initializeSharing,
  222. req: [RuntimeGlobals.initializeSharing],
  223. type: "function",
  224. assign: true
  225. }
  226. };
  227. }
  228. const PLUGIN_NAME = "APIPlugin";
  229. class APIPlugin {
  230. /**
  231. * Applies the plugin by registering its hooks on the compiler.
  232. * @param {Compiler} compiler the compiler instance
  233. * @returns {void}
  234. */
  235. apply(compiler) {
  236. compiler.hooks.compilation.tap(
  237. PLUGIN_NAME,
  238. (compilation, { normalModuleFactory }) => {
  239. const moduleOutput = compilation.options.output.module;
  240. const nodeTarget = compiler.platform.node;
  241. const nodeEsm = moduleOutput && nodeTarget;
  242. const REPLACEMENTS = getReplacements();
  243. if (nodeEsm) {
  244. REPLACEMENTS.__non_webpack_require__.expr =
  245. "__WEBPACK_EXTERNAL_createRequire_require";
  246. }
  247. // A cached module skips parsing, so replay its recorded override flag.
  248. compilation.hooks.stillValidModule.tap(PLUGIN_NAME, (module) => {
  249. const buildInfo =
  250. /** @type {JavascriptModuleBuildInfo | undefined} */
  251. (module.buildInfo);
  252. if (buildInfo && buildInfo.usingPublicPathOverride) {
  253. addRuntimePublicPathOverride(compilation, module);
  254. }
  255. });
  256. compilation.dependencyTemplates.set(
  257. ConstDependency,
  258. new ConstDependency.Template()
  259. );
  260. compilation.dependencyTemplates.set(
  261. ModuleInitFragmentDependency,
  262. new ModuleInitFragmentDependency.Template()
  263. );
  264. compilation.hooks.runtimeRequirementInTree
  265. .for(RuntimeGlobals.chunkName)
  266. .tap(PLUGIN_NAME, (chunk) => {
  267. compilation.addRuntimeModule(
  268. chunk,
  269. new ChunkNameRuntimeModule(/** @type {string} */ (chunk.name))
  270. );
  271. return true;
  272. });
  273. compilation.hooks.runtimeRequirementInTree
  274. .for(RuntimeGlobals.getFullHash)
  275. .tap(PLUGIN_NAME, (chunk, _set) => {
  276. compilation.addRuntimeModule(chunk, new GetFullHashRuntimeModule());
  277. return true;
  278. });
  279. const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation);
  280. hooks.renderModuleContent.tap(
  281. PLUGIN_NAME,
  282. (source, module, renderContext) => {
  283. if (
  284. /** @type {JavascriptModuleBuildInfo} */ (module.buildInfo)
  285. .needCreateRequire
  286. ) {
  287. const chunkInitFragments = [
  288. getExternalModuleNodeCommonjsInitFragment(
  289. renderContext.runtimeTemplate
  290. )
  291. ];
  292. renderContext.chunkInitFragments.push(...chunkInitFragments);
  293. }
  294. return source;
  295. }
  296. );
  297. /**
  298. * Handles the hook callback for this code path.
  299. * @param {JavascriptParser} parser the parser
  300. */
  301. const handler = (parser) => {
  302. parser.hooks.preDeclarator.tap(PLUGIN_NAME, (declarator) => {
  303. if (
  304. parser.scope.topLevelScope === true &&
  305. declarator.id.type === "Identifier" &&
  306. declarator.id.name === "module"
  307. ) {
  308. /** @type {BuildInfo} */
  309. (parser.state.module.buildInfo).moduleArgument =
  310. "__webpack_module__";
  311. }
  312. });
  313. /**
  314. * @param {import("estree").Statement | import("estree").ModuleDeclaration | import("estree").MaybeNamedFunctionDeclaration | import("estree").MaybeNamedClassDeclaration} statement statement
  315. */
  316. const moduleDeclarationHandler = (statement) => {
  317. if (
  318. parser.scope.topLevelScope === true &&
  319. (statement.type === "FunctionDeclaration" ||
  320. statement.type === "ClassDeclaration") &&
  321. statement.id &&
  322. statement.id.name === "module"
  323. ) {
  324. /** @type {BuildInfo} */
  325. (parser.state.module.buildInfo).moduleArgument =
  326. "__webpack_module__";
  327. }
  328. };
  329. parser.hooks.preStatementByType
  330. .for("FunctionDeclaration")
  331. .tap(PLUGIN_NAME, moduleDeclarationHandler);
  332. parser.hooks.preStatementByType
  333. .for("ClassDeclaration")
  334. .tap(PLUGIN_NAME, moduleDeclarationHandler);
  335. for (const key of Object.keys(REPLACEMENTS)) {
  336. const info = REPLACEMENTS[key];
  337. parser.hooks.expression.for(key).tap(PLUGIN_NAME, (expression) => {
  338. const dep = toConstantDependency(parser, info.expr, info.req);
  339. if (key === "__non_webpack_require__" && moduleOutput) {
  340. if (nodeTarget) {
  341. /** @type {JavascriptModuleBuildInfo} */
  342. (parser.state.module.buildInfo).needCreateRequire = true;
  343. } else {
  344. const warning = new WebpackError(
  345. `${PLUGIN_NAME}\n__non_webpack_require__ is only allowed in target node`
  346. );
  347. warning.loc = parser.getLocation(expression);
  348. warning.module = parser.state.module;
  349. compilation.warnings.push(warning);
  350. }
  351. }
  352. return dep(expression);
  353. });
  354. if (info.assign === false) {
  355. parser.hooks.assign.for(key).tap(PLUGIN_NAME, (expr) => {
  356. const err = new WebpackError(`${key} must not be assigned`);
  357. err.loc = parser.getLocation(expr);
  358. throw err;
  359. });
  360. } else if (key === "__webpack_public_path__") {
  361. // Writing the slot needs the scope it lives on, not the runtime
  362. // module computing the value it replaces; a read asks for that.
  363. const writePublicPath = toConstantDependency(parser, info.expr, [
  364. RuntimeGlobals.requireScope
  365. ]);
  366. parser.hooks.assign.for(key).tap(PLUGIN_NAME, (expr) => {
  367. /** @type {JavascriptModuleBuildInfo} */
  368. (parser.state.module.buildInfo).usingPublicPathOverride = true;
  369. addRuntimePublicPathOverride(compilation, parser.state.module);
  370. // A destructuring target is no expression to replace; the read
  371. // handler still spells the global, with a read's requirement.
  372. if (expr.left.type !== "Identifier") return;
  373. return writePublicPath(expr.left);
  374. });
  375. }
  376. if (info.type) {
  377. parser.hooks.evaluateTypeof
  378. .for(key)
  379. .tap(PLUGIN_NAME, evaluateToString(info.type));
  380. }
  381. }
  382. parser.hooks.expression
  383. .for("__webpack_layer__")
  384. .tap(PLUGIN_NAME, (expr) => {
  385. const dep = new ConstDependency(
  386. JSON.stringify(parser.state.module.layer),
  387. /** @type {Range} */ (expr.range)
  388. );
  389. dep.loc = parser.getLocation(expr);
  390. parser.state.module.addPresentationalDependency(dep);
  391. return true;
  392. });
  393. parser.hooks.evaluateIdentifier
  394. .for("__webpack_layer__")
  395. .tap(PLUGIN_NAME, (expr) =>
  396. (parser.state.module.layer === null
  397. ? new BasicEvaluatedExpression().setNull()
  398. : new BasicEvaluatedExpression().setString(
  399. parser.state.module.layer
  400. )
  401. ).setRange(/** @type {Range} */ (expr.range))
  402. );
  403. parser.hooks.evaluateTypeof
  404. .for("__webpack_layer__")
  405. .tap(PLUGIN_NAME, (expr) =>
  406. new BasicEvaluatedExpression()
  407. .setString(
  408. parser.state.module.layer === null ? "object" : "string"
  409. )
  410. .setRange(/** @type {Range} */ (expr.range))
  411. );
  412. parser.hooks.expression
  413. .for("__webpack_module__.id")
  414. .tap(PLUGIN_NAME, (expr) => {
  415. /** @type {JavascriptModuleBuildInfo} */
  416. (parser.state.module.buildInfo).moduleConcatenationBailout =
  417. "__webpack_module__.id";
  418. const moduleArgument = parser.state.module.moduleArgument;
  419. if (moduleArgument === "__webpack_module__") {
  420. const dep = new RuntimeRequirementsDependency([
  421. RuntimeGlobals.moduleId
  422. ]);
  423. dep.loc = parser.getLocation(expr);
  424. parser.state.module.addPresentationalDependency(dep);
  425. } else {
  426. const initDep = new ModuleInitFragmentDependency(
  427. `var __webpack_internal_module_id__ = ${moduleArgument}.id;\n`,
  428. [RuntimeGlobals.moduleId],
  429. "__webpack_internal_module_id__"
  430. );
  431. parser.state.module.addPresentationalDependency(initDep);
  432. const dep = new ConstDependency(
  433. "__webpack_internal_module_id__",
  434. /** @type {Range} */ (expr.range),
  435. []
  436. );
  437. dep.loc = parser.getLocation(expr);
  438. parser.state.module.addPresentationalDependency(dep);
  439. }
  440. return true;
  441. });
  442. parser.hooks.expression
  443. .for("__webpack_module__")
  444. .tap(PLUGIN_NAME, (expr) => {
  445. /** @type {JavascriptModuleBuildInfo} */
  446. (parser.state.module.buildInfo).moduleConcatenationBailout =
  447. "__webpack_module__";
  448. const moduleArgument = parser.state.module.moduleArgument;
  449. if (moduleArgument === "__webpack_module__") {
  450. const dep = new RuntimeRequirementsDependency([
  451. RuntimeGlobals.module
  452. ]);
  453. dep.loc = parser.getLocation(expr);
  454. parser.state.module.addPresentationalDependency(dep);
  455. } else {
  456. const initDep = new ModuleInitFragmentDependency(
  457. `var __webpack_internal_module__ = ${moduleArgument};\n`,
  458. [RuntimeGlobals.module],
  459. "__webpack_internal_module__"
  460. );
  461. parser.state.module.addPresentationalDependency(initDep);
  462. const dep = new ConstDependency(
  463. "__webpack_internal_module__",
  464. /** @type {Range} */ (expr.range),
  465. []
  466. );
  467. dep.loc = parser.getLocation(expr);
  468. parser.state.module.addPresentationalDependency(dep);
  469. }
  470. return true;
  471. });
  472. parser.hooks.evaluateTypeof
  473. .for("__webpack_module__")
  474. .tap(PLUGIN_NAME, evaluateToString("object"));
  475. };
  476. normalModuleFactory.hooks.parser
  477. .for(JAVASCRIPT_MODULE_TYPE_AUTO)
  478. .tap(PLUGIN_NAME, handler);
  479. normalModuleFactory.hooks.parser
  480. .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
  481. .tap(PLUGIN_NAME, handler);
  482. normalModuleFactory.hooks.parser
  483. .for(JAVASCRIPT_MODULE_TYPE_ESM)
  484. .tap(PLUGIN_NAME, handler);
  485. }
  486. );
  487. }
  488. }
  489. APIPlugin.runtimeUsesPublicPathOverride = runtimeUsesPublicPathOverride;
  490. APIPlugin.usesRuntimePublicPathOverride = usesRuntimePublicPathOverride;
  491. module.exports = APIPlugin;