ModuleLibraryPlugin.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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 RuntimeGlobals = require("../RuntimeGlobals");
  9. const Template = require("../Template");
  10. const ConcatenatedModule = require("../optimize/ConcatenatedModule");
  11. const propertyAccess = require("../util/propertyAccess");
  12. const { getEntryRuntime } = require("../util/runtime");
  13. const AbstractLibraryPlugin = require("./AbstractLibraryPlugin");
  14. /** @typedef {import("webpack-sources").Source} Source */
  15. /** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */
  16. /** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */
  17. /** @typedef {import("../../declarations/WebpackOptions").LibraryExport} LibraryExport */
  18. /** @typedef {import("../Chunk")} Chunk */
  19. /** @typedef {import("../Compiler")} Compiler */
  20. /** @typedef {import("../Module")} Module */
  21. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  22. /** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */
  23. /** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */
  24. /** @typedef {import("../javascript/JavascriptModulesPlugin").ModuleRenderContext} ModuleRenderContext */
  25. /**
  26. * @template T
  27. * @typedef {import("./AbstractLibraryPlugin").LibraryContext<T>} LibraryContext<T>
  28. */
  29. /**
  30. * @typedef {object} ModuleLibraryPluginOptions
  31. * @property {LibraryType} type
  32. */
  33. /**
  34. * @typedef {object} ModuleLibraryPluginParsed
  35. * @property {string} name
  36. * @property {LibraryExport=} export
  37. */
  38. const PLUGIN_NAME = "ModuleLibraryPlugin";
  39. /**
  40. * @typedef {ModuleLibraryPluginParsed} T
  41. * @extends {AbstractLibraryPlugin<ModuleLibraryPluginParsed>}
  42. */
  43. class ModuleLibraryPlugin extends AbstractLibraryPlugin {
  44. /**
  45. * @param {ModuleLibraryPluginOptions} options the plugin options
  46. */
  47. constructor(options) {
  48. super({
  49. pluginName: "ModuleLibraryPlugin",
  50. type: options.type
  51. });
  52. }
  53. /**
  54. * Apply the plugin
  55. * @param {Compiler} compiler the compiler instance
  56. * @returns {void}
  57. */
  58. apply(compiler) {
  59. super.apply(compiler);
  60. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  61. const { onDemandExportsGeneration } =
  62. ConcatenatedModule.getCompilationHooks(compilation);
  63. onDemandExportsGeneration.tap(
  64. PLUGIN_NAME,
  65. (module, runtimes, source, finalName) => {
  66. /** @type {BuildMeta} */
  67. const buildMeta = module.buildMeta || (module.buildMeta = {});
  68. const exportsSourceByRuntime =
  69. buildMeta.exportsSourceByRuntime ||
  70. (buildMeta.exportsSourceByRuntime = new Map());
  71. const exportsFinalNameByRuntime =
  72. buildMeta.exportsFinalNameByRuntime ||
  73. (buildMeta.exportsFinalNameByRuntime = new Map());
  74. for (const runtime of runtimes) {
  75. exportsSourceByRuntime.set(runtime, source);
  76. exportsFinalNameByRuntime.set(runtime, finalName);
  77. }
  78. return true;
  79. }
  80. );
  81. });
  82. }
  83. /**
  84. * @param {Module} module the exporting entry module
  85. * @param {string} entryName the name of the entrypoint
  86. * @param {LibraryContext<T>} libraryContext context
  87. * @returns {void}
  88. */
  89. finishEntryModule(
  90. module,
  91. entryName,
  92. { options, compilation, compilation: { moduleGraph } }
  93. ) {
  94. const runtime = getEntryRuntime(compilation, entryName);
  95. if (options.export) {
  96. const exportsInfo = moduleGraph.getExportInfo(
  97. module,
  98. Array.isArray(options.export) ? options.export[0] : options.export
  99. );
  100. exportsInfo.setUsed(UsageState.Used, runtime);
  101. exportsInfo.canMangleUse = false;
  102. } else {
  103. const exportsInfo = moduleGraph.getExportsInfo(module);
  104. // If the entry module is commonjs, its exports cannot be mangled
  105. if (module.buildMeta && module.buildMeta.treatAsCommonJs) {
  106. exportsInfo.setUsedInUnknownWay(runtime);
  107. } else {
  108. exportsInfo.setAllKnownExportsUsed(runtime);
  109. }
  110. }
  111. moduleGraph.addExtraReason(module, "used as library export");
  112. }
  113. /**
  114. * @param {LibraryOptions} library normalized library option
  115. * @returns {T | false} preprocess as needed by overriding
  116. */
  117. parseOptions(library) {
  118. const { name } = library;
  119. if (name) {
  120. throw new Error(
  121. `Library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}`
  122. );
  123. }
  124. const _name = /** @type {string} */ (name);
  125. return {
  126. name: _name,
  127. export: library.export
  128. };
  129. }
  130. /**
  131. * @param {Source} source source
  132. * @param {Module} module module
  133. * @param {StartupRenderContext} renderContext render context
  134. * @param {LibraryContext<T>} libraryContext context
  135. * @returns {Source} source with library export
  136. */
  137. renderStartup(
  138. source,
  139. module,
  140. {
  141. moduleGraph,
  142. chunk,
  143. codeGenerationResults,
  144. inlined,
  145. inlinedInIIFE,
  146. runtimeTemplate
  147. },
  148. { options, compilation }
  149. ) {
  150. const result = new ConcatSource(source);
  151. const exportsInfo = options.export
  152. ? [
  153. moduleGraph.getExportInfo(
  154. module,
  155. Array.isArray(options.export) ? options.export[0] : options.export
  156. )
  157. ]
  158. : moduleGraph.getExportsInfo(module).orderedExports;
  159. const exportsFinalNameByRuntime =
  160. (module.buildMeta &&
  161. module.buildMeta.exportsFinalNameByRuntime &&
  162. module.buildMeta.exportsFinalNameByRuntime.get(chunk.runtime)) ||
  163. {};
  164. const definitions =
  165. inlined && !inlinedInIIFE ? exportsFinalNameByRuntime : {};
  166. /** @type {string[]} */
  167. const shortHandedExports = [];
  168. /** @type {[string, string][]} */
  169. const exports = [];
  170. const isAsync = moduleGraph.isAsync(module);
  171. const treatAsCommonJs =
  172. module.buildMeta && module.buildMeta.treatAsCommonJs;
  173. const skipRenderDefaultExport = Boolean(treatAsCommonJs);
  174. if (isAsync) {
  175. result.add(
  176. `${RuntimeGlobals.exports} = await ${RuntimeGlobals.exports};\n`
  177. );
  178. }
  179. outer: for (const exportInfo of exportsInfo) {
  180. if (!exportInfo.provided) continue;
  181. const originalName = exportInfo.name;
  182. if (skipRenderDefaultExport && originalName === "default") continue;
  183. const target = exportInfo.findTarget(moduleGraph, (_m) => true);
  184. if (target) {
  185. const reexportsInfo = moduleGraph.getExportsInfo(target.module);
  186. for (const reexportInfo of reexportsInfo.orderedExports) {
  187. if (
  188. reexportInfo.provided === false &&
  189. reexportInfo.name !== "default" &&
  190. reexportInfo.name === /** @type {string[]} */ (target.export)[0]
  191. ) {
  192. continue outer;
  193. }
  194. }
  195. }
  196. const usedName =
  197. /** @type {string} */
  198. (exportInfo.getUsedName(originalName, chunk.runtime));
  199. /** @type {string | undefined} */
  200. const definition = definitions[usedName];
  201. /** @type {string | undefined} */
  202. let finalName;
  203. if (definition) {
  204. finalName = definition;
  205. } else {
  206. finalName = `${RuntimeGlobals.exports}${Template.toIdentifier(originalName)}`;
  207. result.add(
  208. `${runtimeTemplate.renderConst()} ${finalName} = ${RuntimeGlobals.exports}${propertyAccess(
  209. [usedName]
  210. )};\n`
  211. );
  212. }
  213. if (
  214. finalName &&
  215. (finalName.includes(".") ||
  216. finalName.includes("[") ||
  217. finalName.includes("("))
  218. ) {
  219. if (exportInfo.isReexport()) {
  220. const { data } = codeGenerationResults.get(module, chunk.runtime);
  221. const topLevelDeclarations =
  222. (data && data.get("topLevelDeclarations")) ||
  223. (module.buildInfo && module.buildInfo.topLevelDeclarations);
  224. if (topLevelDeclarations && topLevelDeclarations.has(originalName)) {
  225. const name = `${RuntimeGlobals.exports}${Template.toIdentifier(originalName)}`;
  226. result.add(
  227. `${runtimeTemplate.renderConst()} ${name} = ${finalName};\n`
  228. );
  229. shortHandedExports.push(`${name} as ${originalName}`);
  230. } else {
  231. exports.push([originalName, finalName]);
  232. }
  233. } else {
  234. exports.push([originalName, finalName]);
  235. }
  236. } else {
  237. shortHandedExports.push(
  238. definition && finalName === originalName
  239. ? finalName
  240. : `${finalName} as ${originalName}`
  241. );
  242. }
  243. }
  244. if (treatAsCommonJs) {
  245. shortHandedExports.push(`${RuntimeGlobals.exports} as default`);
  246. }
  247. if (shortHandedExports.length > 0) {
  248. result.add(`export { ${shortHandedExports.join(", ")} };\n`);
  249. }
  250. for (const [exportName, final] of exports) {
  251. result.add(
  252. `export ${runtimeTemplate.renderConst()} ${exportName} = ${final};\n`
  253. );
  254. }
  255. return result;
  256. }
  257. /**
  258. * @param {Source} source source
  259. * @param {Module} module module
  260. * @param {ModuleRenderContext} renderContext render context
  261. * @param {Omit<LibraryContext<T>, "options">} libraryContext context
  262. * @returns {Source} source with library export
  263. */
  264. renderModuleContent(
  265. source,
  266. module,
  267. { factory, inlinedInIIFE, chunk },
  268. libraryContext
  269. ) {
  270. const exportsSource =
  271. module.buildMeta &&
  272. module.buildMeta.exportsSourceByRuntime &&
  273. module.buildMeta.exportsSourceByRuntime.get(chunk.runtime);
  274. // Re-add the module's exports source when rendered in factory or as an inlined startup module wrapped in an IIFE
  275. if ((inlinedInIIFE || factory) && exportsSource) {
  276. return new ConcatSource(exportsSource, source);
  277. }
  278. return source;
  279. }
  280. }
  281. module.exports = ModuleLibraryPlugin;