ModuleLibraryPlugin.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { ConcatSource } = require("webpack-sources");
  7. const { UsageState } = require("../ExportsInfo");
  8. const ExternalModule = require("../ExternalModule");
  9. const RuntimeGlobals = require("../RuntimeGlobals");
  10. const Template = require("../Template");
  11. const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
  12. const ConcatenatedModule = require("../optimize/ConcatenatedModule");
  13. const { propertyAccess } = require("../util/property");
  14. const { getEntryRuntime, getRuntimeKey } = require("../util/runtime");
  15. const AbstractLibraryPlugin = require("./AbstractLibraryPlugin");
  16. /** @typedef {import("webpack-sources").Source} Source */
  17. /** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
  18. /** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
  19. /** @typedef {import("../../declarations/WebpackOptions").LibraryExport} LibraryExport */
  20. /** @typedef {import("../Chunk")} Chunk */
  21. /** @typedef {import("../Compiler")} Compiler */
  22. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  23. /** @typedef {import("../Module")} Module */
  24. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  25. /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
  26. /** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */
  27. /** @typedef {import("../javascript/JavascriptModulesPlugin").ModuleRenderContext} ModuleRenderContext */
  28. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  29. /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
  30. /**
  31. * Defines the shared type used by this module.
  32. * @template T
  33. * @typedef {import("./AbstractLibraryPlugin").LibraryContext<T>} LibraryContext<T>
  34. */
  35. /**
  36. * Defines the module library plugin options type used by this module.
  37. * @typedef {object} ModuleLibraryPluginOptions
  38. * @property {LibraryType} type
  39. */
  40. /**
  41. * Defines the module library plugin parsed type used by this module.
  42. * @typedef {object} ModuleLibraryPluginParsed
  43. * @property {string} name
  44. * @property {LibraryExport=} export
  45. */
  46. const PLUGIN_NAME = "ModuleLibraryPlugin";
  47. /**
  48. * Represents the module library plugin runtime component.
  49. * @typedef {ModuleLibraryPluginParsed} T
  50. * @extends {AbstractLibraryPlugin<ModuleLibraryPluginParsed>}
  51. */
  52. class ModuleLibraryPlugin extends AbstractLibraryPlugin {
  53. /**
  54. * Creates an instance of ModuleLibraryPlugin.
  55. * @param {ModuleLibraryPluginOptions} options the plugin options
  56. */
  57. constructor(options) {
  58. super({
  59. pluginName: "ModuleLibraryPlugin",
  60. type: options.type
  61. });
  62. }
  63. /**
  64. * Applies the plugin by registering its hooks on the compiler.
  65. * @param {Compiler} compiler the compiler instance
  66. * @returns {void}
  67. */
  68. apply(compiler) {
  69. super.apply(compiler);
  70. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  71. const { onDemandExportsGeneration } =
  72. ConcatenatedModule.getCompilationHooks(compilation);
  73. onDemandExportsGeneration.tap(
  74. PLUGIN_NAME,
  75. (module, runtimes, source, finalName) => {
  76. /** @type {BuildMeta} */
  77. const buildMeta = module.buildMeta || (module.buildMeta = {});
  78. /** @type {BuildMeta["exportsSourceByRuntime"]} */
  79. const exportsSourceByRuntime =
  80. buildMeta.exportsSourceByRuntime ||
  81. (buildMeta.exportsSourceByRuntime = new Map());
  82. /** @type {BuildMeta["exportsFinalNameByRuntime"]} */
  83. const exportsFinalNameByRuntime =
  84. buildMeta.exportsFinalNameByRuntime ||
  85. (buildMeta.exportsFinalNameByRuntime = new Map());
  86. for (const runtime of runtimes) {
  87. const key = getRuntimeKey(runtime);
  88. exportsSourceByRuntime.set(key, source);
  89. exportsFinalNameByRuntime.set(key, finalName);
  90. }
  91. return true;
  92. }
  93. );
  94. });
  95. }
  96. /**
  97. * Finish entry module.
  98. * @param {Module} module the exporting entry module
  99. * @param {string} entryName the name of the entrypoint
  100. * @param {LibraryContext<T>} libraryContext context
  101. * @returns {void}
  102. */
  103. finishEntryModule(
  104. module,
  105. entryName,
  106. { options, compilation, compilation: { moduleGraph } }
  107. ) {
  108. const runtime = getEntryRuntime(compilation, entryName);
  109. if (options.export) {
  110. const exportsInfo = moduleGraph.getExportInfo(
  111. module,
  112. Array.isArray(options.export) ? options.export[0] : options.export
  113. );
  114. exportsInfo.setUsed(UsageState.Used, runtime);
  115. exportsInfo.canMangleUse = false;
  116. } else {
  117. const exportsInfo = moduleGraph.getExportsInfo(module);
  118. if (
  119. // If the entry module is commonjs, its exports cannot be mangled
  120. (module.buildMeta && module.buildMeta.treatAsCommonJs) ||
  121. // The entry module provides unknown exports
  122. exportsInfo._otherExportsInfo.provided === null
  123. ) {
  124. exportsInfo.setUsedInUnknownWay(runtime);
  125. } else {
  126. exportsInfo.setAllKnownExportsUsed(runtime);
  127. }
  128. }
  129. moduleGraph.addExtraReason(module, "used as library export");
  130. }
  131. /**
  132. * Returns preprocess as needed by overriding.
  133. * @param {LibraryOptions} library normalized library option
  134. * @returns {T} preprocess as needed by overriding
  135. */
  136. parseOptions(library) {
  137. const { name } = library;
  138. if (name) {
  139. throw new Error(
  140. `Library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}`
  141. );
  142. }
  143. const _name = /** @type {string} */ (name);
  144. return {
  145. name: _name,
  146. export: library.export
  147. };
  148. }
  149. /**
  150. * Analyze unknown provided exports.
  151. * @param {Source} source source
  152. * @param {Module} module module
  153. * @param {ModuleGraph} moduleGraph moduleGraph
  154. * @param {RuntimeSpec} runtime chunk runtime
  155. * @param {[string, string][]} exports exports
  156. * @param {Set<string>} alreadyRenderedExports already rendered exports
  157. * @returns {ConcatSource} source with null provided exports
  158. */
  159. _analyzeUnknownProvidedExports(
  160. source,
  161. module,
  162. moduleGraph,
  163. runtime,
  164. exports,
  165. alreadyRenderedExports
  166. ) {
  167. const result = new ConcatSource(source);
  168. /** @type {Set<string>} */
  169. const moduleRequests = new Set();
  170. /** @type {Map<string, string>} */
  171. const unknownProvidedExports = new Map();
  172. /**
  173. * Resolves dynamic star reexport.
  174. * @param {Module} module the module
  175. * @param {boolean} isDynamicReexport if module is dynamic reexported
  176. */
  177. const resolveDynamicStarReexport = (module, isDynamicReexport) => {
  178. for (const connection of moduleGraph.getOutgoingConnections(module)) {
  179. const dep = connection.dependency;
  180. // Only handle star-reexport statement
  181. if (
  182. dep instanceof HarmonyExportImportedSpecifierDependency &&
  183. dep.name === null
  184. ) {
  185. const importedModule = connection.resolvedModule;
  186. const importedModuleExportsInfo =
  187. moduleGraph.getExportsInfo(importedModule);
  188. // The imported module provides unknown exports
  189. // So keep the reexports rendered in the bundle
  190. if (
  191. dep.getMode(moduleGraph, runtime).type === "dynamic-reexport" &&
  192. importedModuleExportsInfo._otherExportsInfo.provided === null
  193. ) {
  194. // Handle export * from 'external'
  195. if (importedModule instanceof ExternalModule) {
  196. moduleRequests.add(importedModule.userRequest);
  197. } else {
  198. resolveDynamicStarReexport(importedModule, true);
  199. }
  200. }
  201. // If importer modules existing `dynamic-reexport` dependency
  202. // We should keep export statement rendered in the bundle
  203. else if (isDynamicReexport) {
  204. for (const exportInfo of importedModuleExportsInfo.orderedExports) {
  205. if (!exportInfo.provided || exportInfo.name === "default") {
  206. continue;
  207. }
  208. const originalName = exportInfo.name;
  209. const usedName = exportInfo.getUsedName(originalName, runtime);
  210. if (!alreadyRenderedExports.has(originalName) && usedName) {
  211. unknownProvidedExports.set(originalName, usedName);
  212. }
  213. }
  214. }
  215. }
  216. }
  217. };
  218. resolveDynamicStarReexport(module, false);
  219. for (const request of moduleRequests) {
  220. result.add(`export * from "${request}";\n`);
  221. }
  222. for (const [origin, used] of unknownProvidedExports) {
  223. exports.push([
  224. origin,
  225. `${RuntimeGlobals.exports}${propertyAccess([used])}`
  226. ]);
  227. }
  228. return result;
  229. }
  230. /**
  231. * Renders source with library export.
  232. * @param {Source} source source
  233. * @param {Module} module module
  234. * @param {StartupRenderContext} renderContext render context
  235. * @param {LibraryContext<T>} libraryContext context
  236. * @returns {Source} source with library export
  237. */
  238. renderStartup(source, module, renderContext, { options, compilation }) {
  239. const {
  240. moduleGraph,
  241. chunk,
  242. codeGenerationResults,
  243. inlined,
  244. inlinedInIIFE,
  245. runtimeTemplate
  246. } = renderContext;
  247. let result = new ConcatSource(source);
  248. const exportInfos = options.export
  249. ? [
  250. moduleGraph.getExportInfo(
  251. module,
  252. Array.isArray(options.export) ? options.export[0] : options.export
  253. )
  254. ]
  255. : moduleGraph.getExportsInfo(module).orderedExports;
  256. const exportsFinalNameByRuntime =
  257. (module.buildMeta &&
  258. module.buildMeta.exportsFinalNameByRuntime &&
  259. module.buildMeta.exportsFinalNameByRuntime.get(
  260. getRuntimeKey(chunk.runtime)
  261. )) ||
  262. {};
  263. const isInlinedEntryWithoutIIFE = inlined && !inlinedInIIFE;
  264. // Direct export bindings from on-demand concatenation
  265. const definitions = isInlinedEntryWithoutIIFE
  266. ? exportsFinalNameByRuntime
  267. : {};
  268. /** @type {string[]} */
  269. const shortHandedExports = [];
  270. /** @type {[string, string][]} */
  271. const exports = [];
  272. /** @type {Set<string>} */
  273. const alreadyRenderedExports = new Set();
  274. const isAsync = moduleGraph.isAsync(module);
  275. const treatAsCommonJs =
  276. module.buildMeta && module.buildMeta.treatAsCommonJs;
  277. const skipRenderDefaultExport = Boolean(treatAsCommonJs);
  278. const moduleExportsInfo = moduleGraph.getExportsInfo(module);
  279. // Define ESM compatibility flag will rely on `__webpack_exports__`
  280. const needHarmonyCompatibilityFlag =
  281. moduleExportsInfo.otherExportsInfo.getUsed(chunk.runtime) !==
  282. UsageState.Unused ||
  283. moduleExportsInfo
  284. .getReadOnlyExportInfo("__esModule")
  285. .getUsed(chunk.runtime) !== UsageState.Unused;
  286. let needExportsDeclaration =
  287. !isInlinedEntryWithoutIIFE || isAsync || needHarmonyCompatibilityFlag;
  288. if (isAsync) {
  289. result.add(
  290. `${RuntimeGlobals.exports} = await ${RuntimeGlobals.exports};\n`
  291. );
  292. }
  293. // Try to find all known exports of the entry module
  294. outer: for (const exportInfo of exportInfos) {
  295. if (!exportInfo.provided) continue;
  296. const originalName = exportInfo.name;
  297. // Skip rendering the default export in some cases
  298. if (skipRenderDefaultExport && originalName === "default") continue;
  299. // Try to find all exports from the reexported modules
  300. const target = exportInfo.findTarget(moduleGraph, (_m) => true);
  301. if (target) {
  302. const reexportsInfo = moduleGraph.getExportsInfo(target.module);
  303. for (const reexportInfo of reexportsInfo.orderedExports) {
  304. if (
  305. reexportInfo.provided === false &&
  306. reexportInfo.name !== "default" &&
  307. reexportInfo.name === /** @type {string[]} */ (target.export)[0]
  308. ) {
  309. continue outer;
  310. }
  311. }
  312. }
  313. const usedName =
  314. /** @type {string} */
  315. (exportInfo.getUsedName(originalName, chunk.runtime));
  316. /** @type {string | undefined} */
  317. const definition = definitions[usedName];
  318. /** @type {string | undefined} */
  319. let finalName;
  320. if (definition) {
  321. finalName = definition;
  322. } else {
  323. // Fallback to `__webpack_exports__` property access
  324. // when no direct export binding was found
  325. finalName = `${RuntimeGlobals.exports}${Template.toIdentifier(originalName)}`;
  326. needExportsDeclaration = true;
  327. result.add(
  328. `${runtimeTemplate.renderConst()} ${finalName} = ${RuntimeGlobals.exports}${propertyAccess(
  329. [usedName]
  330. )};\n`
  331. );
  332. }
  333. if (
  334. // If the name includes `property access` and `call expressions`
  335. finalName &&
  336. (finalName.includes(".") ||
  337. finalName.includes("[") ||
  338. finalName.includes("("))
  339. ) {
  340. if (exportInfo.isReexport()) {
  341. const { data } = codeGenerationResults.get(module, chunk.runtime);
  342. const topLevelDeclarations =
  343. (data && data.get("topLevelDeclarations")) ||
  344. (module.buildInfo && module.buildInfo.topLevelDeclarations);
  345. if (topLevelDeclarations && topLevelDeclarations.has(originalName)) {
  346. const name = `${RuntimeGlobals.exports}${Template.toIdentifier(originalName)}`;
  347. result.add(
  348. `${runtimeTemplate.renderConst()} ${name} = ${finalName};\n`
  349. );
  350. shortHandedExports.push(`${name} as ${originalName}`);
  351. } else {
  352. exports.push([originalName, finalName]);
  353. }
  354. } else {
  355. exports.push([originalName, finalName]);
  356. }
  357. } else {
  358. shortHandedExports.push(
  359. definition && finalName === originalName
  360. ? finalName
  361. : `${finalName} as ${originalName}`
  362. );
  363. }
  364. alreadyRenderedExports.add(originalName);
  365. }
  366. // Add default export `__webpack_exports__` statement to keep better compatibility
  367. if (treatAsCommonJs) {
  368. needExportsDeclaration = true;
  369. shortHandedExports.push(`${RuntimeGlobals.exports} as default`);
  370. }
  371. if (shortHandedExports.length > 0) {
  372. result.add(`export { ${shortHandedExports.join(", ")} };\n`);
  373. }
  374. result = this._analyzeUnknownProvidedExports(
  375. result,
  376. module,
  377. moduleGraph,
  378. chunk.runtime,
  379. exports,
  380. alreadyRenderedExports
  381. );
  382. for (const [exportName, final] of exports) {
  383. result.add(
  384. `export ${runtimeTemplate.renderConst()} ${exportName} = ${final};\n`
  385. );
  386. }
  387. if (!needExportsDeclaration) {
  388. renderContext.needExportsDeclaration = false;
  389. }
  390. return result;
  391. }
  392. /**
  393. * Renders module content.
  394. * @param {Source} source source
  395. * @param {Module} module module
  396. * @param {ModuleRenderContext} renderContext render context
  397. * @param {Omit<LibraryContext<T>, "options">} libraryContext context
  398. * @returns {Source} source with library export
  399. */
  400. renderModuleContent(
  401. source,
  402. module,
  403. { factory, inlinedInIIFE, chunk },
  404. libraryContext
  405. ) {
  406. const exportsSource =
  407. module.buildMeta &&
  408. module.buildMeta.exportsSourceByRuntime &&
  409. module.buildMeta.exportsSourceByRuntime.get(getRuntimeKey(chunk.runtime));
  410. // Re-add the module's exports source when rendered in factory
  411. // or as an inlined startup module wrapped in an IIFE
  412. if ((inlinedInIIFE || factory) && exportsSource) {
  413. return new ConcatSource(exportsSource, source);
  414. }
  415. return source;
  416. }
  417. }
  418. module.exports = ModuleLibraryPlugin;