Skip to content

Commit 9c09fcd

Browse files
hardfistCopilot
andauthored
feat(cli): use SWC to do TS config transformamation (#11411)
* feat: use swc to do ts transform * chore: fix require.extensions * Update packages/rspack-cli/src/utils/loadConfig.ts Co-authored-by: Copilot <[email protected]> * chore: fix type * chore: disable noImplicitAnyLet rule * chore: use pirate instead * chore: add more tests * chore: add more error friendly message * chore: support sourcemap * chore: remove rechoir link --------- Co-authored-by: Copilot <[email protected]>
1 parent 0ef053b commit 9c09fcd

File tree

10 files changed

+146
-79
lines changed

10 files changed

+146
-79
lines changed

biome.jsonc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
"allowTrailingCommas": true
1010
}
1111
},
12-
"assist": { "actions": { "source": { "organizeImports": "on" } } },
12+
"assist": {
13+
"actions": {
14+
"source": {
15+
"organizeImports": "on"
16+
}
17+
}
18+
},
1319
"linter": {
1420
"enabled": true,
1521
"rules": {
@@ -42,7 +48,8 @@
4248
"noPrototypeBuiltins": "off",
4349
"noAssignInExpressions": "off",
4450
"noControlCharactersInRegex": "off",
45-
"noExplicitAny": "off"
51+
"noExplicitAny": "off",
52+
"noImplicitAnyLet": "off"
4653
}
4754
}
4855
},
@@ -71,4 +78,4 @@
7178
"clientKind": "git",
7279
"useIgnoreFile": true
7380
}
74-
}
81+
}

crates/rspack_binding_api/src/swc.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ fn _transform(source: String, options: String) -> napi::Result<TransformOutput>
5757
compiler
5858
.transform(
5959
source,
60-
Some(swc_core::common::FileName::Anon),
60+
Some(swc_core::common::FileName::Real(
61+
options.filename.clone().into(),
62+
)),
6163
options,
6264
Some(module_source_map_kind),
6365
|_| {},

packages/rspack-cli/package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,13 @@
3737
"@rspack/dev-server": "~1.1.3",
3838
"colorette": "2.0.20",
3939
"exit-hook": "^4.0.0",
40-
"interpret": "^3.1.1",
41-
"rechoir": "^0.8.0",
4240
"webpack-bundle-analyzer": "4.10.2",
43-
"yargs": "17.7.2"
41+
"yargs": "17.7.2",
42+
"pirates": "^4.0.7"
4443
},
4544
"devDependencies": {
4645
"@rslib/core": "0.12.1",
4746
"@rspack/core": "workspace:*",
48-
"@types/interpret": "^1.1.3",
49-
"@types/rechoir": "^0.6.4",
5047
"@types/webpack-bundle-analyzer": "^4.7.0",
5148
"@types/yargs": "17.0.33",
5249
"concat-stream": "^2.0.0",
@@ -62,4 +59,4 @@
6259
"access": "public",
6360
"provenance": true
6461
}
65-
}
62+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import path from "node:path";
2-
2+
export const TS_EXTENSION = [".ts", ".cts", ".mts"];
33
const isTsFile = (configPath: string) => {
44
const ext = path.extname(configPath);
5-
return /\.(c|m)?ts$/.test(ext);
5+
return TS_EXTENSION.includes(ext);
66
};
77

88
export default isTsFile;

packages/rspack-cli/src/utils/loadConfig.ts

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,77 @@
11
import fs from "node:fs";
22
import path from "node:path";
33
import {
4+
experiments,
45
type MultiRspackOptions,
56
type RspackOptions,
67
util
78
} from "@rspack/core";
9+
import { addHook } from "pirates";
810
import type { RspackCLIOptions } from "../types";
911
import { crossImport } from "./crossImport";
1012
import findConfig from "./findConfig";
1113
import isEsmFile from "./isEsmFile";
12-
import isTsFile from "./isTsFile";
14+
import isTsFile, { TS_EXTENSION } from "./isTsFile";
1315

