You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

651 lines
16 KiB

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