Skip to content

Commit ca4865a

Browse files
authored
Fix: align behaviour to tsc rewriteRelativeImportExtensions (#17118)
* fix: align tsRewriteRelativeImportExtension to tsc * update test fixtures * update helper artifacts * add tsRewriteRelativeImportExtensions unit test * add more test cases * add more test cases * skip call expression handling in Babel 8 * support windows path separator * Revert "support windows path separator" This reverts commit cb85db5. * add more test cases * update min version
1 parent 7162c22 commit ca4865a

File tree

10 files changed

+238
-19
lines changed

10 files changed

+238
-19
lines changed

packages/babel-helpers/src/helpers-generated.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,18 @@ const helpers: Record<string, Helper> = {
13031303
dependencies: {},
13041304
},
13051305
),
1306+
// size: 243, gzip size: 210
1307+
tsRewriteRelativeImportExtensions: helper(
1308+
"7.27.0",
1309+
'function tsRewriteRelativeImportExtensions(t,e){return"string"==typeof t&&/^\\.\\.?\\//.test(t)?t.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+)?)\\.([cm]?)ts$/i,(function(t,s,r,n,o){return s?e?".jsx":".js":!r||n&&o?r+n+"."+o.toLowerCase()+"js":t})):t}',
1310+
{
1311+
globals: [],
1312+
locals: { tsRewriteRelativeImportExtensions: ["body.0.id"] },
1313+
exportBindingAssignments: [],
1314+
exportName: "tsRewriteRelativeImportExtensions",
1315+
dependencies: {},
1316+
},
1317+
),
13061318
// size: 274, gzip size: 157
13071319
typeof: helper(
13081320
"7.0.0-beta.0",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/* @minVersion 7.27.0 */
2+
3+
// https://github.com/microsoft/TypeScript/blob/71716a2868c87248af5020e33a84a2178d41a2d6/src/compiler/factory/emitHelpers.ts#L1451
4+
export default function tsRewriteRelativeImportExtensions(
5+
path: unknown,
6+
preserveJsx?: boolean,
7+
) {
8+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
9+
return path.replace(
10+
/\.(tsx)$|((?:\.d)?)((?:\.[^./]+)?)\.([cm]?)ts$/i,
11+
function (m, tsx, d, ext, cm) {
12+
return tsx
13+
? preserveJsx
14+
? ".jsx"
15+
: ".js"
16+
: d && (!ext || !cm)
17+
? m
18+
: d + ext + "." + cm.toLowerCase() + "js";
19+
},
20+
);
21+
}
22+
return path;
23+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import _tsRewriteRelativeImportExtensions from "../../lib/helpers/tsRewriteRelativeImportExtensions.js";
2+
const tsRewriteRelativeImportExtensions =
3+
_tsRewriteRelativeImportExtensions.default ||
4+
_tsRewriteRelativeImportExtensions;
5+
6+
describe("tsRewriteRelativeImportExtensions", () => {
7+
it.each([
8+
// rewrite relative import
9+
["./a.ts", "./a.js"],
10+
["./a.cts", "./a.cjs"],
11+
["./a.mts", "./a.mjs"],
12+
["./a.tsx", "./a.js"],
13+
14+
// rewrite relative import from parent directory
15+
["../a.ts", "../a.js"],
16+
["../a.cts", "../a.cjs"],
17+
["../a.mts", "../a.mjs"],
18+
["../a.tsx", "../a.js"],
19+
20+
// rewrite transforms uppercase to lowercase
21+
["./a.Ts", "./a.js"],
22+
["./a.CTS", "./a.cjs"],
23+
["./a.MTs", "./a.mjs"],
24+
["./a.tSx", "./a.js"],
25+
26+
// keep .d.css.ts
27+
["./.d.css.ts", "./.d.css.ts"],
28+
// rewrite relative import containing .d
29+
["./.d.css.cts", "./.d.css.cjs"],
30+
["./.d.css.mts", "./.d.css.mjs"],
31+
["./.d.css.tsx", "./.d.css.js"],
32+
33+
// rewrite relative import starting with .d
34+
["./.dcss.ts", "./.dcss.js"],
35+
["./.dcss.cts", "./.dcss.cjs"],
36+
["./.dcss.mts", "./.dcss.mjs"],
37+
["./.dcss.tsx", "./.dcss.js"],
38+
39+
// rewrite relative import containing .d and 3 extra extensions
40+
["./.d.a.css.ts", "./.d.a.css.js"],
41+
["./.d.a.css.cts", "./.d.a.css.cjs"],
42+
["./.d.a.css.mts", "./.d.a.css.mjs"],
43+
["./.d.a.css.tsx", "./.d.a.css.js"],
44+
45+
// rewrite relative import directory containing .d
46+
["./.d/.css.ts", "./.d/.css.js"],
47+
["./.d/.css.cts", "./.d/.css.cjs"],
48+
["./.d/.css.mts", "./.d/.css.mjs"],
49+
["./.d/.css.tsx", "./.d/.css.js"],
50+
51+
// skip d.ts, d.cts, and d.mts
52+
["./a.d.ts", "./a.d.ts"],
53+
["./a.d.cts", "./a.d.cts"],
54+
["./a.d.mts", "./a.d.mts"],
55+
// transform d.tsx
56+
["./a.d.tsx", "./a.d.js"],
57+
58+
// skip bare .d.ts, .d.cts, and .d.mts
59+
["./.d.ts", "./.d.ts"],
60+
["./.d.cts", "./.d.cts"],
61+
["./.d.mts", "./.d.mts"],
62+
// transform bare .d.tsx
63+
["./.d.tsx", "./.d.js"],
64+
65+
// skip absolute
66+
["/a.ts", "/a.ts"],
67+
["/a.cts", "/a.cts"],
68+
["/a.mts", "/a.mts"],
69+
["/a.tsx", "/a.tsx"],
70+
71+
// skip library
72+
["a.ts", "a.ts"],
73+
["a.cts", "a.cts"],
74+
["a.mts", "a.mts"],
75+
["a.tsx", "a.tsx"],
76+
77+
// skip dot-leading library
78+
[".a.ts", ".a.ts"],
79+
[".a.cts", ".a.cts"],
80+
[".a.mts", ".a.mts"],
81+
[".a.tsx", ".a.tsx"],
82+
83+
// skip non-terminal
84+
["./a.ts.foo", "./a.ts.foo"],
85+
["./a.cts.foo", "./a.cts.foo"],
86+
["./a.mts.foo", "./a.mts.foo"],
87+
["./a.tsx.foo", "./a.tsx.foo"],
88+
89+
// skip import path with windows path separator
90+
// Node.js ESM does not support them either
91+
[".\\a.ts", ".\\a.ts"],
92+
[".\\a.cts", ".\\a.cts"],
93+
[".\\a.mts", ".\\a.mts"],
94+
[".\\a.tsx", ".\\a.tsx"],
95+
])("%p -> %p", (input, result) => {
96+
expect(tsRewriteRelativeImportExtensions(input)).toBe(result);
97+
});
98+
99+
it.each([
100+
// skip string wrapper object
101+
[new String("./a.ts")],
102+
[new String("./a.cts")],
103+
[new String("./a.mts")],
104+
[new String("./a.tsx")],
105+
106+
// skip toString() transforms
107+
[{ [Symbol.toStringTag]: "./a.ts" }],
108+
[{ [Symbol.toStringTag]: "./a.cts" }],
109+
[{ [Symbol.toStringTag]: "./a.mts" }],
110+
[{ [Symbol.toStringTag]: "./a.tsx" }],
111+
112+
// skip toPrimitive() calls
113+
[{ [Symbol.toPrimitive]: () => "./a.ts" }],
114+
[{ [Symbol.toPrimitive]: () => "./a.cts" }],
115+
[{ [Symbol.toPrimitive]: () => "./a.mts" }],
116+
[{ [Symbol.toPrimitive]: () => "./a.tsx" }],
117+
])("should skip %p", input => {
118+
expect(tsRewriteRelativeImportExtensions(input)).toBe(input);
119+
});
120+
});

packages/babel-preset-typescript/src/plugin-rewrite-ts-imports.ts

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
11
import { declare } from "@babel/helper-plugin-utils";
2-
import type { types as t, NodePath } from "@babel/core";
2+
import type { types as t, NodePath, PluginPass } from "@babel/core";
33

44
export default declare(function ({ types: t, template }) {
55
function maybeReplace(
6-
source: t.ArgumentPlaceholder | t.SpreadElement | t.Expression,
6+
source: t.ArgumentPlaceholder | t.Expression,
77
path: NodePath,
8+
state: PluginPass,
89
) {
910
if (!source) return;
11+
// todo: if we want to support `preserveJsx`, we can register a global flag via file.set from transform-react-jsx, and read it here.
12+
const preserveJsx = false;
1013
if (t.isStringLiteral(source)) {
11-
if (/[\\/]/.test(source.value)) {
12-
source.value = source.value
13-
.replace(/(\.[mc]?)ts$/, "$1js")
14-
.replace(/\.tsx$/, ".js");
14+
if (/^\.\.?\//.test(source.value)) {
15+
// @see packages/babel-helpers/src/helpers/tsRewriteRelativeImportExtensions.ts
16+
source.value = source.value.replace(
17+
/\.(tsx)$|((?:\.d)?)((?:\.[^./]+)?)\.([cm]?)ts$/i,
18+
function (m, tsx, d, ext, cm) {
19+
return tsx
20+
? preserveJsx
21+
? ".jsx"
22+
: ".js"
23+
: d && (!ext || !cm)
24+
? m
25+
: d + ext + "." + cm.toLowerCase() + "js";
26+
},
27+
);
1528
}
1629
return;
1730
}
1831

19-
path.replaceWith(
20-
template.expression
21-
.ast`(${source} + "").replace(/([\\/].*\.[mc]?)tsx?$/, "$1js")`,
22-
);
32+
if (
33+
process.env.BABEL_8_BREAKING ||
34+
state.availableHelper("tsRewriteRelativeImportExtensions")
35+
) {
36+
path.replaceWith(
37+
t.callExpression(
38+
state.addHelper("tsRewriteRelativeImportExtensions"),
39+
preserveJsx ? [source, t.booleanLiteral(true)] : [source],
40+
),
41+
);
42+
} else {
43+
path.replaceWith(
44+
template.expression
45+
.ast`(${source} + "").replace(/([\\/].*\.[mc]?)tsx?$/, "$1js")`,
46+
);
47+
}
2348
}
2449

2550
return {
@@ -31,22 +56,28 @@ export default declare(function ({ types: t, template }) {
3156
| t.ExportAllDeclaration
3257
| t.ExportNamedDeclaration
3358
>,
59+
state,
3460
) {
3561
const node = path.node;
3662
const kind = t.isImportDeclaration(node)
3763
? node.importKind
3864
: node.exportKind;
3965
if (kind === "value") {
40-
maybeReplace(node.source, path.get("source"));
66+
maybeReplace(node.source, path.get("source"), state);
4167
}
4268
},
43-
CallExpression(path) {
44-
if (t.isImport(path.node.callee)) {
45-
maybeReplace(path.node.arguments[0], path.get("arguments.0"));
69+
CallExpression(path, state) {
70+
if (!process.env.BABEL_8_BREAKING && t.isImport(path.node.callee)) {
71+
maybeReplace(
72+
// The argument of import must not be a spread element
73+
path.node.arguments[0] as t.ArgumentPlaceholder | t.Expression,
74+
path.get("arguments.0"),
75+
state,
76+
);
4677
}
4778
},
48-
ImportExpression(path) {
49-
maybeReplace(path.node.source, path.get("source"));
79+
ImportExpression(path, state) {
80+
maybeReplace(path.node.source, path.get("source"), state);
5081
},
5182
},
5283
};
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
import("./a.js");
2-
import((a + "").replace(/([\/].*.[mc]?)tsx?$/, "$1js"));
2+
import(babelHelpers.tsRewriteRelativeImportExtensions(a));

packages/babel-preset-typescript/test/fixtures/opts/rewriteImportExtensions/output.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import "./react.js";
55
// .mtsx and .ctsx are not valid and should not be transformed.
66
import "./react.mtsx";
77
import "./react.ctsx";
8-
import "a-package/file.js";
8+
import "a-package/file.ts";
99
// Bare import, it's either a node package or remapped by an import map
1010
import "soundcloud.ts";
1111
export * from "./a.js";
1212
export { x } from "./a.mjs";
1313
import("./a.js");
14-
import((a + "").replace(/([\/].*.[mc]?)tsx?$/, "$1js"));
14+
import(babelHelpers.tsRewriteRelativeImportExtensions(a));

packages/babel-runtime-corejs2/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,15 @@
756756
"./helpers/toSetter.js"
757757
],
758758
"./helpers/esm/toSetter": "./helpers/esm/toSetter.js",
759+
"./helpers/tsRewriteRelativeImportExtensions": [
760+
{
761+
"node": "./helpers/tsRewriteRelativeImportExtensions.js",
762+
"import": "./helpers/esm/tsRewriteRelativeImportExtensions.js",
763+
"default": "./helpers/tsRewriteRelativeImportExtensions.js"
764+
},
765+
"./helpers/tsRewriteRelativeImportExtensions.js"
766+
],
767+
"./helpers/esm/tsRewriteRelativeImportExtensions": "./helpers/esm/tsRewriteRelativeImportExtensions.js",
759768
"./helpers/typeof": [
760769
{
761770
"node": "./helpers/typeof.js",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function tsRewriteRelativeImportExtensions(t, e) {
2+
return "string" == typeof t && /^\.\.?\//.test(t) ? t.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+)?)\.([cm]?)ts$/i, function (t, s, r, n, o) {
3+
return s ? e ? ".jsx" : ".js" : !r || n && o ? r + n + "." + o.toLowerCase() + "js" : t;
4+
}) : t;
5+
}
6+
export { tsRewriteRelativeImportExtensions as default };

packages/babel-runtime-corejs3/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,15 @@
755755
"./helpers/toSetter.js"
756756
],
757757
"./helpers/esm/toSetter": "./helpers/esm/toSetter.js",
758+
"./helpers/tsRewriteRelativeImportExtensions": [
759+
{
760+
"node": "./helpers/tsRewriteRelativeImportExtensions.js",
761+
"import": "./helpers/esm/tsRewriteRelativeImportExtensions.js",
762+
"default": "./helpers/tsRewriteRelativeImportExtensions.js"
763+
},
764+
"./helpers/tsRewriteRelativeImportExtensions.js"
765+
],
766+
"./helpers/esm/tsRewriteRelativeImportExtensions": "./helpers/esm/tsRewriteRelativeImportExtensions.js",
758767
"./helpers/typeof": [
759768
{
760769
"node": "./helpers/typeof.js",

packages/babel-runtime/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,15 @@
755755
"./helpers/toSetter.js"
756756
],
757757
"./helpers/esm/toSetter": "./helpers/esm/toSetter.js",
758+
"./helpers/tsRewriteRelativeImportExtensions": [
759+
{
760+
"node": "./helpers/tsRewriteRelativeImportExtensions.js",
761+
"import": "./helpers/esm/tsRewriteRelativeImportExtensions.js",
762+
"default": "./helpers/tsRewriteRelativeImportExtensions.js"
763+
},
764+
"./helpers/tsRewriteRelativeImportExtensions.js"
765+
],
766+
"./helpers/esm/tsRewriteRelativeImportExtensions": "./helpers/esm/tsRewriteRelativeImportExtensions.js",
758767
"./helpers/typeof": [
759768
{
760769
"node": "./helpers/typeof.js",

0 commit comments

Comments
 (0)