Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: extract backend #684

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/backend/esbuild/eval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const createEsbuildEvalTransformSync = (esbuild: typeof import('esbuild')) => (evalCode: string, inputType?: string) => esbuild.transformSync(
evalCode,
{
loader: 'default',
sourcefile: '/eval.ts',
format: inputType === 'module' ? 'esm' : 'cjs',
},
).code;
12 changes: 12 additions & 0 deletions src/backend/esbuild/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { TransformOptions } from 'esbuild';
import type { Backend } from '..';
import { createEsbuildEvalTransformSync } from './eval';
import { createEsbuildReplTransform } from './repl';
import { createEsbuildTransformSync, createEsbuildTransform } from './transform';

const getEsbuildBackend = (esbuild: typeof import('esbuild')): Backend<TransformOptions> => ({
evalTransformSync: createEsbuildEvalTransformSync(esbuild),
replTransform: createEsbuildReplTransform(esbuild),
transformSync: createEsbuildTransformSync(esbuild),
transform: createEsbuildTransform(esbuild),
}); export default getEsbuildBackend;
15 changes: 15 additions & 0 deletions src/backend/esbuild/repl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const createEsbuildReplTransform = (esbuild: typeof import('esbuild')) => async (code: string, filename: string): Promise<string> => esbuild.transform(
code,
{
sourcefile: filename,
loader: 'ts',
tsconfigRaw: {
compilerOptions: {
preserveValueImports: true,
},
},
define: {
require: 'global.require',
},
},
).then(result => result.code);
25 changes: 11 additions & 14 deletions src/utils/transform/index.ts → src/backend/esbuild/transform.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import { pathToFileURL } from 'node:url';
import {
transform as esbuildTransform,
transformSync as esbuildTransformSync,
version as esbuildVersion,
type TransformOptions,
type TransformFailure,
} from 'esbuild';
import { sha1 } from '../sha1.js';
import { sha1 } from '../../utils/sha1.js';
import {
version as transformDynamicImportVersion,
transformDynamicImport,
} from './transform-dynamic-import.js';
import cache from './cache.js';
} from '../../utils/transform/transform-dynamic-import.js';
import cache from '../../utils/transform/cache.js';
import {
applyTransformersSync,
applyTransformers,
type Transformed,
} from './apply-transformers.js';
} from '../../utils/transform/apply-transformers.js';
import {
cacheConfig,
patchOptions,
Expand All @@ -33,8 +30,8 @@ const formatEsbuildError = (
throw error;
};

// Used by cjs-loader
export const transformSync = (
// used by cjs-loader
export const createEsbuildTransformSync = (esbuild: typeof import('esbuild')) => (
code: string,
filePath: string,
extendOptions?: TransformOptions,
Expand Down Expand Up @@ -63,7 +60,7 @@ export const transformSync = (
const hash = sha1([
code,
JSON.stringify(esbuildOptions),
esbuildVersion,
esbuild.version,
transformDynamicImportVersion,
].join('-'));
let transformed = cache.get(hash);
Expand All @@ -77,7 +74,7 @@ export const transformSync = (
const patchResult = patchOptions(esbuildOptions);
let result;
try {
result = esbuildTransformSync(_code, esbuildOptions);
result = esbuild.transformSync(_code, esbuildOptions);
} catch (error) {
throw formatEsbuildError(error as TransformFailure);
}
Expand All @@ -94,7 +91,7 @@ export const transformSync = (
};

// Used by esm-loader
export const transform = async (
export const createEsbuildTransform = (esbuild: typeof import('esbuild')) => async (
code: string,
filePath: string,
extendOptions?: TransformOptions,
Expand All @@ -109,7 +106,7 @@ export const transform = async (
const hash = sha1([
code,
JSON.stringify(esbuildOptions),
esbuildVersion,
esbuild.version,
transformDynamicImportVersion,
].join('-'));
let transformed = cache.get(hash);
Expand All @@ -123,7 +120,7 @@ export const transform = async (
const patchResult = patchOptions(esbuildOptions);
let result;
try {
result = await esbuildTransform(_code, esbuildOptions);
result = await esbuild.transform(_code, esbuildOptions);
} catch (error) {
throw formatEsbuildError(error as TransformFailure);
}
Expand Down
23 changes: 23 additions & 0 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import esbuild from 'esbuild';
import type { Transformed } from '../utils/transform/apply-transformers';

import getEsbuildBackend from './esbuild';

export interface Backend<ExtendedTransformOptions> {
evalTransformSync: (evalCode: string, inputType?: string) => string;
replTransform: (code: string, filename: string) => Promise<string>;
transformSync: (
code: string,
filePath: string,
extendOptions?: ExtendedTransformOptions,
) => Transformed;
transform: (
code: string,
filePath: string,
extendOptions?: ExtendedTransformOptions,
) => Promise<Transformed>;
}

const backend = getEsbuildBackend(esbuild);

export default backend;
4 changes: 2 additions & 2 deletions src/cjs/api/module-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import Module from 'node:module';
import type { TransformOptions } from 'esbuild';
import { transformSync } from '../../utils/transform/index.js';
import backend from '../../backend/index.js';
import { transformDynamicImport } from '../../utils/transform/transform-dynamic-import.js';
import { isESM } from '../../utils/es-module-lexer.js';
import { shouldApplySourceMap, inlineSourceMap } from '../../source-map.js';
Expand Down Expand Up @@ -140,7 +140,7 @@ export const createExtensions = (
// CommonJS file but uses ESM import/export
|| isESM(code)
) {
const transformed = transformSync(
const transformed = backend.transformSync(
code,
filePath,
{
Expand Down
14 changes: 4 additions & 10 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { constants as osConstants } from 'node:os';
import type { ChildProcess, Serializable } from 'node:child_process';
import type { Server } from 'node:net';
import { cli } from 'cleye';
import {
transformSync as esbuildTransformSync,
} from 'esbuild';
import { version } from '../package.json';
import { run } from './run.js';
import { watchCommand } from './watch/index.js';
Expand All @@ -14,6 +11,7 @@ import {
} from './remove-argv-flags.js';
import { isFeatureSupported, testRunnerGlob } from './utils/node-features.js';
import { createIpcServer } from './utils/ipc/server.js';
import backend from './backend';

// const debug = (...messages: any[]) => {
// if (process.env.DEBUG) {
Expand Down Expand Up @@ -206,16 +204,12 @@ cli({
if (evalType) {
const { inputType } = interceptedFlags;
const evalCode = interceptedFlags[evalType]!;
const transformed = esbuildTransformSync(
const transformed = backend.evalTransformSync(
evalCode,
{
loader: 'default',
sourcefile: '/eval.ts',
format: inputType === 'module' ? 'esm' : 'cjs',
},
inputType,
);

argvsToRun.unshift(`--${evalType}`, transformed.code);
argvsToRun.unshift(`--${evalType}`, transformed);
}

// Default --test glob to find TypeScript files
Expand Down
17 changes: 8 additions & 9 deletions src/esm/hook/load.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import type { LoadHook } from 'node:module';
import type { LoadHook, LoadHookContext } from 'node:module';
import { readFile } from 'node:fs/promises';
import type { TransformOptions } from 'esbuild';
import { transform, transformSync } from '../../utils/transform/index.js';
import backend from '../../backend/index.js';
import { transformDynamicImport } from '../../utils/transform/transform-dynamic-import.js';
import { inlineSourceMap } from '../../source-map.js';
import { isFeatureSupported, importAttributes, esmLoadReadFile } from '../../utils/node-features.js';
Expand Down Expand Up @@ -56,11 +56,10 @@ export const load: LoadHook = async (
}

if (isJsonPattern.test(url)) {
if (!context[contextAttributesProperty]) {
context[contextAttributesProperty] = {};
}

context[contextAttributesProperty]!.type = 'json';
// @types/node only declares `importAttributes` type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context[contextAttributesProperty as keyof LoadHookContext] ||= {} as any;
(context[contextAttributesProperty as keyof LoadHookContext] as ImportAttributes).type = 'json';
}

const loaded = await nextLoad(url, context);
Expand Down Expand Up @@ -91,7 +90,7 @@ export const load: LoadHook = async (
* which are already in CJS syntax.
* In CTS, module.exports can be written in any pattern.
*/
const transformed = transformSync(
const transformed = backend.transformSync(
code,
filePath,
{
Expand All @@ -118,7 +117,7 @@ export const load: LoadHook = async (
loaded.format === 'json'
|| tsExtensionsPattern.test(url)
) {
const transformed = await transform(
const transformed = await backend.transform(
code,
filePath,
{
Expand Down
20 changes: 2 additions & 18 deletions src/patch-repl.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
import repl, { type REPLServer, type REPLEval } from 'node:repl';
import { transform } from 'esbuild';
import backend from './backend';

const patchEval = (nodeRepl: REPLServer) => {
const { eval: defaultEval } = nodeRepl;
const preEval: REPLEval = async function (code, context, filename, callback) {
try {
const transformed = await transform(
code,
{
sourcefile: filename,
loader: 'ts',
tsconfigRaw: {
compilerOptions: {
preserveValueImports: true,
},
},
define: {
require: 'global.require',
},
},
);

code = transformed.code;
code = await backend.replTransform(code, filename);
} catch {}

return defaultEval.call(this, code, context, filename, callback);
Expand Down
4 changes: 2 additions & 2 deletions src/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import repl, { type REPLEval } from 'node:repl';
import { version } from '../package.json';
import { transform } from './utils/transform/index.js';
import backend from './backend/index.js';

// Copied from
// https://github.com/nodejs/node/blob/v18.2.0/lib/internal/main/repl.js#L37
Expand All @@ -16,7 +16,7 @@ const nodeRepl = repl.start();
const { eval: defaultEval } = nodeRepl;

const preEval: REPLEval = async function (code, context, filename, callback) {
const transformed = await transform(
const transformed = await backend.transform(
code,
filename,
{
Expand Down
2 changes: 1 addition & 1 deletion tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { nodeVersions } from './utils/node-versions';
(async () => {
await describe('tsx', async ({ runTestSuite, describe }) => {
await runTestSuite(import('./specs/repl'));
await runTestSuite(import('./specs/transform'));
await runTestSuite(import('./specs/transform-esbuild'));

for (const nodeVersion of nodeVersions) {
const node = await createNode(nodeVersion);
Expand Down
17 changes: 10 additions & 7 deletions tests/specs/transform.ts → tests/specs/transform-esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { testSuite, expect } from 'manten';
import { createFsRequire } from 'fs-require';
import { Volume } from 'memfs';
import outdent from 'outdent';
import { transform, transformSync } from '../../src/utils/transform/index.js';
import esbuild from 'esbuild';
import getEsbuildBackend from '../../src/backend/esbuild/index.js';

const backend = getEsbuildBackend(esbuild);

const base64Module = (code: string) => `data:text/javascript;base64,${Buffer.from(code).toString('base64')}`;

Expand Down Expand Up @@ -41,7 +44,7 @@ export default testSuite(({ describe }) => {
describe('transform', ({ describe }) => {
describe('sync', ({ test }) => {
test('transforms ESM to CJS', () => {
const transformed = transformSync(
const transformed = backend.transformSync(
fixtures.esm,
'file.js',
{ format: 'cjs' },
Expand All @@ -64,7 +67,7 @@ export default testSuite(({ describe }) => {
});

test('dynamic import', () => {
const dynamicImport = transformSync(
const dynamicImport = backend.transformSync(
'import((0, _url.pathToFileURL)(path).href)',
'file.js',
{ format: 'cjs' },
Expand All @@ -75,7 +78,7 @@ export default testSuite(({ describe }) => {

test('sourcemap file', () => {
const fileName = 'file.mts';
const transformed = transformSync(
const transformed = backend.transformSync(
fixtures.ts,
fileName,
{ format: 'esm' },
Expand All @@ -93,7 +96,7 @@ export default testSuite(({ describe }) => {

test('quotes in file path', () => {
const fileName = '\'"name.mts';
const transformed = transformSync(
const transformed = backend.transformSync(
fixtures.ts,
fileName,
{ format: 'esm' },
Expand All @@ -112,7 +115,7 @@ export default testSuite(({ describe }) => {

describe('async', ({ test }) => {
test('transforms TS to ESM', async () => {
const transformed = await transform(
const transformed = await backend.transform(
fixtures.ts,
'file.ts',
{ format: 'esm' },
Expand All @@ -133,7 +136,7 @@ export default testSuite(({ describe }) => {

test('sourcemap file', async () => {
const fileName = 'file.cts';
const transformed = await transform(
const transformed = await backend.transform(
fixtures.ts,
fileName,
{ format: 'esm' },
Expand Down