Skip to content

Commit 56f9e65

Browse files
committed
feat(jiti): Move away from ts-node, use jiti
1 parent 68403dd commit 56f9e65

11 files changed

+264
-93
lines changed

lib/index.spec.ts

+64-30
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from "node:path";
22

3-
import { cosmiconfig } from "cosmiconfig";
3+
import { cosmiconfig, cosmiconfigSync } from "cosmiconfig";
44

55
import { TypeScriptLoader } from ".";
66

@@ -14,45 +14,79 @@ describe("TypeScriptLoader", () => {
1414
});
1515

1616
describe("cosmiconfig", () => {
17-
it("should load a valid TS file", async () => {
18-
const cfg = cosmiconfig("test", {
19-
loaders: {
20-
".ts": TypeScriptLoader(),
21-
},
17+
describe("synchronous", () => {
18+
it("should load a valid TS file", () => {
19+
const cfg = cosmiconfigSync("test", {
20+
loaders: {
21+
".ts": TypeScriptLoader(),
22+
},
23+
});
24+
const loadedCfg = cfg.load(
25+
path.resolve(fixturesPath, "valid.fixture.ts")
26+
);
27+
28+
expect(typeof loadedCfg!.config).toStrictEqual("object");
29+
expect(typeof loadedCfg!.config.test).toStrictEqual("object");
30+
expect(loadedCfg!.config.test.cake).toStrictEqual("a lie");
2231
});
23-
const loadedCfg = await cfg.load(
24-
path.resolve(fixturesPath, "valid.fixture.ts")
25-
);
2632

27-
expect(typeof loadedCfg!.config).toStrictEqual("object");
28-
expect(typeof loadedCfg!.config.test).toStrictEqual("object");
29-
expect(loadedCfg!.config.test.cake).toStrictEqual("a lie");
33+
it("should throw an error on loading an invalid TS file", () => {
34+
const cfg = cosmiconfigSync("test", {
35+
loaders: {
36+
".ts": TypeScriptLoader(),
37+
},
38+
});
39+
40+
try {
41+
cfg.load(path.resolve(fixturesPath, "invalid.fixture.ts"));
42+
fail("Should fail to load invalid TS");
43+
} catch (error: any) {
44+
expect(error?.name).toStrictEqual("TypeScriptCompileError");
45+
}
46+
});
3047
});
3148

32-
it("should throw an error on loading an invalid TS file", async () => {
33-
const cfg = cosmiconfig("test", {
34-
loaders: {
35-
".ts": TypeScriptLoader(),
36-
},
49+
describe("asynchronous", () => {
50+
it("should load a valid TS file", async () => {
51+
const cfg = cosmiconfig("test", {
52+
loaders: {
53+
".ts": TypeScriptLoader(),
54+
},
55+
});
56+
const loadedCfg = await cfg.load(
57+
path.resolve(fixturesPath, "valid.fixture.ts")
58+
);
59+
60+
expect(typeof loadedCfg!.config).toStrictEqual("object");
61+
expect(typeof loadedCfg!.config.test).toStrictEqual("object");
62+
expect(loadedCfg!.config.test.cake).toStrictEqual("a lie");
3763
});
3864

39-
try {
40-
await cfg.load(path.resolve(fixturesPath, "invalid.fixture.ts"));
41-
fail("Should fail to load invalid TS");
42-
} catch (error: any) {
43-
expect(error?.name).toStrictEqual("TypeScriptCompileError");
44-
}
65+
it("should throw an error on loading an invalid TS file", async () => {
66+
const cfg = cosmiconfig("test", {
67+
loaders: {
68+
".ts": TypeScriptLoader(),
69+
},
70+
});
71+
72+
try {
73+
await cfg.load(path.resolve(fixturesPath, "invalid.fixture.ts"));
74+
fail("Should fail to load invalid TS");
75+
} catch (error: any) {
76+
expect(error?.name).toStrictEqual("TypeScriptCompileError");
77+
}
78+
});
4579
});
4680
});
4781

4882
describe("cosmiconfigSync", () => {
49-
it("should load a valid TS file", async () => {
50-
const cfg = cosmiconfig("test", {
83+
it("should load a valid TS file", () => {
84+
const cfg = cosmiconfigSync("test", {
5185
loaders: {
5286
".ts": TypeScriptLoader(),
5387
},
5488
});
55-
const loadedCfg = await cfg.load(
89+
const loadedCfg = cfg.load(
5690
path.resolve(fixturesPath, "valid.fixture.ts")
5791
);
5892

@@ -61,16 +95,16 @@ describe("TypeScriptLoader", () => {
6195
expect(loadedCfg!.config.test.cake).toStrictEqual("a lie");
6296
});
6397

64-
it("should throw an error on loading an invalid TS file", async () => {
65-
const cfg = cosmiconfig("test", {
98+
it("should throw an error on loading an invalid TS file", () => {
99+
const cfg = cosmiconfigSync("test", {
66100
loaders: {
67101
".ts": TypeScriptLoader(),
68102
},
69103
});
70104

71-
await expect(() =>
105+
expect(() =>
72106
cfg.load(path.resolve(fixturesPath, "invalid.fixture.ts"))
73-
).rejects.toThrowError();
107+
).toThrowError();
74108
});
75109
});
76110
});

lib/loader.spec.ts

+26-23
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@ import fs from "node:fs";
22
import path from "node:path";
33

44
import { Loader } from "cosmiconfig";
5-
import * as tsnode from "ts-node";
5+
import * as jiti from "jiti";
66

77
import { TypeScriptLoader } from "./loader";
88
import { TypeScriptCompileError } from "./typescript-compile-error";
99

10+
// Handle jiti using `export default`
11+
jest.mock("jiti", () => {
12+
const actual = jest.requireActual("jiti");
13+
return {
14+
__esModule: true,
15+
default: jest.fn(actual),
16+
};
17+
});
18+
1019
describe("TypeScriptLoader", () => {
1120
const fixturesPath = path.resolve(__dirname, "__fixtures__");
12-
const tsNodeSpy = jest.spyOn(tsnode, "register");
21+
const jitiSpy = jest.spyOn(jiti, "default");
1322

1423
let loader: Loader;
1524

@@ -26,24 +35,22 @@ describe("TypeScriptLoader", () => {
2635
loader(filePath, readFixtureContent(filePath));
2736
});
2837

29-
it("should fail on parsing an invalid TS file", async () => {
38+
it("should fail on parsing an invalid TS file", () => {
3039
const filePath = path.resolve(fixturesPath, "invalid.fixture.ts");
31-
await expect(() =>
32-
loader(filePath, readFixtureContent(filePath))
33-
).rejects.toThrowError();
40+
expect(() => loader(filePath, readFixtureContent(filePath))).toThrowError();
3441
});
3542

36-
it("should use the same instance of ts-node across multiple calls", () => {
43+
it("should use the same instance of jiti across multiple calls", () => {
3744
const filePath = path.resolve(fixturesPath, "valid.fixture.ts");
3845
loader(filePath, readFixtureContent(filePath));
3946
loader(filePath, readFixtureContent(filePath));
40-
expect(tsNodeSpy).toHaveBeenCalledTimes(1);
47+
expect(jitiSpy).toHaveBeenCalledTimes(1);
4148
});
4249

43-
it("should throw a TypeScriptCompileError on error", async () => {
50+
it("should throw a TypeScriptCompileError on error", () => {
4451
try {
4552
const filePath = path.resolve(fixturesPath, "invalid.fixture.ts");
46-
await loader(filePath, readFixtureContent(filePath));
53+
loader(filePath, readFixtureContent(filePath));
4754
fail(
4855
"Error should be thrown upon failing to transpile an invalid TS file."
4956
);
@@ -52,31 +59,27 @@ describe("TypeScriptLoader", () => {
5259
}
5360
});
5461

55-
describe("ts-node", () => {
62+
describe("jiti", () => {
5663
const unknownError = "Test Error";
5764

58-
let stub: jest.SpyInstance<tsnode.Service, [service: tsnode.Service]>;
65+
let stub: jest.SpyInstance;
5966

6067
beforeEach(() => {
61-
stub = jest.spyOn(tsnode, "register").mockImplementation(
62-
() =>
63-
({
64-
compile: (): string => {
65-
// eslint-disable-next-line @typescript-eslint/no-throw-literal
66-
throw unknownError;
67-
},
68-
} as any)
69-
);
68+
stub = jest.spyOn(jiti, "default").mockImplementation((() => () => {
69+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
70+
throw unknownError;
71+
}) as any);
72+
7073
loader = TypeScriptLoader();
7174
});
7275

7376
afterEach(() => {
7477
stub.mockRestore();
7578
});
7679

77-
it("rethrows an error if it is not an instance of Error", async () => {
80+
it("rethrows an error if it is not an instance of Error", () => {
7881
try {
79-
await loader("filePath", "readFixtureContent(filePath)");
82+
loader("filePath", "readFixtureContent(filePath)");
8083
fail(
8184
"Error should be thrown upon failing to transpile an invalid TS file."
8285
);

lib/loader.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import type { Loader } from "cosmiconfig";
2-
import { register, RegisterOptions } from "ts-node";
2+
import jiti, { type JITIOptions } from "jiti";
33

44
import { TypeScriptCompileError } from "./typescript-compile-error.js";
55

6-
export function TypeScriptLoader(options?: RegisterOptions): Loader {
7-
const tsNodeInstance = register(options);
8-
return async (path: string, content: string) => {
6+
export function TypeScriptLoader(options?: JITIOptions): Loader {
7+
const loader = jiti("", { interopDefault: true, ...options });
8+
return (path: string) => {
99
try {
10-
// cosmiconfig requires the transpiled configuration to be CJS
11-
tsNodeInstance.compile(content, path);
12-
const result = await import(path);
10+
const result = loader(path);
1311

1412
// `default` is used when exporting using export default, some modules
1513
// may still use `module.exports` or if in TS `export = `

lib/typescript-compile-error.spec.ts

+5-12
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,14 @@ describe("TypeScriptCompileError", () => {
1717
expect(tscError.stack).toBe(testError.stack);
1818
});
1919

20-
it("should replace the legacy tsc error string", () => {
21-
const testMsg =
22-
"TypeScript compiler encountered syntax errors while transpiling. Errors: ";
20+
it("should prefix the jiti parser error", () => {
21+
const testMsg = 'ParseError: Unexpected token, expected ","';
2322
const legacyError = new Error(testMsg);
2423
const tscError = TypeScriptCompileError.fromError(legacyError);
2524

26-
expect(tscError).not.toContainEqual(testMsg);
27-
});
28-
29-
it("should replace the tsc error string", () => {
30-
const testMsg = "⨯ Unable to compile TypeScript:";
31-
const newError = new Error(testMsg);
32-
const tscError = TypeScriptCompileError.fromError(newError);
33-
34-
expect(tscError).not.toContainEqual(testMsg);
25+
expect(tscError.message).toContain(
26+
"TypeScriptLoader failed to compile TypeScript:"
27+
);
3528
});
3629
});
3730
});

lib/typescript-compile-error.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,7 @@ export class TypeScriptCompileError extends Error {
88
}
99

1010
public static fromError(error: Error): TypeScriptCompileError {
11-
const errMsg = error.message.replace(
12-
/(TypeScript compiler encountered syntax errors while transpiling\. Errors:\s?)|( Unable to compile TypeScript:\s?)/,
13-
""
14-
);
15-
16-
const message = `TypeScriptLoader failed to compile TypeScript:\n${errMsg}`;
11+
const message = `TypeScriptLoader failed to compile TypeScript:\n${error.message}`;
1712

1813
const newError = new TypeScriptCompileError(message);
1914
newError.stack = error.stack;

0 commit comments

Comments
 (0)