Skip to content

Commit

Permalink
Implement import defer proposal transform support (#15878)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Sep 25, 2023
1 parent edfac24 commit a65f18e
Show file tree
Hide file tree
Showing 57 changed files with 819 additions and 74 deletions.
50 changes: 36 additions & 14 deletions packages/babel-helper-module-transforms/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import normalizeModuleAndLoadMetadata, {
import type {
ImportInterop,
InteropType,
Lazy,
ModuleMetadata,
SourceModuleMetadata,
} from "./normalize-and-load-metadata.ts";
import * as Lazy from "./lazy-modules.ts";
import type { NodePath } from "@babel/traverse";

const {
Expand Down Expand Up @@ -57,7 +57,13 @@ export interface RewriteModuleStatementsAndPrepareHeaderOptions {
loose?: boolean;
importInterop?: ImportInterop;
noInterop?: boolean;
lazy?: Lazy;
lazy?: Lazy.Lazy;
getWrapperPayload?: (
source: string,
metadata: SourceModuleMetadata,
importNodes: t.Node[],
) => unknown;
wrapReference?: (ref: t.Expression, payload: unknown) => t.Expression | null;
esNamespaceOnly?: boolean;
filename: string | undefined;
constantReexports?: boolean | void;
Expand All @@ -80,7 +86,11 @@ export function rewriteModuleStatementsAndPrepareHeader(
strictMode,
noInterop,
importInterop = noInterop ? "none" : "babel",
// TODO(Babel 8): After that `lazy` implementation is moved to the CJS
// transform, remove this parameter.
lazy,
getWrapperPayload = Lazy.toGetWrapperPayload(lazy ?? false),
wrapReference = Lazy.wrapReference,
esNamespaceOnly,
filename,

Expand All @@ -100,7 +110,7 @@ export function rewriteModuleStatementsAndPrepareHeader(
const meta = normalizeModuleAndLoadMetadata(path, exportName, {
importInterop,
initializeReexports: constantReexports,
lazy,
getWrapperPayload,
esNamespaceOnly,
filename,
});
Expand All @@ -109,7 +119,7 @@ export function rewriteModuleStatementsAndPrepareHeader(
rewriteThis(path);
}

rewriteLiveReferences(path, meta);
rewriteLiveReferences(path, meta, wrapReference);

if (strictMode !== false) {
const hasStrict = path.node.directives.some(directive => {
Expand Down Expand Up @@ -140,6 +150,7 @@ export function rewriteModuleStatementsAndPrepareHeader(
...buildExportInitializationStatements(
path,
meta,
wrapReference,
constantReexports,
noIncompleteNsImportDetection,
),
Expand Down Expand Up @@ -204,6 +215,10 @@ export function buildNamespaceInitStatements(
metadata: ModuleMetadata,
sourceMetadata: SourceModuleMetadata,
constantReexports: boolean | void = false,
wrapReference: (
ref: t.Identifier,
payload: unknown,
) => t.Expression | null = Lazy.wrapReference,
) {
const statements = [];

Expand All @@ -221,17 +236,18 @@ export function buildNamespaceInitStatements(
);
}

const srcNamespace = sourceMetadata.lazy
? callExpression(srcNamespaceId, [])
: srcNamespaceId;
const srcNamespace =
wrapReference(srcNamespaceId, sourceMetadata.wrap) ?? srcNamespaceId;

if (constantReexports) {
statements.push(...buildReexportsFromMeta(metadata, sourceMetadata, true));
statements.push(
...buildReexportsFromMeta(metadata, sourceMetadata, true, wrapReference),
);
}
for (const exportName of sourceMetadata.reexportNamespace) {
// Assign export to namespace object.
statements.push(
(sourceMetadata.lazy
(!t.isIdentifier(srcNamespace)
? template.statement`
Object.defineProperty(EXPORTS, "NAME", {
enumerable: true,
Expand Down Expand Up @@ -278,10 +294,10 @@ function buildReexportsFromMeta(
meta: ModuleMetadata,
metadata: SourceModuleMetadata,
constantReexports: boolean,
wrapReference: (ref: t.Expression, payload: unknown) => t.Expression | null,
) {
const namespace = metadata.lazy
? callExpression(identifier(metadata.name), [])
: identifier(metadata.name);
let namespace: t.Expression = identifier(metadata.name);
namespace = wrapReference(namespace, metadata.wrap) ?? namespace;

const { stringSpecifiers } = meta;
return Array.from(metadata.reexports, ([exportName, importName]) => {
Expand Down Expand Up @@ -342,7 +358,7 @@ function buildESModuleHeader(
*/
function buildNamespaceReexport(
metadata: ModuleMetadata,
namespace: t.Identifier | t.CallExpression,
namespace: t.Expression,
constantReexports: boolean | void,
) {
return (
Expand Down Expand Up @@ -436,6 +452,7 @@ function buildExportNameListDeclaration(
function buildExportInitializationStatements(
programPath: NodePath,
metadata: ModuleMetadata,
wrapReference: (ref: t.Expression, payload: unknown) => t.Expression | null,
constantReexports: boolean | void = false,
noIncompleteNsImportDetection: boolean | void = false,
) {
Expand All @@ -460,7 +477,12 @@ function buildExportInitializationStatements(

for (const data of metadata.source.values()) {
if (!constantReexports) {
const reexportsStatements = buildReexportsFromMeta(metadata, data, false);
const reexportsStatements = buildReexportsFromMeta(
metadata,
data,
false,
wrapReference,
);
const reexports = [...data.reexports.keys()];
for (let i = 0; i < reexportsStatements.length; i++) {
initStatements.push([reexports[i], reexportsStatements[i]]);
Expand Down
37 changes: 37 additions & 0 deletions packages/babel-helper-module-transforms/src/lazy-modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// TODO: Move `lazy` implementation logic into the CommonJS plugin, since other
// modules systems do not support `lazy`.

import { types as t } from "@babel/core";
import {
type SourceModuleMetadata,
isSideEffectImport,
} from "./normalize-and-load-metadata.ts";

export type Lazy = boolean | string[] | ((source: string) => boolean);

export function toGetWrapperPayload(lazy: Lazy) {
return (source: string, metadata: SourceModuleMetadata): null | "lazy" => {
if (lazy === false) return null;
if (isSideEffectImport(metadata) || metadata.reexportAll) return null;
if (lazy === true) {
// 'true' means that local relative files are eagerly loaded and
// dependency modules are loaded lazily.
return /\./.test(source) ? null : "lazy";
}
if (Array.isArray(lazy)) {
return lazy.indexOf(source) === -1 ? null : "lazy";
}
if (typeof lazy === "function") {
return lazy(source) ? "lazy" : null;
}
throw new Error(`.lazy must be a boolean, string array, or function`);
};
}

export function wrapReference(
ref: t.Identifier,
payload: unknown,
): t.Expression | null {
if (payload === "lazy") return t.callExpression(ref, []);
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export type ImportInterop =
| "node"
| ((source: string, filename?: string) => "none" | "babel" | "node");

export type Lazy = boolean | string[] | ((source: string) => boolean);

export interface SourceModuleMetadata {
// A unique variable name to use for this namespace object. Centralized for simplicity.
name: string;
Expand All @@ -53,7 +51,7 @@ export interface SourceModuleMetadata {
reexportAll: null | {
loc: t.SourceLocation | undefined | null;
};
lazy?: Lazy;
wrap?: unknown;
referenced: boolean;
}

Expand Down Expand Up @@ -119,13 +117,17 @@ export default function normalizeModuleAndLoadMetadata(
{
importInterop,
initializeReexports = false,
lazy = false,
getWrapperPayload,
esNamespaceOnly = false,
filename,
}: {
importInterop: ImportInterop;
initializeReexports: boolean | void;
lazy: Lazy;
getWrapperPayload?: (
source: string,
metadata: SourceModuleMetadata,
importNodes: t.Node[],
) => unknown;
esNamespaceOnly: boolean;
filename: string;
},
Expand All @@ -139,7 +141,7 @@ export default function normalizeModuleAndLoadMetadata(

const { local, sources, hasExports } = getModuleMetadata(
programPath,
{ initializeReexports, lazy },
{ initializeReexports, getWrapperPayload },
stringSpecifiers,
);

Expand Down Expand Up @@ -231,11 +233,14 @@ function assertExportSpecifier(
function getModuleMetadata(
programPath: NodePath<t.Program>,
{
lazy,
getWrapperPayload,
initializeReexports,
}: {
// todo(flow-ts) changed from boolean, to match expected usage inside the function
lazy: boolean | string[] | ((source: string) => boolean);
getWrapperPayload?: (
source: string,
metadata: SourceModuleMetadata,
importNodes: t.Node[],
) => unknown;
initializeReexports: boolean | void;
},
stringSpecifiers: Set<string>,
Expand All @@ -246,8 +251,9 @@ function getModuleMetadata(
stringSpecifiers,
);

const importNodes = new Map<string, t.Node[]>();
const sourceData = new Map<string, SourceModuleMetadata>();
const getData = (sourceNode: t.StringLiteral) => {
const getData = (sourceNode: t.StringLiteral, node: t.Node) => {
const source = sourceNode.value;

let data = sourceData.get(source);
Expand All @@ -270,18 +276,29 @@ function getModuleMetadata(
reexportNamespace: new Set(),
reexportAll: null,

lazy: false,
wrap: null,

// @ts-expect-error lazy is not listed in the type.
// This is needed for compatibility with older version of the commonjs
// plusing.
// TODO(Babel 8): Remove this
get lazy() {
return this.wrap === "lazy";
},

referenced: false,
};
sourceData.set(source, data);
importNodes.set(source, [node]);
} else {
importNodes.get(source).push(node);
}
return data;
};
let hasExports = false;
programPath.get("body").forEach(child => {
if (child.isImportDeclaration()) {
const data = getData(child.node.source);
const data = getData(child.node.source, child.node);
if (!data.loc) data.loc = child.node.loc;

child.get("specifiers").forEach(spec => {
Expand Down Expand Up @@ -334,7 +351,7 @@ function getModuleMetadata(
});
} else if (child.isExportAllDeclaration()) {
hasExports = true;
const data = getData(child.node.source);
const data = getData(child.node.source, child.node);
if (!data.loc) data.loc = child.node.loc;

data.reexportAll = {
Expand All @@ -343,7 +360,7 @@ function getModuleMetadata(
data.referenced = true;
} else if (child.isExportNamedDeclaration() && child.node.source) {
hasExports = true;
const data = getData(child.node.source);
const data = getData(child.node.source, child.node);
if (!data.loc) data.loc = child.node.loc;

child.get("specifiers").forEach(spec => {
Expand Down Expand Up @@ -404,22 +421,13 @@ function getModuleMetadata(
}
}

for (const [source, metadata] of sourceData) {
if (
lazy !== false &&
!(isSideEffectImport(metadata) || metadata.reexportAll)
) {
if (lazy === true) {
// 'true' means that local relative files are eagerly loaded and
// dependency modules are loaded lazily.
metadata.lazy = !/\./.test(source);
} else if (Array.isArray(lazy)) {
metadata.lazy = lazy.indexOf(source) !== -1;
} else if (typeof lazy === "function") {
metadata.lazy = lazy(source);
} else {
throw new Error(`.lazy must be a boolean, string array, or function`);
}
if (getWrapperPayload) {
for (const [source, metadata] of sourceData) {
metadata.wrap = getWrapperPayload(
source,
metadata,
importNodes.get(source),
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type { ModuleMetadata } from "./normalize-and-load-metadata.ts";

const {
assignmentExpression,
callExpression,
cloneNode,
expressionStatement,
getOuterBindingIdentifiers,
Expand Down Expand Up @@ -76,6 +75,7 @@ function isInType(path: NodePath) {
export default function rewriteLiveReferences(
programPath: NodePath<t.Program>,
metadata: ModuleMetadata,
wrapReference: (ref: t.Expression, payload: unknown) => null | t.Expression,
) {
const imported = new Map();
const exported = new Map();
Expand Down Expand Up @@ -139,23 +139,22 @@ export default function rewriteLiveReferences(
scope: programPath.scope,
imported, // local / import
exported, // local name => exported name list
buildImportReference: ([source, importName, localName], identNode) => {
buildImportReference([source, importName, localName], identNode) {
const meta = metadata.source.get(source);
meta.referenced = true;

if (localName) {
if (meta.lazy) {
identNode = callExpression(
// @ts-expect-error Fixme: we should handle the case when identNode is a JSXIdentifier
identNode,
[],
);
if (meta.wrap) {
// @ts-expect-error Fixme: we should handle the case when identNode is a JSXIdentifier
identNode = wrapReference(identNode, meta.wrap) ?? identNode;
}
return identNode;
}

let namespace: t.Expression = identifier(meta.name);
if (meta.lazy) namespace = callExpression(namespace, []);
if (meta.wrap) {
namespace = wrapReference(namespace, meta.wrap) ?? namespace;
}

if (importName === "default" && meta.interop === "node-default") {
return namespace;
Expand Down
4 changes: 4 additions & 0 deletions packages/babel-helpers/src/helpers-generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export default Object.freeze({
"7.22.0",
'function dispose_SuppressedError(r,e){return"undefined"!=typeof SuppressedError?dispose_SuppressedError=SuppressedError:(dispose_SuppressedError=function(r,e){this.suppressed=r,this.error=e,this.stack=(new Error).stack},dispose_SuppressedError.prototype=Object.create(Error.prototype,{constructor:{value:dispose_SuppressedError,writable:!0,configurable:!0}})),new dispose_SuppressedError(r,e)}export default function _dispose(r,e,s){function next(){for(;r.length>0;)try{var o=r.pop(),p=o.d.call(o.v);if(o.a)return Promise.resolve(p).then(next,err)}catch(r){return err(r)}if(s)throw e}function err(r){return e=s?new dispose_SuppressedError(r,e):r,s=!0,next()}return next()}',
),
importDeferProxy: helper(
"7.22.0",
"export default function _importDeferProxy(e){var t=null,constValue=function(e){return function(){return e}},proxy=function(r){return function(n,o,f){return null===t&&(t=e()),r(t,o,f)}};return new Proxy({},{defineProperty:constValue(!1),deleteProperty:constValue(!1),get:proxy(Reflect.get),getOwnPropertyDescriptor:proxy(Reflect.getOwnPropertyDescriptor),getPrototypeOf:constValue(null),isExtensible:constValue(!1),has:proxy(Reflect.has),ownKeys:proxy(Reflect.ownKeys),preventExtensions:constValue(!0),set:constValue(!1),setPrototypeOf:constValue(!1)})}",
),
iterableToArrayLimit: helper(
"7.0.0-beta.0",
'export default function _iterableToArrayLimit(r,l){var t=null==r?null:"undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(null!=t){var e,n,i,u,a=[],f=!0,o=!1;try{if(i=(t=t.call(r)).next,0===l){if(Object(t)!==t)return;f=!1}else for(;!(f=(e=i.call(t)).done)&&(a.push(e.value),a.length!==l);f=!0);}catch(r){o=!0,n=r}finally{try{if(!f&&null!=t.return&&(u=t.return(),Object(u)!==u))return}finally{if(o)throw n}}return a}}',
Expand Down
Loading

0 comments on commit a65f18e

Please sign in to comment.