Skip to content

Commit

Permalink
improve
Browse files Browse the repository at this point in the history
  • Loading branch information
liuxingbaoyu committed Dec 14, 2023
1 parent 3f9bb7b commit 8abdadf
Show file tree
Hide file tree
Showing 77 changed files with 1,527 additions and 1,188 deletions.
253 changes: 199 additions & 54 deletions packages/babel-helper-module-transforms/src/index.ts
Expand Up @@ -50,6 +50,7 @@ export type { PluginOptions } from "./get-module-name.ts";
export { hasExports, isSideEffectImport, isModule, rewriteThis };

export interface RewriteModuleStatementsAndPrepareHeaderOptions {
implicitAssignmentExports?: boolean;
exportName?: string;
strict: boolean;
allowTopLevelThis?: boolean;
Expand All @@ -66,9 +67,9 @@ export interface RewriteModuleStatementsAndPrepareHeaderOptions {
wrapReference?: (ref: t.Expression, payload: unknown) => t.Expression | null;
esNamespaceOnly?: boolean;
filename: string | undefined;
constantReexports?: boolean | void;
enumerableModuleMeta?: boolean | void;
noIncompleteNsImportDetection?: boolean | void;
constantReexports?: boolean;
enumerableModuleMeta?: boolean;
noIncompleteNsImportDetection?: boolean;
}

/**
Expand All @@ -80,6 +81,7 @@ export interface RewriteModuleStatementsAndPrepareHeaderOptions {
export function rewriteModuleStatementsAndPrepareHeader(
path: NodePath<t.Program>,
{
implicitAssignmentExports,
exportName,
strict,
allowTopLevelThis,
Expand Down Expand Up @@ -115,6 +117,8 @@ export function rewriteModuleStatementsAndPrepareHeader(
filename,
});

meta.implicitAssignmentExports = implicitAssignmentExports;

if (!allowTopLevelThis) {
rewriteThis(path);
}
Expand All @@ -138,12 +142,7 @@ export function rewriteModuleStatementsAndPrepareHeader(
headers.push(buildESModuleHeader(meta, enumerableModuleMeta));
}

const nameList = buildExportNameListDeclaration(path, meta);

if (nameList) {
meta.exportNameListName = nameList.name;
headers.push(nameList.statement);
}
meta.exportNameList = buildExportNameListDeclaration(meta);

// Create all of the statically known named exports.
headers.push(
Expand All @@ -156,7 +155,23 @@ export function rewriteModuleStatementsAndPrepareHeader(
),
);

return { meta, headers };
const namedReexports = new Set<t.ExpressionStatement>();
const exportHelperName = path.getData(PACKAGE_JSON.name + "#export")?.name;
if (exportHelperName) {
for (const header of headers) {
// _expert("name", ...)
if (
header.type === "ExpressionStatement" &&
header.expression.type === "CallExpression" &&
header.expression.callee.type === "Identifier" &&
header.expression.callee.name === exportHelperName
) {
namedReexports.add(header);
}
}
}

return { meta, headers, namedReexports };
}

/**
Expand Down Expand Up @@ -214,11 +229,12 @@ export function wrapInterop(
export function buildNamespaceInitStatements(
metadata: ModuleMetadata,
sourceMetadata: SourceModuleMetadata,
constantReexports: boolean | void = false,
constantReexports: boolean = false,
wrapReference: (
ref: t.Identifier,
payload: unknown,
) => t.Expression | null = Lazy.wrapReference,
buildExportStar?: (body: t.Statement[]) => t.Statement,
) {
const statements = [];

Expand Down Expand Up @@ -268,6 +284,7 @@ export function buildNamespaceInitStatements(
metadata,
cloneNode(srcNamespace),
constantReexports,
buildExportStar,
);
statement.loc = sourceMetadata.reexportAll.loc;

Expand All @@ -281,6 +298,9 @@ const ReexportTemplate = {
constant: template.statement`EXPORTS.EXPORT_NAME = NAMESPACE_IMPORT;`,
constantComputed: template.statement`EXPORTS["EXPORT_NAME"] = NAMESPACE_IMPORT;`,
spec: template.statement`
EXPORT_FN("EXPORT_NAME", NAMESPACE, IMPORT_NAME);
`,
spec2: template.statement`
Object.defineProperty(EXPORTS, "EXPORT_NAME", {
enumerable: true,
get: function() {
Expand Down Expand Up @@ -327,8 +347,32 @@ function buildReexportsFromMeta(
} else {
return ReexportTemplate.constant(astNodes);
}
} else if (meta.implicitAssignmentExports === undefined) {
return ReexportTemplate.spec2(astNodes);
} else {
return ReexportTemplate.spec(astNodes);
return ReexportTemplate.spec({
EXPORT_NAME: exportName,
NAMESPACE: t.cloneNode(namespace),
IMPORT_NAME:
importName === exportName ? null : t.stringLiteral(importName),
EXPORT_FN: addPrivateHelper(
meta.programPath,
"export",
() =>
template.expression`
function (name, mod, name2) {
Object.defineProperty(EXPORTS, name, {
enumerable: true,
get: function () {
return mod[name2 || name];
}
});
}
`({
EXPORTS: identifier(meta.exportName),
}) as t.FunctionExpression,
),
});
}
});
}
Expand Down Expand Up @@ -359,11 +403,29 @@ function buildESModuleHeader(
function buildNamespaceReexport(
metadata: ModuleMetadata,
namespace: t.Expression,
constantReexports: boolean | void,
constantReexports: boolean,
buildExportStar?: (body: t.Statement[]) => t.Statement,
) {
return (
constantReexports
? template.statement`
const { programPath, exportNameList } = metadata;

if (metadata.implicitAssignmentExports === undefined) {
let exportNameListName;
if (metadata.exportNameList.length > 2) {
exportNameListName =
programPath.scope.generateUidIdentifier("exportNames");
programPath.unshiftContainer(
"body",
variableDeclaration("var", [
variableDeclarator(
exportNameListName,
valueToNode(metadata.exportNameList),
),
]),
);
}
return (
constantReexports
? template.statement`
Object.keys(NAMESPACE).forEach(function(key) {
if (key === "default" || key === "__esModule") return;
VERIFY_NAME_LIST;
Expand All @@ -372,13 +434,13 @@ function buildNamespaceReexport(
EXPORTS[key] = NAMESPACE[key];
});
`
: // Also skip already assigned bindings if they are strictly equal
// to be somewhat more spec-compliant when a file has multiple
// namespace re-exports that would cause a binding to be exported
// multiple times. However, multiple bindings of the same name that
// export the same primitive value are silently skipped
// (the spec requires an "ambiguous bindings" early error here).
template.statement`
: // Also skip already assigned bindings if they are strictly equal
// to be somewhat more spec-compliant when a file has multiple
// namespace re-exports that would cause a binding to be exported
// multiple times. However, multiple bindings of the same name that
// export the same primitive value are silently skipped
// (the spec requires an "ambiguous bindings" early error here).
template.statement`
Object.keys(NAMESPACE).forEach(function(key) {
if (key === "default" || key === "__esModule") return;
VERIFY_NAME_LIST;
Expand All @@ -392,57 +454,127 @@ function buildNamespaceReexport(
});
});
`
)({
NAMESPACE: namespace,
EXPORTS: metadata.exportName,
VERIFY_NAME_LIST: metadata.exportNameListName
? template`
)({
NAMESPACE: namespace,
EXPORTS: metadata.exportName,
VERIFY_NAME_LIST: exportNameListName
? template`
if (Object.prototype.hasOwnProperty.call(EXPORTS_LIST, key)) return;
`({ EXPORTS_LIST: metadata.exportNameListName })
: null,
});
`({ EXPORTS_LIST: exportNameListName })
: null,
});
}

// Also skip already assigned bindings if they are strictly equal
// to be somewhat more spec-compliant when a file has multiple
// namespace re-exports that would cause a binding to be exported
// multiple times. However, multiple bindings of the same name that
// export the same primitive value are silently skipped
// (the spec requires an "ambiguous bindings" early error here).
const fn = template.expression`
function (mod) {
Object.keys(mod).forEach(function (k) {
if (NAME_LIST.indexOf(k) < 0 && !(k in EXPORTS && EXPORTS[k] === mod[k])) {
SET_PROPERTY;
}
});
return mod;
}`({
NAME_LIST: t.valueToNode(exportNameList),
SET_PROPERTY: constantReexports
? template.statement.ast`${metadata.exportName}[k] = mod[k]`
: template.statement.ast`
Object.defineProperty(${metadata.exportName}, k, {
get: function () {
return mod[k];
},
enumerable: true
});
`,
EXPORTS: metadata.exportName,
}) as t.FunctionExpression;

if (buildExportStar) {
return buildExportStar(fn.body.body);
}

const fnId = addPrivateHelper(programPath, "exportStar", () => fn);
return expressionStatement(callExpression(fnId, [namespace]));
}

export function addPrivateHelper(
path: NodePath<t.Program>,
name: string,
buildFn: () => t.FunctionExpression,
) {
const scope = path.scope;
const key = PACKAGE_JSON.name + "#" + name;
let id = path.getData(key);
if (id) {
return t.cloneNode(id);
}
if (name.startsWith("_")) {
let uid;
let i = 1;
do {
uid = scope._generateUid(name, i);
i++;
} while (
scope.hasLabel(uid) ||
scope.hasBinding(uid) ||
scope.hasGlobal(uid) ||
scope.hasReference(uid)
);

scope.references[uid] = true;
scope.uids[uid] = true;

id = t.identifier(uid);
} else {
id = scope.generateUidIdentifier(name);
}
path.setData(key, id);

const fn = buildFn();
const [fnPath] = path.unshiftContainer(
"body",
t.functionDeclaration(id, fn.params, fn.body),
);
path.scope.registerBinding("hoisted", fnPath);

return t.cloneNode(id);
}

/**
* Build a statement declaring a variable that contains all of the exported
* variable names in an object so they can easily be referenced from an
* export * from statement to check for conflicts.
*/
function buildExportNameListDeclaration(
programPath: NodePath,
metadata: ModuleMetadata,
) {
const exportedVars = Object.create(null);
function buildExportNameListDeclaration(metadata: ModuleMetadata) {
const exportedVars = new Set<string>();

exportedVars.add("default");
exportedVars.add("__esModule");

for (const data of metadata.local.values()) {
for (const name of data.names) {
exportedVars[name] = true;
exportedVars.add(name);
}
}

let hasReexport = false;
for (const data of metadata.source.values()) {
for (const exportName of data.reexports.keys()) {
exportedVars[exportName] = true;
exportedVars.add(exportName);
}
for (const exportName of data.reexportNamespace) {
exportedVars[exportName] = true;
exportedVars.add(exportName);
}

hasReexport = hasReexport || !!data.reexportAll;
}

if (!hasReexport || Object.keys(exportedVars).length === 0) return null;

const name = programPath.scope.generateUidIdentifier("exportNames");

delete exportedVars.default;

return {
name: name.name,
statement: variableDeclaration("var", [
variableDeclarator(name, valueToNode(exportedVars)),
]),
};
return Array.from(exportedVars);
}

/**
Expand All @@ -453,8 +585,8 @@ function buildExportInitializationStatements(
programPath: NodePath,
metadata: ModuleMetadata,
wrapReference: (ref: t.Expression, payload: unknown) => t.Expression | null,
constantReexports: boolean | void = false,
noIncompleteNsImportDetection: boolean | void = false,
constantReexports: boolean = false,
noIncompleteNsImportDetection: boolean = false,
) {
const initStatements: Array<[string, t.Statement | null]> = [];

Expand Down Expand Up @@ -504,6 +636,19 @@ function buildExportInitializationStatements(
return 0;
});

const defineInits = [];
if (metadata.implicitAssignmentExports === false) {
for (const init of initStatements) {
if (
t.isExpressionStatement(init[1]) &&
t.isCallExpression(init[1].expression)
) {
defineInits.push(init[1]);
init[1] = null;
}
}
}

const results = [];
if (noIncompleteNsImportDetection) {
for (const [, initStatement] of initStatements) {
Expand Down Expand Up @@ -547,7 +692,7 @@ function buildExportInitializationStatements(
}
}

return results;
return results.concat(defineInits);
}

/**
Expand Down

0 comments on commit 8abdadf

Please sign in to comment.