HarmonyImportDependency.js 13 KB

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