Skip to content

Commit ae1190e

Browse files
pedrotainhapmtcofidisMenci
authored
fix: parse ExportNamedDeclaration with source (#46)
* fix: parse ExportNamedDeclaration with source * Fix: exportedNames were not being created after latest commit * Fix transform logic and add test --------- Co-authored-by: TAINHA Pedro (PRESTA EXT) <[email protected]> Co-authored-by: Menci <[email protected]>
1 parent cda7687 commit ae1190e

File tree

5 files changed

+121
-15
lines changed

5 files changed

+121
-15
lines changed

src/bundle-info.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,37 @@ describe("Bundle info parser", () => {
4141
}
4242
});
4343

44+
it("should parse dependencies correctly", async () => {
45+
const chunks = {
46+
a: `
47+
export { x } from "./b";
48+
import { s } from "./c";
49+
const w = await globalThis.y?.z(s);
50+
export { w };
51+
`,
52+
b: `
53+
const x = await globalThis.x?.y;
54+
export { x };
55+
`,
56+
c: `
57+
const s = await globalThis.p?.q;
58+
export { s };
59+
`
60+
};
61+
62+
const bundleInfo = await parseBundleInfo(await parseBundleAsts(chunks));
63+
64+
expect(bundleInfo["a"]).toBeTruthy();
65+
expect(bundleInfo["a"].imported).toIncludeSameMembers(["b", "c"]);
66+
expect(bundleInfo["a"].importedBy).toIncludeSameMembers([]);
67+
expect(bundleInfo["b"]).toBeTruthy();
68+
expect(bundleInfo["b"].imported).toIncludeSameMembers([]);
69+
expect(bundleInfo["b"].importedBy).toIncludeSameMembers(["a"]);
70+
expect(bundleInfo["c"]).toBeTruthy();
71+
expect(bundleInfo["c"].imported).toIncludeSameMembers([]);
72+
expect(bundleInfo["c"].importedBy).toIncludeSameMembers(["a"]);
73+
});
74+
4475
it("should parse dependency graph correctly", async () => {
4576
const dependencyGraph = {
4677
a: ["b", "c", "d"],

src/bundle-info.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export async function parseBundleInfo(bundleAsts: Record<string, SWC.Module>): P
5050
// Parse imports
5151
moduleInfo.imported = ast.body
5252
.map(item => {
53-
if (item.type === "ImportDeclaration") {
53+
if (item.type === "ImportDeclaration" || (item.type === "ExportNamedDeclaration" && item.source)) {
5454
return resolveImport(moduleName, item.source.value);
5555
}
5656
})

src/transform.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,39 @@ describe("Transform top-level await", () => {
392392
);
393393
});
394394

395+
it("should work for a module with export-from statements", () => {
396+
test(
397+
"a",
398+
{
399+
a: { imported: ["./b"], importedBy: [], transformNeeded: true, withTopLevelAwait: true },
400+
b: { imported: [], importedBy: ["./a"], transformNeeded: true, withTopLevelAwait: true }
401+
},
402+
`
403+
import { qwq } from "./b";
404+
export { owo as uwu, default as ovo } from "./b";
405+
export * as QwQ from "./b";
406+
const qaq = await globalThis.someFunc(qwq);
407+
export { qaq };
408+
`,
409+
`
410+
import { qwq, __tla as __tla_0 } from "./b";
411+
import { __tla as __tla_1 } from "./b";
412+
import { __tla as __tla_2 } from "./b";
413+
export { owo as uwu, default as ovo } from "./b";
414+
export * as QwQ from "./b";
415+
let qaq;
416+
let __tla = Promise.all([
417+
(() => { try { return __tla_0; } catch {} })(),
418+
(() => { try { return __tla_1; } catch {} })(),
419+
(() => { try { return __tla_2; } catch {} })(),
420+
]).then(async () => {
421+
qaq = await globalThis.someFunc(qwq);
422+
});
423+
export { qaq, __tla };
424+
`
425+
);
426+
});
427+
395428
it("should skip processing imports of external modules", () => {
396429
test(
397430
"a",

src/transform.ts

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
makeVariableInitDeclaration,
1818
makeExportListDeclaration,
1919
makeStatement,
20-
makeAwaitExpression
20+
makeAwaitExpression,
21+
makeImportDeclaration
2122
} from "./utils/make-node";
2223
import { RandomIdentifierGenerator } from "./utils/random-identifier";
2324
import { resolveImport } from "./utils/resolve-import";
@@ -86,6 +87,12 @@ export function transformModule(
8687
// Extract import declarations
8788
const imports = ast.body.filter((item): item is SWC.ImportDeclaration => item.type === "ImportDeclaration");
8889

90+
// Handle `export { named as renamed } from "module";`
91+
const exportFroms = ast.body.filter(
92+
(item): item is SWC.ExportNamedDeclaration => item.type === "ExportNamedDeclaration" && !!item.source
93+
);
94+
const newImportsByExportFroms: SWC.ImportDeclaration[] = [];
95+
8996
const exportMap: Record<string, string> = {};
9097

9198
// Extract export declarations
@@ -130,15 +137,18 @@ export function transformModule(
130137
}
131138

132139
return false;
140+
// Handle `export { named as renamed };` without "from"
133141
case "ExportNamedDeclaration":
134-
item.specifiers.forEach(specifier => {
135-
/* istanbul ignore if */
136-
if (specifier.type !== "ExportSpecifier") {
137-
raiseUnexpectedNode("export specifier", specifier.type);
138-
}
139-
140-
exportMap[(specifier.exported || specifier.orig).value] = specifier.orig.value;
141-
});
142+
if (!item.source) {
143+
item.specifiers.forEach(specifier => {
144+
/* istanbul ignore if */
145+
if (specifier.type !== "ExportSpecifier") {
146+
raiseUnexpectedNode("export specifier", specifier.type);
147+
}
148+
149+
exportMap[(specifier.exported || specifier.orig).value] = specifier.orig.value;
150+
});
151+
}
142152

143153
return true;
144154
}
@@ -181,7 +191,20 @@ export function transformModule(
181191
const importedNames = new Set(
182192
imports.flatMap(importStmt => importStmt.specifiers.map(specifier => specifier.local.value))
183193
);
184-
const exportedNamesDeclaration = makeVariablesDeclaration(exportedNames.filter(name => !importedNames.has(name)));
194+
const exportFromedNames = new Set(
195+
exportFroms.flatMap(exportStmt => exportStmt.specifiers.map(specifier => {
196+
if (specifier.type === "ExportNamespaceSpecifier") {
197+
return specifier.name.value;
198+
} else if (specifier.type === "ExportDefaultSpecifier") {
199+
// When will this happen?
200+
return specifier.exported.value;
201+
} else {
202+
return (specifier.exported || specifier.orig).value;
203+
}
204+
}))
205+
);
206+
const exportedNamesDeclaration = makeVariablesDeclaration(exportedNames.filter(name => !importedNames.has(name) && !exportFromedNames.has(name)));
207+
185208
const warppedStatements = topLevelStatements.flatMap<SWC.Statement>(stmt => {
186209
if (stmt.type === "VariableDeclaration") {
187210
const declaredNames = stmt.declarations.flatMap(decl => resolvePattern(decl.id));
@@ -283,12 +306,19 @@ export function transformModule(
283306

284307
// Add import of TLA promises from imported modules
285308
let importedPromiseCount = 0;
286-
for (const importDeclaration of imports) {
287-
const importedModuleName = resolveImport(moduleName, importDeclaration.source.value);
309+
for (const declaration of [...imports, ...exportFroms]) {
310+
const importedModuleName = resolveImport(moduleName, declaration.source.value);
288311
if (!importedModuleName || !bundleInfo[importedModuleName]) continue;
289312

290313
if (bundleInfo[importedModuleName].transformNeeded) {
291-
importDeclaration.specifiers.push(
314+
let targetImportDeclaration: SWC.ImportDeclaration;
315+
if (declaration.type === "ImportDeclaration") {
316+
targetImportDeclaration = declaration;
317+
} else {
318+
targetImportDeclaration = makeImportDeclaration(declaration.source);
319+
newImportsByExportFroms.push(targetImportDeclaration);
320+
}
321+
targetImportDeclaration.specifiers.push(
292322
makeImportSpecifier(options.promiseExportName, options.promiseImportName(importedPromiseCount))
293323
);
294324
importedPromiseCount++;
@@ -338,7 +368,7 @@ export function transformModule(
338368
* export { ..., __tla };
339369
*/
340370

341-
const newTopLevel: SWC.ModuleItem[] = [...imports, exportedNamesDeclaration];
371+
const newTopLevel: SWC.ModuleItem[] = [...imports, ...newImportsByExportFroms, ...exportFroms, exportedNamesDeclaration];
342372

343373
if (exportedNames.length > 0 || bundleInfo[moduleName]?.importedBy?.length > 0) {
344374
// If the chunk is being imported, append export of the TLA promise to export list

src/utils/make-node.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,15 @@ export function makeAwaitExpression(expression: SWC.Expression): SWC.AwaitExpres
231231
argument: expression
232232
};
233233
}
234+
235+
export function makeImportDeclaration(source: SWC.StringLiteral): SWC.ImportDeclaration {
236+
const importDeclaration: SWC.ImportDeclaration = {
237+
type: "ImportDeclaration",
238+
specifiers: [],
239+
source,
240+
span: span(),
241+
typeOnly: false
242+
};
243+
244+
return importDeclaration;
245+
}

0 commit comments

Comments
 (0)