Skip to content

Commit 38f25f1

Browse files
committed
refactor-extract-backend
1 parent 28a3e7d commit 38f25f1

File tree

14 files changed

+237
-189
lines changed

14 files changed

+237
-189
lines changed

src/backend/esbuild/eval.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export const createEsbuildEvalTransformSync = (esbuild: typeof import('esbuild')) => {
2+
return function evalTransformSync(evalCode: string, inputType?: string) {
3+
return esbuild.transformSync(
4+
evalCode,
5+
{
6+
loader: 'default',
7+
sourcefile: '/eval.ts',
8+
format: inputType === 'module' ? 'esm' : 'cjs',
9+
},
10+
).code
11+
}
12+
}

src/backend/esbuild/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { TransformOptions } from 'esbuild';
2+
import type { Backend } from '..';
3+
import { createEsbuildEvalTransformSync } from './eval';
4+
import { createEsbuildReplTransform } from './repl';
5+
import { createEsbuildTransformSync, createEsbuildTransform } from './transform';
6+
7+
export default function getEsbuildBackend(esbuild: typeof import('esbuild')): Backend<TransformOptions> {
8+
return {
9+
evalTransformSync: createEsbuildEvalTransformSync(esbuild),
10+
replTransform: createEsbuildReplTransform(esbuild),
11+
transformSync: createEsbuildTransformSync(esbuild),
12+
transform: createEsbuildTransform(esbuild),
13+
}
14+
}

src/backend/esbuild/repl.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const createEsbuildReplTransform = (esbuild: typeof import('esbuild')) => {
2+
return async function replTransform (code: string, filename: string): Promise<string> {
3+
return (await esbuild.transform(
4+
code,
5+
{
6+
sourcefile: filename,
7+
loader: 'ts',
8+
tsconfigRaw: {
9+
compilerOptions: {
10+
preserveValueImports: true,
11+
},
12+
},
13+
define: {
14+
require: 'global.require',
15+
},
16+
},
17+
)).code
18+
}
19+
}

