ModuleLibraryPlugin.js 13 KB

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