JavascriptGenerator.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. const { RawSource, ReplaceSource } = require("webpack-sources");
  8. const Generator = require("../Generator");
  9. const InitFragment = require("../InitFragment");
  10. const { JAVASCRIPT_TYPES } = require("../ModuleSourceTypeConstants");
  11. const RuntimeGlobals = require("../RuntimeGlobals");
  12. const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency");
  13. /** @import { Source } from "webpack-sources" */
  14. /** @import { DependencyConstructor } from "../Compilation" */
  15. /** @import DependenciesBlock from "../DependenciesBlock" */
  16. /** @import Dependency from "../Dependency" */
  17. /**
  18. * @import DependencyTemplate, {
  19. * DependencyTemplateContext
  20. * } from "../DependencyTemplate"
  21. */
  22. /** @import { GenerateContext } from "../Generator" */
  23. /**
  24. * @import Module, {
  25. * ConcatenationBailoutReasonContext,
  26. * SourceType,
  27. * SourceTypes
  28. * } from "../Module"
  29. */
  30. /** @import { JavascriptModuleBuildInfo } from "./JavascriptModule" */
  31. /** @import NormalModule from "../NormalModule" */
  32. const DEFAULT_SOURCE = {
  33. source() {
  34. return new RawSource("throw new Error('No source available');");
  35. },
  36. /**
  37. * Returns the estimated size for the requested source type.
  38. * @returns {number} size of the DEFAULT_SOURCE.source()
  39. */
  40. size() {
  41. return 39;
  42. }
  43. };
  44. // TODO: clean up this file
  45. // replace with newer constructs
  46. // A wrapped CommonJS module keeps real `module`/`exports` objects and top-level
  47. // `this`, so only requirements that need a module object webpack does not
  48. // synthesize here (id, loaded state, cache, decorators) block wrapping.
  49. /** @type {ReadonlySet<string>} */
  50. const WRAPPING_INCOMPATIBLE_RUNTIME_REQUIREMENTS = new Set([
  51. RuntimeGlobals.moduleId,
  52. RuntimeGlobals.moduleLoaded,
  53. RuntimeGlobals.moduleCache,
  54. RuntimeGlobals.harmonyModuleDecorator,
  55. RuntimeGlobals.nodeModuleDecorator
  56. ]);
  57. const deprecatedGetInitFragments = util.deprecate(
  58. /**
  59. * Handles the callback logic for this hook.
  60. * @param {DependencyTemplate} template template
  61. * @param {Dependency} dependency dependency
  62. * @param {DependencyTemplateContext} templateContext template context
  63. * @returns {InitFragment<GenerateContext>[]} init fragments
  64. */
  65. (template, dependency, templateContext) =>
  66. /** @type {DependencyTemplate & { getInitFragments: (dependency: Dependency, dependencyTemplateContext: DependencyTemplateContext) => InitFragment<GenerateContext>[] }} */
  67. (template).getInitFragments(dependency, templateContext),
  68. "DependencyTemplate.getInitFragment is deprecated (use apply(dep, source, { initFragments }) instead)",
  69. "DEP_WEBPACK_JAVASCRIPT_GENERATOR_GET_INIT_FRAGMENTS"
  70. );
  71. class JavascriptGenerator extends Generator {
  72. /**
  73. * Returns the source types available for this module.
  74. * @param {NormalModule} module fresh module
  75. * @returns {SourceTypes} available types (do not mutate)
  76. */
  77. getTypes(module) {
  78. return JAVASCRIPT_TYPES;
  79. }
  80. /**
  81. * Returns the estimated size for the requested source type.
  82. * @param {NormalModule} module the module
  83. * @param {SourceType=} type source type
  84. * @returns {number} estimate size of the module
  85. */
  86. getSize(module, type) {
  87. const originalSource = module.originalSource();
  88. if (!originalSource) {
  89. return DEFAULT_SOURCE.size();
  90. }
  91. return originalSource.size();
  92. }
  93. /**
  94. * Returns the reason this module cannot be concatenated, when one exists.
  95. * @param {NormalModule} module module for which the bailout reason should be determined
  96. * @param {ConcatenationBailoutReasonContext} context context
  97. * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated
  98. */
  99. getConcatenationBailoutReason(module, context) {
  100. const buildMeta = module.buildMeta;
  101. const buildInfo = /** @type {JavascriptModuleBuildInfo | undefined} */ (
  102. module.buildInfo
  103. );
  104. const presentationalDependencies = module.presentationalDependencies;
  105. // An ESM is a "namespace" module carrying the interop flag the harmony
  106. // parser emits; anything else is treated as CommonJS below
  107. const isESM =
  108. buildMeta !== undefined &&
  109. buildMeta.exportsType === "namespace" &&
  110. presentationalDependencies !== undefined &&
  111. presentationalDependencies.some(
  112. (d) => d instanceof HarmonyCompatibilityDependency
  113. );
  114. if (!isESM) {
  115. // CommonJS concatenation is opt-in via optimization.concatenateModules.commonjs
  116. if (!context.concatenateCommonJsModules) {
  117. return "Module is not an ECMAScript module";
  118. }
  119. // Concatenated CommonJS always runs wrapped, with real `module`/`exports`,
  120. // so only a foreign exports shape rules the module out (an undefined
  121. // exports type is a dynamic `module.exports`, which wrapping supports)
  122. if (buildMeta === undefined) {
  123. return "Module is neither an ECMAScript module nor a CommonJS";
  124. }
  125. // Sloppy-mode modules bail out in ModuleConcatenationPlugin anyway,
  126. // so skip the dependency scan for them (most of node_modules)
  127. if (buildInfo === undefined || !buildInfo.strict) {
  128. return "Module is not in strict mode";
  129. }
  130. if (presentationalDependencies !== undefined) {
  131. for (const dep of presentationalDependencies) {
  132. const runtimeRequirements =
  133. /** @type {{ runtimeRequirements?: ReadonlySet<string> | null }} */
  134. (dep).runtimeRequirements;
  135. if (!runtimeRequirements) continue;
  136. for (const requirement of runtimeRequirements) {
  137. if (WRAPPING_INCOMPATIBLE_RUNTIME_REQUIREMENTS.has(requirement)) {
  138. return `Module uses ${requirement}`;
  139. }
  140. }
  141. }
  142. }
  143. }
  144. // Some expressions are not compatible with module concatenation
  145. // because they may produce unexpected results. The plugin bails out
  146. // if some were detected upfront.
  147. if (buildInfo !== undefined && buildInfo.moduleConcatenationBailout) {
  148. return `Module uses ${buildInfo.moduleConcatenationBailout}`;
  149. }
  150. }
  151. /**
  152. * Processes the provided module.
  153. * @param {Dependency} dependency the dependency to generate
  154. * @param {ReplaceSource} source the current replace source which can be modified
  155. * @param {DependencyTemplateContext} templateContext the template context shared for the whole module
  156. * @param {GenerateContext} generateContext the render context
  157. * @returns {void}
  158. */
  159. sourceDependency(dependency, source, templateContext, generateContext) {
  160. const constructor =
  161. /** @type {DependencyConstructor} */
  162. (dependency.constructor);
  163. const template = generateContext.dependencyTemplates.get(constructor);
  164. if (!template) {
  165. throw new Error(
  166. `No template for dependency: ${dependency.constructor.name}`
  167. );
  168. }
  169. template.apply(dependency, source, templateContext);
  170. // TODO remove in webpack 6
  171. if ("getInitFragments" in template) {
  172. const fragments = deprecatedGetInitFragments(
  173. template,
  174. dependency,
  175. templateContext
  176. );
  177. if (fragments) {
  178. for (const fragment of fragments) {
  179. templateContext.initFragments.push(fragment);
  180. }
  181. }
  182. }
  183. }
  184. /**
  185. * Processes the provided module.
  186. * @param {Module} module the module to generate
  187. * @param {DependenciesBlock} block the dependencies block which will be processed
  188. * @param {DependencyTemplateContext} templateContext the template context shared for the whole module
  189. * @param {ReplaceSource} source the current replace source which can be modified
  190. * @param {GenerateContext} generateContext the generateContext
  191. * @returns {void}
  192. */
  193. sourceBlock(module, block, templateContext, source, generateContext) {
  194. for (const dependency of block.dependencies) {
  195. this.sourceDependency(
  196. dependency,
  197. source,
  198. templateContext,
  199. generateContext
  200. );
  201. }
  202. for (const childBlock of block.blocks) {
  203. this.sourceBlock(
  204. module,
  205. childBlock,
  206. templateContext,
  207. source,
  208. generateContext
  209. );
  210. }
  211. }
  212. /**
  213. * Processes the provided module.
  214. * @param {Module} module the module to generate
  215. * @param {InitFragment<GenerateContext>[]} initFragments mutable list of init fragments
  216. * @param {ReplaceSource} source the current replace source which can be modified
  217. * @param {GenerateContext} generateContext the generateContext
  218. * @returns {void}
  219. */
  220. sourceModule(module, initFragments, source, generateContext) {
  221. /** @type {InitFragment<GenerateContext>[] | undefined} */
  222. let chunkInitFragments;
  223. // one template context for all dependencies of the module (hot path)
  224. /** @type {DependencyTemplateContext} */
  225. const templateContext = {
  226. runtimeTemplate: generateContext.runtimeTemplate,
  227. dependencyTemplates: generateContext.dependencyTemplates,
  228. moduleGraph: generateContext.moduleGraph,
  229. chunkGraph: generateContext.chunkGraph,
  230. module,
  231. runtime: generateContext.runtime,
  232. runtimeRequirements: generateContext.runtimeRequirements,
  233. concatenationScope: generateContext.concatenationScope,
  234. codeGenerationResults:
  235. /** @type {NonNullable<GenerateContext["codeGenerationResults"]>} */
  236. (generateContext.codeGenerationResults),
  237. initFragments,
  238. get chunkInitFragments() {
  239. if (!chunkInitFragments) {
  240. const data =
  241. /** @type {NonNullable<GenerateContext["getData"]>} */
  242. (generateContext.getData)();
  243. chunkInitFragments = data.get("chunkInitFragments");
  244. if (!chunkInitFragments) {
  245. chunkInitFragments = [];
  246. data.set("chunkInitFragments", chunkInitFragments);
  247. }
  248. }
  249. return chunkInitFragments;
  250. }
  251. };
  252. for (const dependency of module.dependencies) {
  253. this.sourceDependency(
  254. dependency,
  255. source,
  256. templateContext,
  257. generateContext
  258. );
  259. }
  260. if (module.presentationalDependencies !== undefined) {
  261. for (const dependency of module.presentationalDependencies) {
  262. this.sourceDependency(
  263. dependency,
  264. source,
  265. templateContext,
  266. generateContext
  267. );
  268. }
  269. }
  270. for (const childBlock of module.blocks) {
  271. this.sourceBlock(
  272. module,
  273. childBlock,
  274. templateContext,
  275. source,
  276. generateContext
  277. );
  278. }
  279. }
  280. /**
  281. * Generates generated code for this runtime module.
  282. * @param {NormalModule} module module for which the code should be generated
  283. * @param {GenerateContext} generateContext context for generate
  284. * @returns {Source | null} generated code
  285. */
  286. generate(module, generateContext) {
  287. const originalSource = module.originalSource();
  288. if (!originalSource) {
  289. return DEFAULT_SOURCE.source();
  290. }
  291. const source = new ReplaceSource(originalSource);
  292. /** @type {InitFragment<GenerateContext>[]} */
  293. const initFragments = [];
  294. this.sourceModule(module, initFragments, source, generateContext);
  295. return InitFragment.addToSource(source, initFragments, generateContext);
  296. }
  297. /**
  298. * Generates fallback output for the provided error condition.
  299. * @param {Error} error the error
  300. * @param {NormalModule} module module for which the code should be generated
  301. * @param {GenerateContext} generateContext context for generate
  302. * @returns {Source | null} generated code
  303. */
  304. generateError(error, module, generateContext) {
  305. return new RawSource(Generator.throwBuildErrorCode(error));
  306. }
  307. }
  308. module.exports = JavascriptGenerator;