src/backend/esbuild/transform.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { pathToFileURL } from 'node:url';
2+
import {
3+
type TransformOptions,
4+
type TransformFailure,
5+
} from 'esbuild';
6+
import { sha1 } from '../../utils/sha1.js';
7+
import {
8+
version as transformDynamicImportVersion,
9+
transformDynamicImport,
10+
} from '../../utils/transform/transform-dynamic-import.js';
11+
import cache from '../../utils/transform/cache.js';
12+
import {
13+
applyTransformersSync,
14+
applyTransformers,
15+
type Transformed,
16+
} from '../../utils/transform/apply-transformers.js';
17+
import {
18+
cacheConfig,
19+
patchOptions,
20+
} from './get-esbuild-options.js';
21+
22+
const formatEsbuildError = (
23+
error: TransformFailure,
24+
) => {
25+
error.name = 'TransformError';
26+
// @ts-expect-error deleting non-option property
27+
delete error.errors;
28+
// @ts-expect-error deleting non-option property
29+
delete error.warnings;
30+
throw error;
31+
};
32+
33+
export const createEsbuildTransformSync = (esbuild: typeof import('esbuild')) => {
34+
// Used by cjs-loader
35+
return function transformSync(
36+
code: string,
37+
filePath: string,
38+
extendOptions?: TransformOptions,
39+
): Transformed {
40+
const [filePathWithoutQuery, query] = filePath.split('?');
41+
const define: { [key: string]: string } = {};
42+
43+
if (!(filePathWithoutQuery.endsWith('.cjs') || filePathWithoutQuery.endsWith('.cts'))) {
44+
define['import.meta.url'] = JSON.stringify(pathToFileURL(filePathWithoutQuery) + (query ? `?${query}` : ''));
45+
}
46+
47+
const esbuildOptions = {
48+
...cacheConfig,
49+
format: 'cjs',
50+
sourcefile: filePathWithoutQuery,
51+
define,
52+
banner: '(()=>{',
53+
footer: '})()',
54+
55+
// CJS Annotations for Node. Used by ESM loader for CJS interop
56+
platform: 'node',
57+
58+
...extendOptions,
59+
} as const;
60+
61+
const hash = sha1([
62+
code,
63+
JSON.stringify(esbuildOptions),
64+
esbuild.version,
65+
transformDynamicImportVersion,
66+
].join('-'));
67+
let transformed = cache.get(hash);
68+
69+
if (!transformed) {
70+
transformed = applyTransformersSync(
71+
filePath,
72+
code,
73+
[
74+
(_filePath, _code) => {
75+
const patchResult = patchOptions(esbuildOptions);
76+
let result;
77+
try {
78+
result = esbuild.transformSync(_code, esbuildOptions);
79+
} catch (error) {
80+
throw formatEsbuildError(error as TransformFailure);
81+
}
82+
return patchResult(result);
83+
},
84+
(_filePath, _code) => transformDynamicImport(_filePath, _code, true),
85+
],
86+
);
87+
88+
cache.set(hash, transformed);
89+
}
90+
91+
return transformed;
92+
};
93+
};
94+
95+
export const createEsbuildTransform = (esbuild: typeof import('esbuild')) => {
96+
// Used by esm-loader
97+
return async function transform(
98+
code: string,
99+
filePath: string,
100+
extendOptions?: TransformOptions,
101+
): Promise<Transformed> {
102+
const esbuildOptions = {
103+
...cacheConfig,
104+
format: 'esm',
105+
sourcefile: filePath,
106+
...extendOptions,
107+
} as const;
108+
109+
const hash = sha1([
110+
code,
111+
JSON.stringify(esbuildOptions),
112+
esbuild.version,
113+
transformDynamicImportVersion,
114+
].join('-'));
115+
let transformed = cache.get(hash);
116+
117+
if (!transformed) {
118+
transformed = await applyTransformers(
119+
filePath,
120+
code,
121+
[
122+
async (_filePath, _code) => {
123+
const patchResult = patchOptions(esbuildOptions);
124+
let result;
125+
try {
126+
result = await esbuild.transform(_code, esbuildOptions);
127+
} catch (error) {
128+
throw formatEsbuildError(error as TransformFailure);
129+
}
130+
return patchResult(result);
131+
},
132+
(_filePath, _code) => transformDynamicImport(_filePath, _code, true),
133+
],
134+
);
135+
136+
cache.set(hash, transformed);
137+
}
138+
139+
return transformed;
140+
};
141+
}

src/backend/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { Transformed } from '../utils/transform/apply-transformers';
2+
3+
import esbuild from 'esbuild';
4+
5+
import getEsbuildBackend from './esbuild';
6+
7+
export interface Backend<ExtendedTransformOptions> {
8+
evalTransformSync: (evalCode: string, inputType?: string) => string;
9+
replTransform: (code: string, filename: string) => Promise<string>;
10+
transformSync: (
11+
code: string,
12+
filePath: string,
13+
extendOptions?: ExtendedTransformOptions,
14+
) => Transformed,
15+
transform: (
16+
code: string,
17+
filePath: string,
18+
extendOptions?: ExtendedTransformOptions,
19+
) => Promise<Transformed>,
20+
}
21+
22+
const backend = getEsbuildBackend(esbuild);
23+
24+
export default backend;

