HarmonyImportDependency.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const ConditionalInitFragment = require("../ConditionalInitFragment");
  7. const Dependency = require("../Dependency");
  8. const HarmonyLinkingError = require("../HarmonyLinkingError");
  9. const InitFragment = require("../InitFragment");
  10. const Template = require("../Template");
  11. const AwaitDependenciesInitFragment = require("../async-modules/AwaitDependenciesInitFragment");
  12. const { filterRuntime, mergeRuntime } = require("../util/runtime");
  13. const ModuleDependency = require("./ModuleDependency");
  14. /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
  15. /** @typedef {import("webpack-sources").Source} Source */
  16. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  17. /** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
  18. /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
  19. /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
  20. /** @typedef {import("../ExportsInfo")} ExportsInfo */
  21. /** @typedef {import("../Module")} Module */
  22. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  23. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  24. /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
  25. /** @typedef {import("../WebpackError")} WebpackError */
  26. /** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */
  27. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
  28. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
  29. /** @typedef {import("../util/Hash")} Hash */
  30. /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
  31. const ExportPresenceModes = {
  32. NONE: /** @type {0} */ (0),
  33. WARN: /** @type {1} */ (1),
  34. AUTO: /** @type {2} */ (2),
  35. ERROR: /** @type {3} */ (3),
  36. /**
  37. * @param {string | false} str param
  38. * @returns {0 | 1 | 2 | 3} result
  39. */
  40. fromUserOption(str) {
  41. switch (str) {
  42. case "error":
  43. return ExportPresenceModes.ERROR;
  44. case "warn":
  45. return ExportPresenceModes.WARN;
  46. case "auto":
  47. return ExportPresenceModes.AUTO;
  48. case false:
  49. return ExportPresenceModes.NONE;
  50. default:
  51. throw new Error(`Invalid export presence value ${str}`);
  52. }
  53. }
  54. };
  55. class HarmonyImportDependency extends ModuleDependency {
  56. /**
  57. *
  58. * @param {string} request request string
  59. * @param {number} sourceOrder source order
  60. * @param {ImportAttributes=} attributes import attributes
  61. */
  62. constructor(request, sourceOrder, attributes) {
  63. super(request);
  64. this.sourceOrder = sourceOrder;
  65. this.assertions = attributes;
  66. }
  67. get category() {
  68. return "esm";
  69. }
  70. /**
  71. * Returns list of exports referenced by this dependency
  72. * @param {ModuleGraph} moduleGraph module graph
  73. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  74. * @returns {(string[] | ReferencedExport)[]} referenced exports
  75. */
  76. getReferencedExports(moduleGraph, runtime) {
  77. return Dependency.NO_EXPORTS_REFERENCED;
  78. }
  79. /**
  80. * @param {ModuleGraph} moduleGraph the module graph
  81. * @returns {string} name of the variable for the import
  82. */
  83. getImportVar(moduleGraph) {
  84. const module = moduleGraph.getParentModule(this);
  85. const meta = /** @type {TODO} */ (moduleGraph.getMeta(module));
  86. let importVarMap = meta.importVarMap;
  87. if (!importVarMap) meta.importVarMap = importVarMap = new Map();
  88. let importVar = importVarMap.get(
  89. /** @type {Module} */ (moduleGraph.getModule(this))
  90. );
  91. if (importVar) return importVar;
  92. importVar = `${Template.toIdentifier(
  93. `${this.userRequest}`
  94. )}__WEBPACK_IMPORTED_MODULE_${importVarMap.size}__`;
  95. importVarMap.set(
  96. /** @type {Module} */ (moduleGraph.getModule(this)),
  97. importVar
  98. );
  99. return importVar;
  100. }
  101. /**
  102. * @param {boolean} update create new variables or update existing one
  103. * @param {DependencyTemplateContext} templateContext the template context
  104. * @returns {[string, string]} the import statement and the compat statement
  105. */
  106. getImportStatement(
  107. update,
  108. { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements }
  109. ) {
  110. return runtimeTemplate.importStatement({
  111. update,
  112. module: /** @type {Module} */ (moduleGraph.getModule(this)),
  113. chunkGraph,
  114. importVar: this.getImportVar(moduleGraph),
  115. request: this.request,
  116. originModule: module,
  117. runtimeRequirements
  118. });
  119. }
  120. /**
  121. * @param {ModuleGraph} moduleGraph module graph
  122. * @param {string[]} ids imported ids
  123. * @param {string} additionalMessage extra info included in the error message
  124. * @returns {WebpackError[] | undefined} errors
  125. */
  126. getLinkingErrors(moduleGraph, ids, additionalMessage) {
  127. const importedModule = moduleGraph.getModule(this);
  128. // ignore errors for missing or failed modules
  129. if (!importedModule || importedModule.getNumberOfErrors() > 0) {
  130. return;
  131. }
  132. const parentModule =
  133. /** @type {Module} */
  134. (moduleGraph.getParentModule(this));
  135. const exportsType = importedModule.getExportsType(
  136. moduleGraph,
  137. /** @type {BuildMeta} */ (parentModule.buildMeta).strictHarmonyModule
  138. );
  139. if (exportsType === "namespace" || exportsType === "default-with-named") {
  140. if (ids.length === 0) {
  141. return;
  142. }
  143. if (
  144. (exportsType !== "default-with-named" || ids[0] !== "default") &&
  145. moduleGraph.isExportProvided(importedModule, ids) === false
  146. ) {
  147. // We are sure that it's not provided
  148. // Try to provide detailed info in the error message
  149. let pos = 0;
  150. let exportsInfo = moduleGraph.getExportsInfo(importedModule);
  151. while (pos < ids.length && exportsInfo) {
  152. const id = ids[pos++];
  153. const exportInfo = exportsInfo.getReadOnlyExportInfo(id);
  154. if (exportInfo.provided === false) {
  155. // We are sure that it's not provided
  156. const providedExports = exportsInfo.getProvidedExports();
  157. const moreInfo = !Array.isArray(providedExports)
  158. ? " (possible exports unknown)"
  159. : providedExports.length === 0
  160. ? " (module has no exports)"
  161. : ` (possible exports: ${providedExports.join(", ")})`;
  162. return [
  163. new HarmonyLinkingError(
  164. `export ${ids
  165. .slice(0, pos)
  166. .map(id => `'${id}'`)
  167. .join(".")} ${additionalMessage} was not found in '${
  168. this.userRequest
  169. }'${moreInfo}`
  170. )
  171. ];
  172. }
  173. exportsInfo =
  174. /** @type {ExportsInfo} */
  175. (exportInfo.getNestedExportsInfo());
  176. }
  177. // General error message
  178. return [
  179. new HarmonyLinkingError(
  180. `export ${ids
  181. .map(id => `'${id}'`)
  182. .join(".")} ${additionalMessage} was not found in '${
  183. this.userRequest
  184. }'`
  185. )
  186. ];
  187. }
  188. }
  189. switch (exportsType) {
  190. case "default-only":
  191. // It's has only a default export
  192. if (ids.length > 0 && ids[0] !== "default") {
  193. // In strict harmony modules we only support the default export
  194. return [
  195. new HarmonyLinkingError(
  196. `Can't import the named export ${ids
  197. .map(id => `'${id}'`)
  198. .join(
  199. "."
  200. )} ${additionalMessage} from default-exporting module (only default export is available)`
  201. )
  202. ];
  203. }
  204. break;
  205. case "default-with-named":
  206. // It has a default export and named properties redirect
  207. // In some cases we still want to warn here
  208. if (
  209. ids.length > 0 &&
  210. ids[0] !== "default" &&
  211. /** @type {BuildMeta} */
  212. (importedModule.buildMeta).defaultObject === "redirect-warn"
  213. ) {
  214. // For these modules only the default export is supported
  215. return [
  216. new HarmonyLinkingError(
  217. `Should not import the named export ${ids
  218. .map(id => `'${id}'`)
  219. .join(
  220. "."
  221. )} ${additionalMessage} from default-exporting module (only default export is available soon)`
  222. )
  223. ];
  224. }
  225. break;
  226. }
  227. }
  228. /**
  229. * @param {ObjectSerializerContext} context context
  230. */
  231. serialize(context) {
  232. const { write } = context;
  233. write(this.sourceOrder);
  234. write(this.assertions);
  235. super.serialize(context);
  236. }
  237. /**
  238. * @param {ObjectDeserializerContext} context context
  239. */
  240. deserialize(context) {
  241. const { read } = context;
  242. this.sourceOrder = read();
  243. this.assertions = read();
  244. super.deserialize(context);
  245. }
  246. }
  247. module.exports = HarmonyImportDependency;
  248. /** @type {WeakMap<Module, WeakMap<Module, RuntimeSpec | boolean>>} */
  249. const importEmittedMap = new WeakMap();
  250. HarmonyImportDependency.Template = class HarmonyImportDependencyTemplate extends (
  251. ModuleDependency.Template
  252. ) {
  253. /**
  254. * @param {Dependency} dependency the dependency for which the template should be applied
  255. * @param {ReplaceSource} source the current replace source which can be modified
  256. * @param {DependencyTemplateContext} templateContext the context object
  257. * @returns {void}
  258. */
  259. apply(dependency, source, templateContext) {
  260. const dep = /** @type {HarmonyImportDependency} */ (dependency);
  261. const { module, chunkGraph, moduleGraph, runtime } = templateContext;
  262. const connection = moduleGraph.getConnection(dep);
  263. if (connection && !connection.isTargetActive(runtime)) return;
  264. const referencedModule = connection && connection.module;
  265. if (
  266. connection &&
  267. connection.weak &&
  268. referencedModule &&
  269. chunkGraph.getModuleId(referencedModule) === null
  270. ) {
  271. // in weak references, module might not be in any chunk
  272. // but that's ok, we don't need that logic in this case
  273. return;
  274. }
  275. const moduleKey = referencedModule
  276. ? referencedModule.identifier()
  277. : dep.request;
  278. const key = `harmony import ${moduleKey}`;
  279. const runtimeCondition = dep.weak
  280. ? false
  281. : connection
  282. ? filterRuntime(runtime, r => connection.isTargetActive(r))
  283. : true;
  284. if (module && referencedModule) {
  285. let emittedModules = importEmittedMap.get(module);
  286. if (emittedModules === undefined) {
  287. emittedModules = new WeakMap();
  288. importEmittedMap.set(module, emittedModules);
  289. }
  290. let mergedRuntimeCondition = runtimeCondition;
  291. const oldRuntimeCondition = emittedModules.get(referencedModule) || false;
  292. if (oldRuntimeCondition !== false && mergedRuntimeCondition !== true) {
  293. if (mergedRuntimeCondition === false || oldRuntimeCondition === true) {
  294. mergedRuntimeCondition = oldRuntimeCondition;
  295. } else {
  296. mergedRuntimeCondition = mergeRuntime(
  297. oldRuntimeCondition,
  298. mergedRuntimeCondition
  299. );
  300. }
  301. }
  302. emittedModules.set(referencedModule, mergedRuntimeCondition);
  303. }
  304. const importStatement = dep.getImportStatement(false, templateContext);
  305. if (
  306. referencedModule &&
  307. templateContext.moduleGraph.isAsync(referencedModule)
  308. ) {
  309. templateContext.initFragments.push(
  310. new ConditionalInitFragment(
  311. importStatement[0],
  312. InitFragment.STAGE_HARMONY_IMPORTS,
  313. dep.sourceOrder,
  314. key,
  315. runtimeCondition
  316. )
  317. );
  318. templateContext.initFragments.push(
  319. new AwaitDependenciesInitFragment(
  320. new Set([dep.getImportVar(templateContext.moduleGraph)])
  321. )
  322. );
  323. templateContext.initFragments.push(
  324. new ConditionalInitFragment(
  325. importStatement[1],
  326. InitFragment.STAGE_ASYNC_HARMONY_IMPORTS,
  327. dep.sourceOrder,
  328. key + " compat",
  329. runtimeCondition
  330. )
  331. );
  332. } else {
  333. templateContext.initFragments.push(
  334. new ConditionalInitFragment(
  335. importStatement[0] + importStatement[1],
  336. InitFragment.STAGE_HARMONY_IMPORTS,
  337. dep.sourceOrder,
  338. key,
  339. runtimeCondition
  340. )
  341. );
  342. }
  343. }
  344. /**
  345. *
  346. * @param {Module} module the module
  347. * @param {Module} referencedModule the referenced module
  348. * @returns {RuntimeSpec | boolean} runtimeCondition in which this import has been emitted
  349. */
  350. static getImportEmittedRuntime(module, referencedModule) {
  351. const emittedModules = importEmittedMap.get(module);
  352. if (emittedModules === undefined) return false;
  353. return emittedModules.get(referencedModule) || false;
  354. }
  355. };
  356. module.exports.ExportPresenceModes = ExportPresenceModes;