HarmonyAcceptDependency.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Template = require("../Template");
  7. const AwaitDependenciesInitFragment = require("../async-modules/AwaitDependenciesInitFragment");
  8. const makeSerializable = require("../util/makeSerializable");
  9. const HarmonyImportDependency = require("./HarmonyImportDependency");
  10. const { ImportPhaseUtils } = require("./ImportPhase");
  11. const NullDependency = require("./NullDependency");
  12. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  13. /** @typedef {import("../Dependency")} Dependency */
  14. /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
  15. /** @typedef {import("../javascript/JavascriptParser").Range} Range */
  16. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
  17. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
  18. /** @typedef {import("./HarmonyAcceptImportDependency")} HarmonyAcceptImportDependency */
  19. /** @typedef {import("../Module")} Module */
  20. /** @typedef {import("../Module").ModuleId} ModuleId */
  21. class HarmonyAcceptDependency extends NullDependency {
  22. /**
  23. * Creates an instance of HarmonyAcceptDependency.
  24. * @param {Range} range expression range
  25. * @param {HarmonyAcceptImportDependency[]} dependencies import dependencies
  26. * @param {boolean} hasCallback true, if the range wraps an existing callback
  27. */
  28. constructor(range, dependencies, hasCallback) {
  29. super();
  30. this.range = range;
  31. this.dependencies = dependencies;
  32. this.hasCallback = hasCallback;
  33. }
  34. get type() {
  35. return "accepted harmony modules";
  36. }
  37. /**
  38. * Serializes this instance into the provided serializer context.
  39. * @param {ObjectSerializerContext} context context
  40. */
  41. serialize(context) {
  42. const { write } = context;
  43. write(this.range);
  44. write(this.dependencies);
  45. write(this.hasCallback);
  46. super.serialize(context);
  47. }
  48. /**
  49. * Restores this instance from the provided deserializer context.
  50. * @param {ObjectDeserializerContext} context context
  51. */
  52. deserialize(context) {
  53. const { read } = context;
  54. this.range = read();
  55. this.dependencies = read();
  56. this.hasCallback = read();
  57. super.deserialize(context);
  58. }
  59. }
  60. makeSerializable(
  61. HarmonyAcceptDependency,
  62. "webpack/lib/dependencies/HarmonyAcceptDependency"
  63. );
  64. HarmonyAcceptDependency.Template = class HarmonyAcceptDependencyTemplate extends (
  65. NullDependency.Template
  66. ) {
  67. /**
  68. * Applies the plugin by registering its hooks on the compiler.
  69. * @param {Dependency} dependency the dependency for which the template should be applied
  70. * @param {ReplaceSource} source the current replace source which can be modified
  71. * @param {DependencyTemplateContext} templateContext the context object
  72. * @returns {void}
  73. */
  74. apply(dependency, source, templateContext) {
  75. const dep = /** @type {HarmonyAcceptDependency} */ (dependency);
  76. const {
  77. module,
  78. runtime,
  79. runtimeRequirements,
  80. runtimeTemplate,
  81. moduleGraph,
  82. chunkGraph
  83. } = templateContext;
  84. /**
  85. * Gets dependency module id.
  86. * @param {Dependency} dependency the dependency to get module id for
  87. * @returns {ModuleId | null} the module id or null if not found
  88. */
  89. const getDependencyModuleId = (dependency) =>
  90. chunkGraph.getModuleId(
  91. /** @type {Module} */ (moduleGraph.getModule(dependency))
  92. );
  93. /**
  94. * Checks whether this harmony accept dependency is related harmony import dependency.
  95. * @param {Dependency} a the first dependency
  96. * @param {Dependency} b the second dependency
  97. * @returns {boolean} true if the dependencies are related
  98. */
  99. const isRelatedHarmonyImportDependency = (a, b) =>
  100. a !== b &&
  101. b instanceof HarmonyImportDependency &&
  102. getDependencyModuleId(a) === getDependencyModuleId(b);
  103. /**
  104. * HarmonyAcceptImportDependency lacks a lot of information, such as the defer property.
  105. * One HarmonyAcceptImportDependency may need to generate multiple ImportStatements.
  106. * Therefore, we find its original HarmonyImportDependency for code generation.
  107. * @param {HarmonyAcceptImportDependency} dependency the dependency to get harmony import dependencies for
  108. * @returns {HarmonyImportDependency[]} array of related harmony import dependencies
  109. */
  110. const getHarmonyImportDependencies = (dependency) => {
  111. /** @type {HarmonyImportDependency[]} */
  112. const result = [];
  113. /** @type {HarmonyImportDependency | null} */
  114. let deferDependency = null;
  115. /** @type {HarmonyImportDependency | null} */
  116. let noDeferredDependency = null;
  117. for (const d of module.dependencies) {
  118. if (deferDependency && noDeferredDependency) break;
  119. if (isRelatedHarmonyImportDependency(dependency, d)) {
  120. if (
  121. ImportPhaseUtils.isDefer(
  122. /** @type {HarmonyImportDependency} */ (d).phase
  123. )
  124. ) {
  125. deferDependency = /** @type {HarmonyImportDependency} */ (d);
  126. } else {
  127. noDeferredDependency = /** @type {HarmonyImportDependency} */ (d);
  128. }
  129. }
  130. }
  131. if (deferDependency) result.push(deferDependency);
  132. if (noDeferredDependency) result.push(noDeferredDependency);
  133. if (result.length === 0) {
  134. // fallback to the original dependency
  135. result.push(dependency);
  136. }
  137. return result;
  138. };
  139. /** @type {HarmonyImportDependency[]} */
  140. const syncDeps = [];
  141. /** @type {HarmonyAcceptImportDependency[]} */
  142. const asyncDeps = [];
  143. for (const dependency of dep.dependencies) {
  144. const connection = moduleGraph.getConnection(dependency);
  145. if (connection && moduleGraph.isAsync(connection.module)) {
  146. asyncDeps.push(dependency);
  147. } else {
  148. syncDeps.push(...getHarmonyImportDependencies(dependency));
  149. }
  150. }
  151. let content = syncDeps
  152. .map((dependency) => {
  153. const referencedModule = moduleGraph.getModule(dependency);
  154. return {
  155. dependency,
  156. runtimeCondition: referencedModule
  157. ? HarmonyImportDependency.Template.getImportEmittedRuntime(
  158. module,
  159. referencedModule
  160. )
  161. : false
  162. };
  163. })
  164. .filter(({ runtimeCondition }) => runtimeCondition !== false)
  165. .map(({ dependency, runtimeCondition }) => {
  166. const condition = runtimeTemplate.runtimeConditionExpression({
  167. chunkGraph,
  168. runtime,
  169. runtimeCondition,
  170. runtimeRequirements
  171. });
  172. const s = dependency.getImportStatement(true, templateContext);
  173. const code = s[0] + s[1];
  174. if (condition !== "true") {
  175. return `if (${condition}) {\n${Template.indent(code)}\n}\n`;
  176. }
  177. return code;
  178. })
  179. .join("");
  180. const promises = new Map(
  181. asyncDeps.map((dependency) => [
  182. dependency.getImportVar(moduleGraph),
  183. dependency.getModuleExports(templateContext)
  184. ])
  185. );
  186. let optAsync = "";
  187. if (promises.size !== 0) {
  188. optAsync = "async ";
  189. content += new AwaitDependenciesInitFragment(promises).getContent({
  190. ...templateContext,
  191. type: "javascript"
  192. });
  193. }
  194. if (dep.hasCallback) {
  195. if (runtimeTemplate.supportsArrowFunction()) {
  196. source.insert(
  197. dep.range[0],
  198. `${optAsync}__WEBPACK_OUTDATED_DEPENDENCIES__ => { ${content} return (`
  199. );
  200. source.insert(dep.range[1], ")(__WEBPACK_OUTDATED_DEPENDENCIES__); }");
  201. } else {
  202. source.insert(
  203. dep.range[0],
  204. `${optAsync}function(__WEBPACK_OUTDATED_DEPENDENCIES__) { ${content} return (`
  205. );
  206. source.insert(
  207. dep.range[1],
  208. ")(__WEBPACK_OUTDATED_DEPENDENCIES__); }.bind(this)"
  209. );
  210. }
  211. return;
  212. }
  213. const arrow = runtimeTemplate.supportsArrowFunction();
  214. source.insert(
  215. dep.range[1] - 0.5,
  216. `, ${arrow ? `${optAsync}() =>` : `${optAsync}function()`} { ${content} }`
  217. );
  218. }
  219. };
  220. module.exports = HarmonyAcceptDependency;