14-
interface RechoirError extends Error {
15-
failures: RechoirError[];
16-
error: Error;
16+
const injectInlineSourceMap = ({
17+
filename,
18+
code,
19+
map
20+
}: {
21+
filename: string;
22+
code: string;
23+
map: string | undefined;
24+
}): string => {
25+
if (map) {
26+
const base64Map = Buffer.from(map, "utf8").toString("base64");
27+
const sourceMapContent = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`;
28+
return `${code}\n${sourceMapContent}`;
29+
}
30+
return code;
31+
};
32+
export function compile(sourcecode: string, filename: string) {
33+
const { code, map } = experiments.swc.transformSync(sourcecode, {
34+
jsc: {
35+
parser: {
36+
syntax: "typescript",
37+
tsx: false,
38+
decorators: true,
39+
dynamicImport: true
40+
}
41+
},
42+
filename: filename,
43+
module: { type: "commonjs" },
44+
sourceMaps: true,
45+
isModule: true
46+
});
47+
return injectInlineSourceMap({ filename, code, map });
1748
}
18-
1949
const DEFAULT_CONFIG_NAME = "rspack.config" as const;
20-
21-
const registerLoader = async (configPath: string) => {
22-
const ext = path.extname(configPath);
23-
// TODO implement good `.mts` support after https://github.com/gulpjs/rechoir/issues/43
50+
// modified based on https://github.com/swc-project/swc-node/blob/master/packages/register/register.ts#L117
51+
const registerLoader = (configPath: string) => {
2452
// For ESM and `.mts` you need to use: 'NODE_OPTIONS="--loader ts-node/esm" rspack build --config ./rspack.config.mts'
2553
if (isEsmFile(configPath) && isTsFile(configPath)) {
2654
return;
2755
}
2856

29-
const { default: interpret } = await import("interpret");
30-
const extensions = Object.fromEntries(
31-
Object.entries(interpret.extensions).filter(([key]) => key === ext)
32-
);
33-
if (Object.keys(extensions).length === 0) {
57+
// Only support TypeScript files with a CommonJS loader here
58+
if (!isTsFile(configPath)) {
3459
throw new Error(`config file "${configPath}" is not supported.`);
3560
}
36-
37-
try {
38-
const { default: rechoir } = await import("rechoir");
39-
rechoir.prepare(extensions, configPath);
40-
} catch (error) {
41-
const failures = (error as RechoirError)?.failures;
42-
if (failures) {
43-
const messages = failures.map(failure => failure.error.message);
44-
throw new Error(`${messages.join("\n")}`);
61+
addHook(
62+
(code, filename) => {
63+
try {
64+
return compile(code, filename);
65+
} catch (err) {
66+
throw new Error(
67+
`Failed to transform file "${filename}" when loading TypeScript config file:\n ${err instanceof Error ? err.message : String(err)}`
68+
);
69+
}
70+
},
71+
{
72+
exts: TS_EXTENSION
4573
}
46-
throw error;
47-
}
74+
);
4875
};
4976

5077
export type LoadedRspackConfig =
@@ -186,7 +213,7 @@ export async function loadExtendedConfig(
186213

187214
// Register loader for TypeScript files
188215
if (isTsFile(resolvedPath) && options.configLoader === "register") {
189-
await registerLoader(resolvedPath);
216+
registerLoader(resolvedPath);
190217
}
191218

192219
// Load the extended configuration
@@ -241,7 +268,7 @@ export async function loadRspackConfig(
241268

242269
// load config
243270
if (isTsFile(configPath) && options.configLoader === "register") {
244-
await registerLoader(configPath);
271+
registerLoader(configPath);
245272
}
246273
const loadedConfig = await crossImport(configPath, cwd);
247274

packages/rspack-cli/tests/build/config/config.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,39 @@ describe("rspack cli", () => {
260260
).resolves.toMatch(/Main monorepo file/);
261261
});
262262
});
263+
describe("should support loader ts-node register", () => {
264+
const cwd = resolve(__dirname, "./ts-node-register");
265+
it("should load ts-node-register to support declare const enum", async () => {
266+
const { exitCode, stdout, stderr } = await run(
267+
cwd,
268+
["-c", "rspack.config.ts"],
269+
{
270+
nodeOptions: ["--require", "ts-node/register"]
271+
}
272+
);
273+
expect(stdout).toBeTruthy();
274+
expect(stderr).toBeFalsy();
275+
expect(exitCode).toBe(0);
276+
expect(
277+
readFile(resolve(cwd, `./dist/node-register.bundle.js`), {
278+
encoding: "utf-8"
279+
})
280+
).resolves.toMatch(/Main ts-node-register file: 42/);
281+
});
282+
it("builtin swc-register can't handle declare const enum", async () => {
283+
const { exitCode, stdout, stderr } = await run(
284+
cwd,
285+
["-c", "rspack.config.ts"],
286+
{
287+
nodeOptions: []
288+
}
289+
);
290+
expect(stdout).toBeFalsy();
291+
expect(stderr).toBeTruthy();
292+
expect(stderr).toMatch(/ReferenceError: JSB is not defined/);
293+
expect(exitCode).toBe(1);
294+
});
295+
});
263296

264297
// describe("loose-unrecognized-keys (default)", () => {
265298
// const cwd = resolve(__dirname, "./loose-unrecognized-keys");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log(`Main ts-node-register file: ${ANSWER}`);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import path from "path";
2+
import rspack from "@rspack/core";
3+
declare const enum JSB {
4+
SECRET = 42
5+
}
6+
export default {
7+
mode: "production",
8+
entry: path.resolve(__dirname, "main.ts"),
9+
10+
output: {
11+
path: path.resolve(__dirname, "dist"),
12+
filename: "node-register.bundle.js"
13+
},
14+
plugins: [
15+
new rspack.DefinePlugin({
16+
ANSWER: JSB.SECRET
17+
})
18+
]
19+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"rootDir": ".",
5+
"target": "ESNext",
6+
"module": "commonjs",
7+
"noEmit": true,
8+
"moduleResolution": "node",
9+
"esModuleInterop": true,
10+
"types": [
11+
"./namespace.d.ts"
12+
]
13+
},
14+
"include": [
15+
"**/*.ts",
16+
"**/*.js"
17+
]
18+
}

pnpm-lock.yaml

Lines changed: 3 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)