Skip to content

Commit

Permalink
fix: parse ExportNamedDeclaration with source (#46)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people authored Aug 8, 2024
1 parent cda7687 commit ae1190e
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 15 deletions.
31 changes: 31 additions & 0 deletions src/bundle-info.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,37 @@ describe("Bundle info parser", () => {
}
});

it("should parse dependencies correctly", async () => {
const chunks = {
a: `
export { x } from "./b";
import { s } from "./c";
const w = await globalThis.y?.z(s);
export { w };
`,
b: `
const x = await globalThis.x?.y;
export { x };
`,
c: `
const s = await globalThis.p?.q;
export { s };
`
};

const bundleInfo = await parseBundleInfo(await parseBundleAsts(chunks));

expect(bundleInfo["a"]).toBeTruthy();
expect(bundleInfo["a"].imported).toIncludeSameMembers(["b", "c"]);
expect(bundleInfo["a"].importedBy).toIncludeSameMembers([]);
expect(bundleInfo["b"]).toBeTruthy();
expect(bundleInfo["b"].imported).toIncludeSameMembers([]);
expect(bundleInfo["b"].importedBy).toIncludeSameMembers(["a"]);
expect(bundleInfo["c"]).toBeTruthy();
expect(bundleInfo["c"].imported).toIncludeSameMembers([]);
expect(bundleInfo["c"].importedBy).toIncludeSameMembers(["a"]);
});

