Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add transform support for JSON modules imports (#16172)
- Loading branch information
1 parent
1332da5
commit d8dc333
Showing
93 changed files
with
862 additions
and
255 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
src | ||
test | ||
*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# @babel/helper-import-to-platform-api | ||
|
||
> Helper function to transform import statements to platform-specific APIs | ||
See our website [@babel/helper-import-to-platform-api](https://babeljs.io/docs/babel-helper-import-to-platform-api) for more information. | ||
|
||
## Install | ||
|
||
Using npm: | ||
|
||
```sh | ||
npm install --save @babel/helper-import-to-platform-api | ||
``` | ||
|
||
or using yarn: | ||
|
||
```sh | ||
yarn add @babel/helper-import-to-platform-api | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "@babel/helper-import-to-platform-api", | ||
"version": "7.22.5", | ||
"description": "Helper function to transform import statements to platform-specific APIs", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/babel/babel.git", | ||
"directory": "packages/babel-helper-import-to-platform-api" | ||
}, | ||
"homepage": "https://babel.dev/docs/en/next/babel-helper-import-to-platform-api", | ||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"main": "./lib/index.js", | ||
"dependencies": { | ||
"@babel/helper-compilation-targets": "workspace:^", | ||
"@babel/helper-module-imports": "workspace:^" | ||
}, | ||
"peerDependencies": { | ||
"@babel/core": "^7.0.0-0" | ||
}, | ||
"TODO": "The @babel/traverse dependency is only needed for the NodePath TS type. We can consider exporting it from @babel/core.", | ||
"devDependencies": { | ||
"@babel/core": "workspace:^", | ||
"@babel/traverse": "workspace:^" | ||
}, | ||
"engines": { | ||
"node": ">=6.9.0" | ||
}, | ||
"author": "The Babel Team (https://babel.dev/team)", | ||
"conditions": { | ||
"BABEL_8_BREAKING": [ | ||
{ | ||
"engines": { | ||
"node": "^16.20.0 || ^18.16.0 || >=20.0.0" | ||
} | ||
}, | ||
{ | ||
"exports": null | ||
} | ||
], | ||
"USE_ESM": [ | ||
{ | ||
"type": "module" | ||
}, | ||
null | ||
] | ||
}, | ||
"exports": { | ||
".": "./lib/index.js", | ||
"./package.json": "./package.json" | ||
}, | ||
"type": "commonjs" | ||
} |
237 changes: 237 additions & 0 deletions
237
packages/babel-helper-import-to-platform-api/src/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
import { types as t, template } from "@babel/core"; | ||
import type { NodePath } from "@babel/traverse"; | ||
import type { Targets } from "@babel/helper-compilation-targets"; | ||
import { addNamed } from "@babel/helper-module-imports"; | ||
|
||
import getSupport from "./platforms-support.ts"; | ||
|
||
function imp(path: NodePath, name: string, module: string) { | ||
return addNamed(path, name, module, { importedType: "es6" }); | ||
} | ||
|
||
export interface Pieces { | ||
webFetch: (fetch: t.Expression) => t.Expression; | ||
nodeFsSync: (read: t.Expression) => t.Expression; | ||
nodeFsAsync: () => t.Expression; | ||
} | ||
|
||
export interface Builders { | ||
buildFetch: (specifier: t.Expression, path: NodePath) => t.Expression; | ||
buildFetchAsync: (specifier: t.Expression, path: NodePath) => t.Expression; | ||
needsAwait: boolean; | ||
} | ||
|
||
const imr = (s: t.Expression) => template.expression.ast` | ||
import.meta.resolve(${s}) | ||
`; | ||
const imrWithFallback = (s: t.Expression) => template.expression.ast` | ||
import.meta.resolve?.(${s}) ?? new URL(${t.cloneNode(s)}, import.meta.url) | ||
`; | ||
|
||
export function importToPlatformApi( | ||
targets: Targets, | ||
transformers: Pieces, | ||
toCommonJS: boolean, | ||
) { | ||
const { | ||
needsNodeSupport, | ||
needsWebSupport, | ||
nodeSupportsIMR, | ||
webSupportsIMR, | ||
nodeSupportsFsPromises, | ||
} = getSupport(targets); | ||
|
||
let buildFetchAsync: ( | ||
specifier: t.Expression, | ||
path: NodePath, | ||
) => t.Expression; | ||
let buildFetchSync: typeof buildFetchAsync; | ||
|
||
// "p" stands for pattern matching :) | ||
const p = ({ | ||
web: w, | ||
node: n, | ||
nodeFSP: nF = nodeSupportsFsPromises, | ||
webIMR: wI = webSupportsIMR, | ||
nodeIMR: nI = nodeSupportsIMR, | ||
toCJS: c = toCommonJS, | ||
}: { | ||
web: boolean; | ||
node: boolean; | ||
nodeFSP?: boolean; | ||
webIMR?: boolean; | ||
nodeIMR?: boolean; | ||
toCJS?: boolean; | ||
preferSync?: boolean; | ||
}) => +w + (+n << 1) + (+wI << 2) + (+nI << 3) + (+c << 4) + (+nF << 5); | ||
|
||
const readFileP = (fs: t.Expression, arg: t.Expression) => { | ||
if (nodeSupportsFsPromises) { | ||
return template.expression.ast`${fs}.promises.readFile(${arg})`; | ||
} | ||
return template.expression.ast` | ||
new Promise( | ||
(a => | ||
(r, j) => ${fs}.readFile(a, (e, d) => e ? j(e) : r(d)) | ||
)(${arg}) | ||
)`; | ||
}; | ||
|
||
switch ( | ||
p({ | ||
web: needsWebSupport, | ||
node: needsNodeSupport, | ||
webIMR: webSupportsIMR, | ||
nodeIMR: nodeSupportsIMR, | ||
toCJS: toCommonJS, | ||
}) | ||
) { | ||
case p({ web: true, node: true }): | ||
buildFetchAsync = specifier => { | ||
const web = transformers.webFetch( | ||
t.callExpression(t.identifier("fetch"), [ | ||
(webSupportsIMR ? imr : imrWithFallback)(t.cloneNode(specifier)), | ||
]), | ||
); | ||
const node = nodeSupportsIMR | ||
? template.expression.ast` | ||
import("fs").then( | ||
fs => ${readFileP( | ||
t.identifier("fs"), | ||
template.expression.ast`new URL(${imr(specifier)})`, | ||
)} | ||
).then(${transformers.nodeFsAsync()}) | ||
` | ||
: template.expression.ast` | ||
Promise.all([import("fs"), import("module")]) | ||
.then(([fs, module]) => | ||
${readFileP( | ||
t.identifier("fs"), | ||
template.expression.ast` | ||
module.createRequire(import.meta.url).resolve(${specifier}) | ||
`, | ||
)} | ||
) | ||
.then(${transformers.nodeFsAsync()}) | ||
`; | ||
|
||
return template.expression.ast` | ||
typeof process === "object" && process.versions?.node | ||
? ${node} | ||
: ${web} | ||
`; | ||
}; | ||
break; | ||
case p({ web: true, node: false, webIMR: true }): | ||
buildFetchAsync = specifier => | ||
transformers.webFetch( | ||
t.callExpression(t.identifier("fetch"), [imr(specifier)]), | ||
); | ||
break; | ||
case p({ web: true, node: false, webIMR: false }): | ||
buildFetchAsync = specifier => | ||
transformers.webFetch( | ||
t.callExpression(t.identifier("fetch"), [imrWithFallback(specifier)]), | ||
); | ||
break; | ||
case p({ web: false, node: true, toCJS: true }): | ||
buildFetchSync = specifier => | ||
transformers.nodeFsSync(template.expression.ast` | ||
require("fs").readFileSync(require.resolve(${specifier})) | ||
`); | ||
buildFetchAsync = specifier => template.expression.ast` | ||
require("fs").promises.readFile(require.resolve(${specifier})) | ||
.then(${transformers.nodeFsAsync()}) | ||
`; | ||
break; | ||
case p({ web: false, node: true, toCJS: false, nodeIMR: true }): | ||
buildFetchSync = (specifier, path) => | ||
transformers.nodeFsSync(template.expression.ast` | ||
${imp(path, "readFileSync", "fs")}( | ||
new URL(${imr(specifier)}) | ||
) | ||
`); | ||
buildFetchAsync = (specifier, path) => | ||
template.expression.ast` | ||
${imp(path, "promises", "fs")} | ||
.readFile(new URL(${imr(specifier)})) | ||
.then(${transformers.nodeFsAsync()}) | ||
`; | ||
break; | ||
case p({ web: false, node: true, toCJS: false, nodeIMR: false }): | ||
buildFetchSync = (specifier, path) => | ||
transformers.nodeFsSync(template.expression.ast` | ||
${imp(path, "readFileSync", "fs")}( | ||
${imp(path, "createRequire", "module")}(import.meta.url) | ||
.resolve(${specifier}) | ||
) | ||
`); | ||
buildFetchAsync = (specifier, path) => | ||
transformers.webFetch(template.expression.ast` | ||
${imp(path, "promises", "fs")} | ||
.readFile( | ||
${imp(path, "createRequire", "module")}(import.meta.url) | ||
.resolve(${specifier}) | ||
) | ||
`); | ||
break; | ||
default: | ||
throw new Error("Internal Babel error: unreachable code."); | ||
} | ||
|
||
buildFetchAsync ??= buildFetchSync; | ||
const buildFetchAsyncWrapped: typeof buildFetchAsync = (expression, path) => { | ||
if (t.isStringLiteral(expression)) { | ||
return template.expression.ast` | ||
Promise.resolve().then(() => ${buildFetchAsync(expression, path)}) | ||
`; | ||
} else { | ||
return template.expression.ast` | ||
Promise.resolve(\`\${${expression}}\`).then((s) => ${buildFetchAsync( | ||
t.identifier("s"), | ||
path, | ||
)}) | ||
`; | ||
} | ||
}; | ||
|
||
return { | ||
buildFetch: buildFetchSync || buildFetchAsync, | ||
buildFetchAsync: buildFetchAsyncWrapped, | ||
needsAwait: !buildFetchSync, | ||
}; | ||
} | ||
|
||
export function buildParallelStaticImports( | ||
data: Array<{ id: t.Identifier; fetch: t.Expression }>, | ||
needsAwait: boolean, | ||
): t.VariableDeclaration | null { | ||
if (data.length === 0) return null; | ||
|
||
const declarators: t.VariableDeclarator[] = []; | ||
|
||
if (data.length === 1) { | ||
let rhs = data[0].fetch; | ||
if (needsAwait) rhs = t.awaitExpression(rhs); | ||
declarators.push(t.variableDeclarator(data[0].id, rhs)); | ||
} else if (needsAwait) { | ||
const ids = data.map(({ id }) => id); | ||
const fetches = data.map(({ fetch }) => fetch); | ||
declarators.push( | ||
t.variableDeclarator( | ||
t.arrayPattern(ids), | ||
t.awaitExpression( | ||
template.expression.ast` | ||
Promise.all(${t.arrayExpression(fetches)}) | ||
`, | ||
), | ||
), | ||
); | ||
} else { | ||
for (const { id, fetch } of data) { | ||
declarators.push(t.variableDeclarator(id, fetch)); | ||
} | ||
} | ||
|
||
return t.variableDeclaration("const", declarators); | ||
} |
70 changes: 70 additions & 0 deletions
70
packages/babel-helper-import-to-platform-api/src/platforms-support.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { isRequired, type Targets } from "@babel/helper-compilation-targets"; | ||
|
||
function isEmpty(obj: object) { | ||
return Object.keys(obj).length === 0; | ||
} | ||
|
||
const isRequiredOptions = { | ||
compatData: { | ||
// `import.meta.resolve` compat data. | ||
// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve#browser_compatibility | ||
// Once Node.js implements `fetch` of local files, we can re-use the web implementation for it | ||
// similarly to how we do for Deno. | ||
webIMR: { | ||
chrome: "105.0.0", | ||
edge: "105.0.0", | ||
firefox: "106.0.0", | ||
opera: "91.0.0", | ||
safari: "16.4.0", | ||
opera_mobile: "72.0.0", | ||
ios: "16.4.0", | ||
samsung: "20.0", | ||
deno: "1.24.0", | ||
}, | ||
nodeIMR: { | ||
node: "20.6.0", | ||
}, | ||
// Node.js require("fs").promises compat data. | ||
nodeFSP: { | ||
node: "10.0.0", | ||
}, | ||
}, | ||
}; | ||
|
||
interface Support { | ||
needsNodeSupport: boolean; | ||
needsWebSupport: boolean; | ||
nodeSupportsIMR: boolean; | ||
webSupportsIMR: boolean; | ||
nodeSupportsFsPromises: boolean; | ||
} | ||
|
||
const SUPPORT_CACHE = new WeakMap<Targets, Support>(); | ||
export default function getSupport(targets: Targets): Support { | ||
if (SUPPORT_CACHE.has(targets)) return SUPPORT_CACHE.get(targets)!; | ||
|
||
const { node: nodeTarget, ...webTargets } = targets; | ||
const emptyNodeTarget = nodeTarget == null; | ||
const emptyWebTargets = isEmpty(webTargets); | ||
const needsNodeSupport = !emptyNodeTarget || emptyWebTargets; | ||
const needsWebSupport = !emptyWebTargets || emptyNodeTarget; | ||
|
||
const webSupportsIMR = | ||
!emptyWebTargets && !isRequired("webIMR", webTargets, isRequiredOptions); | ||
const nodeSupportsIMR = | ||
!emptyNodeTarget && | ||
!isRequired("nodeIMR", { node: nodeTarget }, isRequiredOptions); | ||
const nodeSupportsFsPromises = | ||
!emptyNodeTarget && | ||
!isRequired("nodeFSP", { node: nodeTarget }, isRequiredOptions); | ||
|
||
const result = { | ||
needsNodeSupport, | ||
needsWebSupport, | ||
nodeSupportsIMR, | ||
webSupportsIMR, | ||
nodeSupportsFsPromises, | ||
}; | ||
SUPPORT_CACHE.set(targets, result); | ||
return result; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.