src/cjs/api/module-extensions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs from 'node:fs';
22
import path from 'node:path';
33
import Module from 'node:module';
44
import type { TransformOptions } from 'esbuild';
5-
import { transformSync } from '../../utils/transform/index.js';
5+
import backend from '../../backend/index.js';
66
import { transformDynamicImport } from '../../utils/transform/transform-dynamic-import.js';
77
import { isESM } from '../../utils/es-module-lexer.js';
88
import { shouldApplySourceMap, inlineSourceMap } from '../../source-map.js';
@@ -140,7 +140,7 @@ export const createExtensions = (
140140
// CommonJS file but uses ESM import/export
141141
|| isESM(code)
142142
) {
143-
const transformed = transformSync(
143+
const transformed = backend.transformSync(
144144
code,
145145
filePath,
146146
{

src/cli.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import { constants as osConstants } from 'node:os';
22
import type { ChildProcess, Serializable } from 'node:child_process';
33
import type { Server } from 'node:net';
44
import { cli } from 'cleye';
5-
import {
6-
transformSync as esbuildTransformSync,
7-
} from 'esbuild';
85
import { version } from '../package.json';
96
import { run } from './run.js';
107
import { watchCommand } from './watch/index.js';
@@ -14,6 +11,7 @@ import {
1411
} from './remove-argv-flags.js';
1512
import { isFeatureSupported, testRunnerGlob } from './utils/node-features.js';
1613
import { createIpcServer } from './utils/ipc/server.js';
14+
import backend from './backend';
1715

1816
// const debug = (...messages: any[]) => {
1917
// if (process.env.DEBUG) {
@@ -206,16 +204,12 @@ cli({
206204
if (evalType) {
207205
const { inputType } = interceptedFlags;
208206
const evalCode = interceptedFlags[evalType]!;
209-
const transformed = esbuildTransformSync(
207+
const transformed = backend.evalTransformSync(
210208
evalCode,
211-
{
212-
loader: 'default',
213-
sourcefile: '/eval.ts',
214-
format: inputType === 'module' ? 'esm' : 'cjs',
215-
},
209+
inputType,
216210
);
217211

218-
argvsToRun.unshift(`--${evalType}`, transformed.code);
212+
argvsToRun.unshift(`--${evalType}`, transformed);
219213
}
220214

221215
// Default --test glob to find TypeScript files

src/esm/hook/load.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { fileURLToPath } from 'node:url';
22
import path from 'node:path';
3-
import type { LoadHook } from 'node:module';
3+
import type { LoadHook, LoadHookContext } from 'node:module';
44
import { readFile } from 'node:fs/promises';
55
import type { TransformOptions } from 'esbuild';
6-
import { transform, transformSync } from '../../utils/transform/index.js';
6+
import backend from '../../backend/index.js';
77
import { transformDynamicImport } from '../../utils/transform/transform-dynamic-import.js';
88
import { inlineSourceMap } from '../../source-map.js';
99
import { isFeatureSupported, importAttributes, esmLoadReadFile } from '../../utils/node-features.js';
@@ -56,11 +56,8 @@ export const load: LoadHook = async (
5656
}
5757

5858
if (isJsonPattern.test(url)) {
59-
if (!context[contextAttributesProperty]) {
60-
context[contextAttributesProperty] = {};
61-
}
62-
63-
context[contextAttributesProperty]!.type = 'json';
59+
context[contextAttributesProperty as keyof LoadHookContext] ||= {} as any;
60+
(context[contextAttributesProperty as keyof LoadHookContext] as ImportAttributes).type = 'json';
6461
}
6562

6663
const loaded = await nextLoad(url, context);
@@ -91,7 +88,7 @@ export const load: LoadHook = async (
9188
* which are already in CJS syntax.
9289
* In CTS, module.exports can be written in any pattern.
9390
*/
94-
const transformed = transformSync(
91+
const transformed = backend.transformSync(
9592
code,
9693
filePath,
9794
{
@@ -118,7 +115,7 @@ export const load: LoadHook = async (
118115
loaded.format === 'json'
119116
|| tsExtensionsPattern.test(url)
120117
) {
121-
const transformed = await transform(
118+
const transformed = await backend.transform(
122119
code,
123120
filePath,
124121
{

src/patch-repl.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
import repl, { type REPLServer, type REPLEval } from 'node:repl';
2-
import { transform } from 'esbuild';
2+
import backend from './backend';
33

44
const patchEval = (nodeRepl: REPLServer) => {
55
const { eval: defaultEval } = nodeRepl;
66
const preEval: REPLEval = async function (code, context, filename, callback) {
77
try {
8-
const transformed = await transform(
9-
code,
10-
{
11-
sourcefile: filename,
12-
loader: 'ts',
13-
tsconfigRaw: {
14-
compilerOptions: {
15-
preserveValueImports: true,
16-
},
17-
},
18-
define: {
19-
require: 'global.require',
20-
},
21-
},
22-
);
23-
24-
code = transformed.code;
8+
code = await backend.replTransform(code, filename);
259
} catch {}
2610

2711
return defaultEval.call(this, code, context, filename, callback);

0 commit comments

Comments
 (0)