it("should parse dependency graph correctly", async () => {
const dependencyGraph = {
a: ["b", "c", "d"],
Expand Down
2 changes: 1 addition & 1 deletion src/bundle-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export async function parseBundleInfo(bundleAsts: Record<string, SWC.Module>): P
// Parse imports
moduleInfo.imported = ast.body
.map(item => {
if (item.type === "ImportDeclaration") {
if (item.type === "ImportDeclaration" || (item.type === "ExportNamedDeclaration" && item.source)) {
return resolveImport(moduleName, item.source.value);
}
})
Expand Down
33 changes: 33 additions & 0 deletions src/transform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,39 @@ describe("Transform top-level await", () => {
);
});

it("should work for a module with export-from statements", () => {
test(
"a",
{
a: { imported: ["./b"], importedBy: [], transformNeeded: true, withTopLevelAwait: true },
b: { imported: [], importedBy: ["./a"], transformNeeded: true, withTopLevelAwait: true }
},
`
import { qwq } from "./b";
export { owo as uwu, default as ovo } from "./b";
export * as QwQ from "./b";
const qaq = await globalThis.someFunc(qwq);
export { qaq };
`,
`
import { qwq, __tla as __tla_0 } from "./b";
import { __tla as __tla_1 } from "./b";
import { __tla as __tla_2 } from "./b";
export { owo as uwu, default as ovo } from "./b";
export * as QwQ from "./b";
let qaq;
let __tla = Promise.all([
(() => { try { return __tla_0; } catch {} })(),
(() => { try { return __tla_1; } catch {} })(),
(() => { try { return __tla_2; } catch {} })(),
]).then(async () => {
qaq = await globalThis.someFunc(qwq);
});
export { qaq, __tla };
`
);
});

it("should skip processing imports of external modules", () => {
test(
"a",
Expand Down
58 changes: 44 additions & 14 deletions src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
makeVariableInitDeclaration,
makeExportListDeclaration,
makeStatement,
makeAwaitExpression
makeAwaitExpression,
makeImportDeclaration
} from "./utils/make-node";
import { RandomIdentifierGenerator } from "./utils/random-identifier";
import { resolveImport } from "./utils/resolve-import";
Expand Down Expand Up @@ -86,6 +87,12 @@ export function transformModule(
// Extract import declarations
const imports = ast.body.filter((item): item is SWC.ImportDeclaration => item.type === "ImportDeclaration");

// Handle `export { named as renamed } from "module";`
const exportFroms = ast.body.filter(
(item): item is SWC.ExportNamedDeclaration => item.type === "ExportNamedDeclaration" && !!item.source
);
const newImportsByExportFroms: SWC.ImportDeclaration[] = [];

const exportMap: Record<string, string> = {};

// Extract export declarations
Expand Down Expand Up @@ -130,15 +137,18 @@ export function transformModule(
}

return false;
// Handle `export { named as renamed };` without "from"
case "ExportNamedDeclaration":
item.specifiers.forEach(specifier => {
/* istanbul ignore if */
if (specifier.type !== "ExportSpecifier") {
raiseUnexpectedNode("export specifier", specifier.type);
}

exportMap[(specifier.exported || specifier.orig).value] = specifier.orig.value;
});
if (!item.source) {
item.specifiers.forEach(specifier => {
/* istanbul ignore if */
if (specifier.type !== "ExportSpecifier") {
raiseUnexpectedNode("export specifier", specifier.type);
}

exportMap[(specifier.exported || specifier.orig).value] = specifier.orig.value;
});
}

return true;
}
Expand Down Expand Up @@ -181,7 +191,20 @@ export function transformModule(
const importedNames = new Set(
imports.flatMap(importStmt => importStmt.specifiers.map(specifier => specifier.local.value))
);
const exportedNamesDeclaration = makeVariablesDeclaration(exportedNames.filter(name => !importedNames.has(name)));
const exportFromedNames = new Set(
exportFroms.flatMap(exportStmt => exportStmt.specifiers.map(specifier => {
if (specifier.type === "ExportNamespaceSpecifier") {
return specifier.name.value;
} else if (specifier.type === "ExportDefaultSpecifier") {
// When will this happen?
return specifier.exported.value;
} else {
return (specifier.exported || specifier.orig).value;
}
}))
);
const exportedNamesDeclaration = makeVariablesDeclaration(exportedNames.filter(name => !importedNames.has(name) && !exportFromedNames.has(name)));

const warppedStatements = topLevelStatements.flatMap<SWC.Statement>(stmt => {
if (stmt.type === "VariableDeclaration") {
const declaredNames = stmt.declarations.flatMap(decl => resolvePattern(decl.id));
Expand Down Expand Up @@ -283,12 +306,19 @@ export function transformModule(

// Add import of TLA promises from imported modules
let importedPromiseCount = 0;
for (const importDeclaration of imports) {
const importedModuleName = resolveImport(moduleName, importDeclaration.source.value);
for (const declaration of [...imports, ...exportFroms]) {
const importedModuleName = resolveImport(moduleName, declaration.source.value);
if (!importedModuleName || !bundleInfo[importedModuleName]) continue;

if (bundleInfo[importedModuleName].transformNeeded) {
importDeclaration.specifiers.push(
let targetImportDeclaration: SWC.ImportDeclaration;
if (declaration.type === "ImportDeclaration") {
targetImportDeclaration = declaration;
} else {
targetImportDeclaration = makeImportDeclaration(declaration.source);
newImportsByExportFroms.push(targetImportDeclaration);
}
targetImportDeclaration.specifiers.push(
makeImportSpecifier(options.promiseExportName, options.promiseImportName(importedPromiseCount))
);
importedPromiseCount++;
Expand Down Expand Up @@ -338,7 +368,7 @@ export function transformModule(
* export { ..., __tla };
*/

const newTopLevel: SWC.ModuleItem[] = [...imports, exportedNamesDeclaration];
const newTopLevel: SWC.ModuleItem[] = [...imports, ...newImportsByExportFroms, ...exportFroms, exportedNamesDeclaration];

if (exportedNames.length > 0 || bundleInfo[moduleName]?.importedBy?.length > 0) {
// If the chunk is being imported, append export of the TLA promise to export list
Expand Down
12 changes: 12 additions & 0 deletions src/utils/make-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,15 @@ export function makeAwaitExpression(expression: SWC.Expression): SWC.AwaitExpres
argument: expression
};
}

export function makeImportDeclaration(source: SWC.StringLiteral): SWC.ImportDeclaration {
const importDeclaration: SWC.ImportDeclaration = {
type: "ImportDeclaration",
specifiers: [],
source,
span: span(),
typeOnly: false
};

return importDeclaration;
}

0 comments on commit ae1190e

Please sign in to comment.