From a387d995e49c338cff349e4d3fbb604ed32d6f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 9 Mar 2022 22:43:01 +0100 Subject: [PATCH 1/6] Avoid unnecessary traverse to get program path --- packages/babel-helpers/src/index.ts | 109 +++++++++++++--------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/packages/babel-helpers/src/index.ts b/packages/babel-helpers/src/index.ts index 03c58e2efd34..e2cb2697f259 100644 --- a/packages/babel-helpers/src/index.ts +++ b/packages/babel-helpers/src/index.ts @@ -205,68 +205,61 @@ function permuteHelperAST( toRename[exportName] = id.name; } - const visitor: Visitor = { - Program(path) { - // We need to compute these in advance because removing nodes would - // invalidate the paths. - const exp: NodePath = path.get(exportPath); - const imps: NodePath[] = importPaths.map(p => - path.get(p), + const { path } = file; + + // We need to compute these in advance because removing nodes would + // invalidate the paths. + const exp: NodePath = path.get(exportPath); + const imps: NodePath[] = importPaths.map(p => + path.get(p), + ); + const impsBindingRefs: NodePath[] = + importBindingsReferences.map(p => path.get(p)); + + const decl = exp.get("declaration"); + if (id.type === "Identifier") { + if (decl.isFunctionDeclaration()) { + exp.replaceWith(decl); + } else { + exp.replaceWith( + variableDeclaration("var", [ + variableDeclarator(id, decl.node as t.Expression), + ]), ); - const impsBindingRefs: NodePath[] = - importBindingsReferences.map(p => path.get(p)); - - const decl = exp.get("declaration"); - if (id.type === "Identifier") { - if (decl.isFunctionDeclaration()) { - exp.replaceWith(decl); - } else { - exp.replaceWith( - variableDeclaration("var", [ - variableDeclarator(id, decl.node as t.Expression), - ]), - ); - } - } else if (id.type === "MemberExpression") { - if (decl.isFunctionDeclaration()) { - exportBindingAssignments.forEach(assignPath => { - const assign: NodePath = path.get(assignPath); - assign.replaceWith(assignmentExpression("=", id, assign.node)); - }); - exp.replaceWith(decl); - path.pushContainer( - "body", - expressionStatement( - assignmentExpression("=", id, identifier(exportName)), - ), - ); - } else { - exp.replaceWith( - expressionStatement( - assignmentExpression("=", id, decl.node as t.Expression), - ), - ); - } - } else { - throw new Error("Unexpected helper format."); - } - - Object.keys(toRename).forEach(name => { - path.scope.rename(name, toRename[name]); + } + } else if (id.type === "MemberExpression") { + if (decl.isFunctionDeclaration()) { + exportBindingAssignments.forEach(assignPath => { + const assign: NodePath = path.get(assignPath); + assign.replaceWith(assignmentExpression("=", id, assign.node)); }); + exp.replaceWith(decl); + path.pushContainer( + "body", + expressionStatement( + assignmentExpression("=", id, identifier(exportName)), + ), + ); + } else { + exp.replaceWith( + expressionStatement( + assignmentExpression("=", id, decl.node as t.Expression), + ), + ); + } + } else { + throw new Error("Unexpected helper format."); + } - for (const path of imps) path.remove(); - for (const path of impsBindingRefs) { - const node = cloneNode(dependenciesRefs[path.node.name]); - path.replaceWith(node); - } + Object.keys(toRename).forEach(name => { + path.scope.rename(name, toRename[name]); + }); - // We only use "traverse" for all the handy scoping helpers, so we can stop immediately without - // actually doing the traversal. - path.stop(); - }, - }; - traverse(file.ast, visitor, file.scope); + for (const path of imps) path.remove(); + for (const path of impsBindingRefs) { + const node = cloneNode(dependenciesRefs[path.node.name]); + path.replaceWith(node); + } } interface HelperData { From d387c5bb644ed9802a34c3112cc3c5e92cecb24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 9 Mar 2022 22:51:32 +0100 Subject: [PATCH 2/6] Helpers only export function declarations --- packages/babel-helpers/src/index.ts | 62 ++++++++++------------------- 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/packages/babel-helpers/src/index.ts b/packages/babel-helpers/src/index.ts index e2cb2697f259..eb4109b77ecd 100644 --- a/packages/babel-helpers/src/index.ts +++ b/packages/babel-helpers/src/index.ts @@ -7,8 +7,6 @@ import { expressionStatement, file as t_file, identifier, - variableDeclaration, - variableDeclarator, } from "@babel/types"; import type * as t from "@babel/types"; import helpers from "./helpers"; @@ -74,15 +72,13 @@ function getHelperMetadata(file: File): HelperMetadata { ExportDefaultDeclaration(child) { const decl = child.get("declaration"); - if (decl.isFunctionDeclaration()) { - if (!decl.node.id) { - throw decl.buildCodeFrameError( - "Helpers should give names to their exported func declaration", - ); - } - - exportName = decl.node.id.name; + if (!decl.isFunctionDeclaration() || !decl.node.id) { + throw decl.buildCodeFrameError( + "Helpers can only export named function declarations", + ); } + + exportName = decl.node.id.name; exportPath = makePath(child); }, ExportAllDeclaration(child) { @@ -140,7 +136,7 @@ function getHelperMetadata(file: File): HelperMetadata { traverse(file.ast, dependencyVisitor, file.scope); traverse(file.ast, referenceVisitor, file.scope); - if (!exportPath) throw new Error("Helpers must default-export something."); + if (!exportPath) throw new Error("Helpers must have a default export."); // Process these in reverse so that mutating the references does not invalidate any later paths in // the list. @@ -216,37 +212,23 @@ function permuteHelperAST( const impsBindingRefs: NodePath[] = importBindingsReferences.map(p => path.get(p)); - const decl = exp.get("declaration"); + // We assert that this is a FunctionDeclaration in dependencyVisitor. + const decl = exp.get("declaration") as NodePath; + if (id.type === "Identifier") { - if (decl.isFunctionDeclaration()) { - exp.replaceWith(decl); - } else { - exp.replaceWith( - variableDeclaration("var", [ - variableDeclarator(id, decl.node as t.Expression), - ]), - ); - } + exp.replaceWith(decl); } else if (id.type === "MemberExpression") { - if (decl.isFunctionDeclaration()) { - exportBindingAssignments.forEach(assignPath => { - const assign: NodePath = path.get(assignPath); - assign.replaceWith(assignmentExpression("=", id, assign.node)); - }); - exp.replaceWith(decl); - path.pushContainer( - "body", - expressionStatement( - assignmentExpression("=", id, identifier(exportName)), - ), - ); - } else { - exp.replaceWith( - expressionStatement( - assignmentExpression("=", id, decl.node as t.Expression), - ), - ); - } + exportBindingAssignments.forEach(assignPath => { + const assign: NodePath = path.get(assignPath); + assign.replaceWith(assignmentExpression("=", id, assign.node)); + }); + exp.replaceWith(decl); + path.pushContainer( + "body", + expressionStatement( + assignmentExpression("=", id, identifier(exportName)), + ), + ); } else { throw new Error("Unexpected helper format."); } From 84ebbfc73fce42c16b7e827f9f86e3fbd6d3d49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 9 Mar 2022 23:24:34 +0100 Subject: [PATCH 3/6] [babel 8] Remove old compat code --- packages/babel-helpers/src/index.ts | 36 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/babel-helpers/src/index.ts b/packages/babel-helpers/src/index.ts index eb4109b77ecd..35b336d2c4ec 100644 --- a/packages/babel-helpers/src/index.ts +++ b/packages/babel-helpers/src/index.ts @@ -5,7 +5,7 @@ import { assignmentExpression, cloneNode, expressionStatement, - file as t_file, + file, identifier, } from "@babel/types"; import type * as t from "@babel/types"; @@ -22,7 +22,7 @@ function makePath(path: NodePath) { return parts.reverse().join("."); } -let fileClass = undefined; +let FileClass: typeof File | undefined = undefined; interface HelperMetadata { globals: string[]; @@ -269,16 +269,19 @@ function loadHelper(name: string) { } const fn = (): File => { - const file = { ast: t_file(helper.ast()) }; - if (fileClass) { - return new fileClass( - { - filename: `babel-helper://${name}`, - }, - file, - ); + if (!process.env.BABEL_8_BREAKING) { + if (!FileClass) { + return { ast: file(helper.ast()) } as File; + } } - return file as File; + return new FileClass( + { filename: `babel-helper://${name}` }, + { + ast: file(helper.ast()), + code: "[internal Babel helper code]", + inputMap: null, + }, + ); }; const metadata = getHelperMetadata(fn()); @@ -320,12 +323,11 @@ export function getDependencies(name: string): ReadonlyArray { return Array.from(loadHelper(name).dependencies.values()); } -export function ensure(name: string, newFileClass?) { - if (!fileClass) { - // optional fileClass used to wrap helper snippets into File instance, - // offering `path.hub` support during traversal - fileClass = newFileClass; - } +export function ensure(name: string, newFileClass: typeof File) { + // We inject the File class here rather than importing it to avoid + // circular dependencies between @babel/core and @babel/helpers. + FileClass ||= newFileClass; + loadHelper(name); } From de7eb9f8d5bd08e774f17a3df794ffd4a2e2238c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 10 Mar 2022 00:33:51 +0100 Subject: [PATCH 4/6] Lazily compute helper metadata - When using `@babel/runtime`, we don't need the metadata so we can skip computing it - This fixes a Babel 8 bug introduced in the prev commit, because FileClass is not initialized when not using inline helpers --- packages/babel-helpers/src/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/babel-helpers/src/index.ts b/packages/babel-helpers/src/index.ts index 35b336d2c4ec..239c690b6060 100644 --- a/packages/babel-helpers/src/index.ts +++ b/packages/babel-helpers/src/index.ts @@ -284,11 +284,15 @@ function loadHelper(name: string) { ); }; - const metadata = getHelperMetadata(fn()); + // We compute the helper metadata lazily, so that we skip that + // work if we only need the `.minVersion` (for example because + // of a call to `.availableHelper` when `@babel/rutime`). + let metadata: HelperMetadata | null = null; helperData[name] = { build(getDependency, id, localBindings) { const file = fn(); + metadata ||= getHelperMetadata(file); permuteHelperAST(file, metadata, id, localBindings, getDependency); return { @@ -299,7 +303,10 @@ function loadHelper(name: string) { minVersion() { return helper.minVersion; }, - dependencies: metadata.dependencies, + get dependencies() { + metadata ||= getHelperMetadata(fn()); + return metadata.dependencies; + }, }; } From d1c7ee515cb3194cca901c42aae16bc534060b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 10 Mar 2022 00:41:50 +0100 Subject: [PATCH 5/6] Move side effects from a getter to a function It's just nicer, side effects in getters are surprising. --- packages/babel-helpers/src/index.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/babel-helpers/src/index.ts b/packages/babel-helpers/src/index.ts index 239c690b6060..5a422f226374 100644 --- a/packages/babel-helpers/src/index.ts +++ b/packages/babel-helpers/src/index.ts @@ -253,8 +253,8 @@ interface HelperData { nodes: t.Program["body"]; globals: string[]; }; - minVersion: () => string; - dependencies: Map; + minVersion: string; + getDependencies: () => string[]; } const helperData: Record = Object.create(null); @@ -290,6 +290,7 @@ function loadHelper(name: string) { let metadata: HelperMetadata | null = null; helperData[name] = { + minVersion: helper.minVersion, build(getDependency, id, localBindings) { const file = fn(); metadata ||= getHelperMetadata(file); @@ -300,12 +301,9 @@ function loadHelper(name: string) { globals: metadata.globals, }; }, - minVersion() { - return helper.minVersion; - }, - get dependencies() { + getDependencies() { metadata ||= getHelperMetadata(fn()); - return metadata.dependencies; + return Array.from(metadata.dependencies.values()); }, }; } @@ -323,11 +321,11 @@ export function get( } export function minVersion(name: string) { - return loadHelper(name).minVersion(); + return loadHelper(name).minVersion; } export function getDependencies(name: string): ReadonlyArray { - return Array.from(loadHelper(name).dependencies.values()); + return loadHelper(name).getDependencies(); } export function ensure(name: string, newFileClass: typeof File) { From 51439df9ff19d031ebfe87b3077feea70f8e8a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Thu, 10 Mar 2022 00:44:49 +0100 Subject: [PATCH 6/6] Fix `@babel/core@7.0.0` compatibility --- packages/babel-helpers/src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/babel-helpers/src/index.ts b/packages/babel-helpers/src/index.ts index 5a422f226374..cf8c3c28601e 100644 --- a/packages/babel-helpers/src/index.ts +++ b/packages/babel-helpers/src/index.ts @@ -271,7 +271,11 @@ function loadHelper(name: string) { const fn = (): File => { if (!process.env.BABEL_8_BREAKING) { if (!FileClass) { - return { ast: file(helper.ast()) } as File; + const fakeFile = { ast: file(helper.ast()), path: null } as File; + traverse(fakeFile.ast, { + Program: path => (fakeFile.path = path).stop(), + }); + return fakeFile; } } return new FileClass(