HarmonyExportImportedSpecifierDependency.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const DependencyReference = require("./DependencyReference");
  7. const HarmonyImportDependency = require("./HarmonyImportDependency");
  8. const Template = require("../Template");
  9. const HarmonyLinkingError = require("../HarmonyLinkingError");
  10. /** @typedef {import("../Module")} Module */
  11. /** @typedef {"missing"|"unused"|"empty-star"|"reexport-non-harmony-default"|"reexport-named-default"|"reexport-namespace-object"|"reexport-non-harmony-default-strict"|"reexport-fake-namespace-object"|"rexport-non-harmony-undefined"|"safe-reexport"|"checked-reexport"|"dynamic-reexport"} ExportModeType */
  12. /** @type {Map<string, string>} */
  13. const EMPTY_MAP = new Map();
  14. class ExportMode {
  15. /**
  16. * @param {ExportModeType} type type of the mode
  17. */
  18. constructor(type) {
  19. /** @type {ExportModeType} */
  20. this.type = type;
  21. /** @type {string|null} */
  22. this.name = null;
  23. /** @type {Map<string, string>} */
  24. this.map = EMPTY_MAP;
  25. /** @type {Set<string>|null} */
  26. this.ignored = null;
  27. /** @type {Module|null} */
  28. this.module = null;
  29. /** @type {string|null} */
  30. this.userRequest = null;
  31. }
  32. }
  33. const EMPTY_STAR_MODE = new ExportMode("empty-star");
  34. class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
  35. constructor(
  36. request,
  37. originModule,
  38. sourceOrder,
  39. parserScope,
  40. id,
  41. name,
  42. activeExports,
  43. otherStarExports,
  44. strictExportPresence
  45. ) {
  46. super(request, originModule, sourceOrder, parserScope);
  47. this.id = id;
  48. this.redirectedId = undefined;
  49. this.name = name;
  50. this.activeExports = activeExports;
  51. this.otherStarExports = otherStarExports;
  52. this.strictExportPresence = strictExportPresence;
  53. }
  54. get type() {
  55. return "harmony export imported specifier";
  56. }
  57. get _id() {
  58. return this.redirectedId || this.id;
  59. }
  60. getMode(ignoreUnused) {
  61. const name = this.name;
  62. const id = this._id;
  63. const used = this.originModule.isUsed(name);
  64. const importedModule = this._module;
  65. if (!importedModule) {
  66. const mode = new ExportMode("missing");
  67. mode.userRequest = this.userRequest;
  68. return mode;
  69. }
  70. if (
  71. !ignoreUnused &&
  72. (name ? !used : this.originModule.usedExports === false)
  73. ) {
  74. const mode = new ExportMode("unused");
  75. mode.name = name || "*";
  76. return mode;
  77. }
  78. const strictHarmonyModule = this.originModule.buildMeta.strictHarmonyModule;
  79. if (name && id === "default" && importedModule.buildMeta) {
  80. if (!importedModule.buildMeta.exportsType) {
  81. const mode = new ExportMode(
  82. strictHarmonyModule
  83. ? "reexport-non-harmony-default-strict"
  84. : "reexport-non-harmony-default"
  85. );
  86. mode.name = name;
  87. mode.module = importedModule;
  88. return mode;
  89. } else if (importedModule.buildMeta.exportsType === "named") {
  90. const mode = new ExportMode("reexport-named-default");
  91. mode.name = name;
  92. mode.module = importedModule;
  93. return mode;
  94. }
  95. }
  96. const isNotAHarmonyModule =
  97. importedModule.buildMeta && !importedModule.buildMeta.exportsType;
  98. if (name) {
  99. let mode;
  100. if (id) {
  101. // export { name as name }
  102. if (isNotAHarmonyModule && strictHarmonyModule) {
  103. mode = new ExportMode("rexport-non-harmony-undefined");
  104. mode.name = name;
  105. } else {
  106. mode = new ExportMode("safe-reexport");
  107. mode.map = new Map([[name, id]]);
  108. }
  109. } else {
  110. // export { * as name }
  111. if (isNotAHarmonyModule && strictHarmonyModule) {
  112. mode = new ExportMode("reexport-fake-namespace-object");
  113. mode.name = name;
  114. } else {
  115. mode = new ExportMode("reexport-namespace-object");
  116. mode.name = name;
  117. }
  118. }
  119. mode.module = importedModule;
  120. return mode;
  121. }
  122. const hasUsedExports = Array.isArray(this.originModule.usedExports);
  123. const hasProvidedExports = Array.isArray(
  124. importedModule.buildMeta.providedExports
  125. );
  126. const activeFromOtherStarExports = this._discoverActiveExportsFromOtherStartExports();
  127. // export *
  128. if (hasUsedExports) {
  129. // reexport * with known used exports
  130. if (hasProvidedExports) {
  131. const map = new Map(
  132. this.originModule.usedExports
  133. .filter(id => {
  134. if (id === "default") return false;
  135. if (this.activeExports.has(id)) return false;
  136. if (activeFromOtherStarExports.has(id)) return false;
  137. if (!importedModule.buildMeta.providedExports.includes(id))
  138. return false;
  139. return true;
  140. })
  141. .map(item => [item, item])
  142. );
  143. if (map.size === 0) {
  144. return EMPTY_STAR_MODE;
  145. }
  146. const mode = new ExportMode("safe-reexport");
  147. mode.module = importedModule;
  148. mode.map = map;
  149. return mode;
  150. }
  151. const map = new Map(
  152. this.originModule.usedExports
  153. .filter(id => {
  154. if (id === "default") return false;
  155. if (this.activeExports.has(id)) return false;
  156. if (activeFromOtherStarExports.has(id)) return false;
  157. return true;
  158. })
  159. .map(item => [item, item])
  160. );
  161. if (map.size === 0) {
  162. return EMPTY_STAR_MODE;
  163. }
  164. const mode = new ExportMode("checked-reexport");
  165. mode.module = importedModule;
  166. mode.map = map;
  167. return mode;
  168. }
  169. if (hasProvidedExports) {
  170. const map = new Map(
  171. importedModule.buildMeta.providedExports
  172. .filter(id => {
  173. if (id === "default") return false;
  174. if (this.activeExports.has(id)) return false;
  175. if (activeFromOtherStarExports.has(id)) return false;
  176. return true;
  177. })
  178. .map(item => [item, item])
  179. );
  180. if (map.size === 0) {
  181. return EMPTY_STAR_MODE;
  182. }
  183. const mode = new ExportMode("safe-reexport");
  184. mode.module = importedModule;
  185. mode.map = map;
  186. return mode;
  187. }
  188. const mode = new ExportMode("dynamic-reexport");
  189. mode.module = importedModule;
  190. mode.ignored = new Set([
  191. "default",
  192. ...this.activeExports,
  193. ...activeFromOtherStarExports
  194. ]);
  195. return mode;
  196. }
  197. getReference() {
  198. const mode = this.getMode(false);
  199. switch (mode.type) {
  200. case "missing":
  201. case "unused":
  202. case "empty-star":
  203. return null;
  204. case "reexport-non-harmony-default":
  205. case "reexport-named-default":
  206. return new DependencyReference(
  207. mode.module,
  208. ["default"],
  209. false,
  210. this.sourceOrder
  211. );
  212. case "reexport-namespace-object":
  213. case "reexport-non-harmony-default-strict":
  214. case "reexport-fake-namespace-object":
  215. case "rexport-non-harmony-undefined":
  216. return new DependencyReference(
  217. mode.module,
  218. true,
  219. false,
  220. this.sourceOrder
  221. );
  222. case "safe-reexport":
  223. case "checked-reexport":
  224. return new DependencyReference(
  225. mode.module,
  226. Array.from(mode.map.values()),
  227. false,
  228. this.sourceOrder
  229. );
  230. case "dynamic-reexport":
  231. return new DependencyReference(
  232. mode.module,
  233. true,
  234. false,
  235. this.sourceOrder
  236. );
  237. default:
  238. throw new Error(`Unknown mode ${mode.type}`);
  239. }
  240. }
  241. _discoverActiveExportsFromOtherStartExports() {
  242. if (!this.otherStarExports) return new Set();
  243. const result = new Set();
  244. // try to learn impossible exports from other star exports with provided exports
  245. for (const otherStarExport of this.otherStarExports) {
  246. const otherImportedModule = otherStarExport._module;
  247. if (
  248. otherImportedModule &&
  249. Array.isArray(otherImportedModule.buildMeta.providedExports)
  250. ) {
  251. for (const exportName of otherImportedModule.buildMeta
  252. .providedExports) {
  253. result.add(exportName);
  254. }
  255. }
  256. }
  257. return result;
  258. }
  259. getExports() {
  260. if (this.name) {
  261. return {
  262. exports: [this.name],
  263. dependencies: undefined
  264. };
  265. }
  266. const importedModule = this._module;
  267. if (!importedModule) {
  268. // no imported module available
  269. return {
  270. exports: null,
  271. dependencies: undefined
  272. };
  273. }
  274. if (Array.isArray(importedModule.buildMeta.providedExports)) {
  275. const activeFromOtherStarExports = this._discoverActiveExportsFromOtherStartExports();
  276. return {
  277. exports: importedModule.buildMeta.providedExports.filter(
  278. id =>
  279. id !== "default" &&
  280. !activeFromOtherStarExports.has(id) &&
  281. !this.activeExports.has(id)
  282. ),
  283. dependencies: [importedModule]
  284. };
  285. }
  286. if (importedModule.buildMeta.providedExports) {
  287. return {
  288. exports: true,
  289. dependencies: undefined
  290. };
  291. }
  292. return {
  293. exports: null,
  294. dependencies: [importedModule]
  295. };
  296. }
  297. getWarnings() {
  298. if (
  299. this.strictExportPresence ||
  300. this.originModule.buildMeta.strictHarmonyModule
  301. ) {
  302. return [];
  303. }
  304. return this._getErrors();
  305. }
  306. getErrors() {
  307. if (
  308. this.strictExportPresence ||
  309. this.originModule.buildMeta.strictHarmonyModule
  310. ) {
  311. return this._getErrors();
  312. }
  313. return [];
  314. }
  315. _getErrors() {
  316. const importedModule = this._module;
  317. if (!importedModule) {
  318. return;
  319. }
  320. if (!importedModule.buildMeta || !importedModule.buildMeta.exportsType) {
  321. // It's not an harmony module
  322. if (
  323. this.originModule.buildMeta.strictHarmonyModule &&
  324. this._id &&
  325. this._id !== "default"
  326. ) {
  327. // In strict harmony modules we only support the default export
  328. return [
  329. new HarmonyLinkingError(
  330. `Can't reexport the named export '${this._id}' from non EcmaScript module (only default export is available)`
  331. )
  332. ];
  333. }
  334. return;
  335. }
  336. if (!this._id) {
  337. return;
  338. }
  339. if (importedModule.isProvided(this._id) !== false) {
  340. // It's provided or we are not sure
  341. return;
  342. }
  343. // We are sure that it's not provided
  344. const idIsNotNameMessage =
  345. this._id !== this.name ? ` (reexported as '${this.name}')` : "";
  346. const errorMessage = `"export '${this._id}'${idIsNotNameMessage} was not found in '${this.userRequest}'`;
  347. return [new HarmonyLinkingError(errorMessage)];
  348. }
  349. updateHash(hash) {
  350. super.updateHash(hash);
  351. const hashValue = this.getHashValue(this._module);
  352. hash.update(hashValue);
  353. }
  354. getHashValue(importedModule) {
  355. if (!importedModule) {
  356. return "";
  357. }
  358. const stringifiedUsedExport = JSON.stringify(importedModule.usedExports);
  359. const stringifiedProvidedExport = JSON.stringify(
  360. importedModule.buildMeta.providedExports
  361. );
  362. return (
  363. importedModule.used + stringifiedUsedExport + stringifiedProvidedExport
  364. );
  365. }
  366. disconnect() {
  367. super.disconnect();
  368. this.redirectedId = undefined;
  369. }
  370. }
  371. module.exports = HarmonyExportImportedSpecifierDependency;
  372. HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedSpecifierDependencyTemplate extends HarmonyImportDependency.Template {
  373. harmonyInit(dep, source, runtime, dependencyTemplates) {
  374. super.harmonyInit(dep, source, runtime, dependencyTemplates);
  375. const content = this.getContent(dep);
  376. source.insert(-1, content);
  377. }
  378. getHarmonyInitOrder(dep) {
  379. if (dep.name) {
  380. const used = dep.originModule.isUsed(dep.name);
  381. if (!used) return NaN;
  382. } else {
  383. const importedModule = dep._module;
  384. const activeFromOtherStarExports = dep._discoverActiveExportsFromOtherStartExports();
  385. if (Array.isArray(dep.originModule.usedExports)) {
  386. // we know which exports are used
  387. const unused = dep.originModule.usedExports.every(id => {
  388. if (id === "default") return true;
  389. if (dep.activeExports.has(id)) return true;
  390. if (importedModule.isProvided(id) === false) return true;
  391. if (activeFromOtherStarExports.has(id)) return true;
  392. return false;
  393. });
  394. if (unused) return NaN;
  395. } else if (
  396. dep.originModule.usedExports &&
  397. importedModule &&
  398. Array.isArray(importedModule.buildMeta.providedExports)
  399. ) {
  400. // not sure which exports are used, but we know which are provided
  401. const unused = importedModule.buildMeta.providedExports.every(id => {
  402. if (id === "default") return true;
  403. if (dep.activeExports.has(id)) return true;
  404. if (activeFromOtherStarExports.has(id)) return true;
  405. return false;
  406. });
  407. if (unused) return NaN;
  408. }
  409. }
  410. return super.getHarmonyInitOrder(dep);
  411. }
  412. getContent(dep) {
  413. const mode = dep.getMode(false);
  414. const module = dep.originModule;
  415. const importedModule = dep._module;
  416. const importVar = dep.getImportVar();
  417. switch (mode.type) {
  418. case "missing":
  419. return `throw new Error(${JSON.stringify(
  420. `Cannot find module '${mode.userRequest}'`
  421. )});\n`;
  422. case "unused":
  423. return `${Template.toNormalComment(
  424. `unused harmony reexport ${mode.name}`
  425. )}\n`;
  426. case "reexport-non-harmony-default":
  427. return (
  428. "/* harmony reexport (default from non-harmony) */ " +
  429. this.getReexportStatement(
  430. module,
  431. module.isUsed(mode.name),
  432. importVar,
  433. null
  434. )
  435. );
  436. case "reexport-named-default":
  437. return (
  438. "/* harmony reexport (default from named exports) */ " +
  439. this.getReexportStatement(
  440. module,
  441. module.isUsed(mode.name),
  442. importVar,
  443. ""
  444. )
  445. );
  446. case "reexport-fake-namespace-object":
  447. return (
  448. "/* harmony reexport (fake namespace object from non-harmony) */ " +
  449. this.getReexportFakeNamespaceObjectStatement(
  450. module,
  451. module.isUsed(mode.name),
  452. importVar
  453. )
  454. );
  455. case "rexport-non-harmony-undefined":
  456. return (
  457. "/* harmony reexport (non default export from non-harmony) */ " +
  458. this.getReexportStatement(
  459. module,
  460. module.isUsed(mode.name),
  461. "undefined",
  462. ""
  463. )
  464. );
  465. case "reexport-non-harmony-default-strict":
  466. return (
  467. "/* harmony reexport (default from non-harmony) */ " +
  468. this.getReexportStatement(
  469. module,
  470. module.isUsed(mode.name),
  471. importVar,
  472. ""
  473. )
  474. );
  475. case "reexport-namespace-object":
  476. return (
  477. "/* harmony reexport (module object) */ " +
  478. this.getReexportStatement(
  479. module,
  480. module.isUsed(mode.name),
  481. importVar,
  482. ""
  483. )
  484. );
  485. case "empty-star":
  486. return "/* empty/unused harmony star reexport */";
  487. case "safe-reexport":
  488. return Array.from(mode.map.entries())
  489. .map(item => {
  490. return (
  491. "/* harmony reexport (safe) */ " +
  492. this.getReexportStatement(
  493. module,
  494. module.isUsed(item[0]),
  495. importVar,
  496. importedModule.isUsed(item[1])
  497. ) +
  498. "\n"
  499. );
  500. })
  501. .join("");
  502. case "checked-reexport":
  503. return Array.from(mode.map.entries())
  504. .map(item => {
  505. return (
  506. "/* harmony reexport (checked) */ " +
  507. this.getConditionalReexportStatement(
  508. module,
  509. item[0],
  510. importVar,
  511. item[1]
  512. ) +
  513. "\n"
  514. );
  515. })
  516. .join("");
  517. case "dynamic-reexport": {
  518. const ignoredExports = mode.ignored;
  519. let content =
  520. "/* harmony reexport (unknown) */ for(var __WEBPACK_IMPORT_KEY__ in " +
  521. importVar +
  522. ") ";
  523. // Filter out exports which are defined by other exports
  524. // and filter out default export because it cannot be reexported with *
  525. if (ignoredExports.size > 0) {
  526. content +=
  527. "if(" +
  528. JSON.stringify(Array.from(ignoredExports)) +
  529. ".indexOf(__WEBPACK_IMPORT_KEY__) < 0) ";
  530. } else {
  531. content += "if(__WEBPACK_IMPORT_KEY__ !== 'default') ";
  532. }
  533. const exportsName = dep.originModule.exportsArgument;
  534. return (
  535. content +
  536. `(function(key) { __webpack_require__.d(${exportsName}, key, function() { return ${importVar}[key]; }) }(__WEBPACK_IMPORT_KEY__));\n`
  537. );
  538. }
  539. default:
  540. throw new Error(`Unknown mode ${mode.type}`);
  541. }
  542. }
  543. getReexportStatement(module, key, name, valueKey) {
  544. const exportsName = module.exportsArgument;
  545. const returnValue = this.getReturnValue(name, valueKey);
  546. return `__webpack_require__.d(${exportsName}, ${JSON.stringify(
  547. key
  548. )}, function() { return ${returnValue}; });\n`;
  549. }
  550. getReexportFakeNamespaceObjectStatement(module, key, name) {
  551. const exportsName = module.exportsArgument;
  552. return `__webpack_require__.d(${exportsName}, ${JSON.stringify(
  553. key
  554. )}, function() { return __webpack_require__.t(${name}); });\n`;
  555. }
  556. getConditionalReexportStatement(module, key, name, valueKey) {
  557. if (valueKey === false) {
  558. return "/* unused export */\n";
  559. }
  560. const exportsName = module.exportsArgument;
  561. const returnValue = this.getReturnValue(name, valueKey);
  562. return `if(__webpack_require__.o(${name}, ${JSON.stringify(
  563. valueKey
  564. )})) __webpack_require__.d(${exportsName}, ${JSON.stringify(
  565. key
  566. )}, function() { return ${returnValue}; });\n`;
  567. }
  568. getReturnValue(name, valueKey) {
  569. if (valueKey === null) {
  570. return `${name}_default.a`;
  571. }
  572. if (valueKey === "") {
  573. return name;
  574. }
  575. if (valueKey === false) {
  576. return "/* unused export */ undefined";
  577. }
  578. return `${name}[${JSON.stringify(valueKey)}]`;
  579. }
  580. };