Skip to content

Commit

Permalink
feat(esm api): configurable tsconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber committed May 23, 2024
1 parent 52d696c commit 3f42ae3
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 20 deletions.
18 changes: 18 additions & 0 deletions docs/node/ts-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ const { tsImport } = require('tsx/esm/api')
const loaded = await tsImport('./file.ts', __filename)
```
## `tsconfig.json`
### Custom `tsconfig.json` path
```ts
tsImport('./file.ts', {
parentURL: import.meta.url,
tsconfig: './custom-tsconfig.json'
})
```
### Disable `tsconfig.json` lookup
```ts
tsImport('./file.ts', {
parentURL: import.meta.url,
tsconfig: false
})
```
## Tracking loaded files
Detect files that get loaded with the `onImport` hook:
Expand Down
3 changes: 3 additions & 0 deletions src/cjs/api/global-require-patch.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import Module from 'node:module';
import { loadTsconfig } from '../../utils/tsconfig.js';
import { extensions } from './module-extensions.js';
import { resolveFilename } from './module-resolve-filename.js';

export const register = () => {
const { sourceMapsEnabled } = process;
const { _extensions, _resolveFilename } = Module;

loadTsconfig(process.env.TSX_TSCONFIG_PATH);

// register
process.setSourceMapsEnabled(true);
// @ts-expect-error overwriting read-only property
Expand Down
7 changes: 6 additions & 1 deletion src/esm/api/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import { MessageChannel, type MessagePort } from 'node:worker_threads';
import type { Message } from '../types.js';
import { createScopedImport, type ScopedImport } from './scoped-import.js';

export type TsconfigOptions = false | string;

export type InitializationOptions = {
namespace?: string;
port?: MessagePort;
tsconfig?: TsconfigOptions;
};

export type RegisterOptions = {
namespace?: string;
onImport?: (url: string) => void;
tsconfig?: TsconfigOptions;
};

export type Unregister = () => Promise<void>;
Expand Down Expand Up @@ -44,8 +48,9 @@ export const register: Register = (
{
parentURL: import.meta.url,
data: {
namespace: options?.namespace,
port: port2,
namespace: options?.namespace,
tsconfig: options?.tsconfig,
} satisfies InitializationOptions,
transferList: [port2],
},
Expand Down
9 changes: 5 additions & 4 deletions src/esm/api/ts-import.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { register } from './register.js';
import { register, type TsconfigOptions } from './register.js';

type Options = {
parentURL: string;
onImport?: (url: string) => void;
tsconfig?: TsconfigOptions;
};
const tsImport = (
specifier: string,
Expand All @@ -27,10 +28,10 @@ const tsImport = (
*/
const api = register({
namespace,
onImport: (
...(
isOptionsString
? undefined
: options.onImport
? {}
: options
),
});

Expand Down
10 changes: 9 additions & 1 deletion src/esm/hook/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { GlobalPreloadHook, InitializeHook } from 'node:module';
import type { InitializationOptions } from '../api/register.js';
import type { Message } from '../types.js';
import { loadTsconfig } from '../../utils/tsconfig.js';

type Data = InitializationOptions & {
active: boolean;
Expand All @@ -19,6 +20,10 @@ export const initialize: InitializeHook = async (

data.namespace = options.namespace;

if (options.tsconfig !== false) {
loadTsconfig(options.tsconfig ?? process.env.TSX_TSCONFIG_PATH);
}

if (options.port) {
data.port = options.port;

Expand All @@ -32,4 +37,7 @@ export const initialize: InitializeHook = async (
}
};

export const globalPreload: GlobalPreloadHook = () => 'process.setSourceMapsEnabled(true);';
export const globalPreload: GlobalPreloadHook = () => {
loadTsconfig(process.env.TSX_TSCONFIG_PATH);
return 'process.setSourceMapsEnabled(true);';
};
40 changes: 29 additions & 11 deletions src/utils/tsconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,37 @@ import {
parseTsconfig,
createFilesMatcher,
createPathsMatcher,
type TsConfigResult,
type FileMatcher,
} from 'get-tsconfig';

const tsconfig = (
process.env.TSX_TSCONFIG_PATH
? {
path: path.resolve(process.env.TSX_TSCONFIG_PATH),
config: parseTsconfig(process.env.TSX_TSCONFIG_PATH),
}
: getTsconfig()
);
// eslint-disable-next-line import-x/no-mutable-exports
export let fileMatcher: undefined | FileMatcher;

// eslint-disable-next-line import-x/no-mutable-exports
export let tsconfigPathsMatcher: undefined | ReturnType<typeof createPathsMatcher>;

export const fileMatcher = tsconfig && createFilesMatcher(tsconfig);
// eslint-disable-next-line import-x/no-mutable-exports
export let allowJs = false;

export const tsconfigPathsMatcher = tsconfig && createPathsMatcher(tsconfig);
export const loadTsconfig = (
configPath?: string,
) => {
let tsconfig: TsConfigResult | null = null;
if (configPath) {
const resolvedConfigPath = path.resolve(configPath);
tsconfig = {
path: resolvedConfigPath,
config: parseTsconfig(resolvedConfigPath),
};
} else {
tsconfig = getTsconfig();
if (!tsconfig) {
return;
}
}

export const allowJs = tsconfig?.config.compilerOptions?.allowJs ?? false;
fileMatcher = createFilesMatcher(tsconfig);
tsconfigPathsMatcher = createPathsMatcher(tsconfig);
allowJs = tsconfig?.config.compilerOptions?.allowJs ?? false;
};
160 changes: 157 additions & 3 deletions tests/specs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
tsxEsmApiCjsPath,
type NodeApis,
} from '../utils/tsx.js';
import { createPackageJson } from '../fixtures.js';
import { createPackageJson, createTsconfig } from '../fixtures.js';

const tsFiles = {
'file.ts': `
Expand Down Expand Up @@ -188,7 +188,7 @@ export default testSuite(({ describe }, node: NodeApis) => {
expect(stdout).toBe('Fails as expected\nfoo bar');
});

describe('register / unregister', ({ test }) => {
describe('register / unregister', ({ test, describe }) => {
test('register / unregister', async () => {
await using fixture = await createFixture({
'package.json': createPackageJson({ type: 'module' }),
Expand Down Expand Up @@ -285,9 +285,141 @@ export default testSuite(({ describe }, node: NodeApis) => {
});
expect(stdout).toBe('file.ts\nfoo.ts\nbar.ts\nindex.js');
});

describe('tsconfig', ({ test }) => {
test('should error on unresolvable tsconfig', async () => {
await using fixture = await createFixture({
'tsconfig.json': createTsconfig({
extends: 'doesnt-exist',
}),
'register.mjs': `
import { register } from ${JSON.stringify(tsxEsmApiPath)};
register();
`,
});

const { exitCode, stderr } = await execaNode('register.mjs', [], {
reject: false,
cwd: fixture.path,
nodePath: node.path,
nodeOptions: [],
});
expect(exitCode).toBe(1);
expect(stderr).toMatch('File \'doesnt-exist\' not found.');
});

test('disable lookup', async () => {
await using fixture = await createFixture({
'tsconfig.json': createTsconfig({
extends: 'doesnt-exist',
}),
'register.mjs': `
import { register } from ${JSON.stringify(tsxEsmApiPath)};
register({
tsconfig: false,
});
`,
});

await execaNode('register.mjs', [], {
cwd: fixture.path,
nodePath: node.path,
nodeOptions: [],
});
});

test('custom path', async () => {
await using fixture = await createFixture({
'package.json': createPackageJson({ type: 'module' }),
'tsconfig.json': createTsconfig({
extends: 'doesnt-exist',
}),
'tsconfig-custom.json': createTsconfig({
compilerOptions: {
jsxFactory: 'Array',
jsxFragmentFactory: 'null',
},
}),
'register.mjs': `
import { register } from ${JSON.stringify(tsxEsmApiPath)};
register({
tsconfig: './tsconfig-custom.json',
});
await import('./tsx.tsx');
`,
'tsx.tsx': `
console.log(<>hi</>);
`,
});

const { stdout } = await execaNode('register.mjs', [], {
cwd: fixture.path,
nodePath: node.path,
nodeOptions: [],
});
expect(stdout).toBe('[ null, null, \'hi\' ]');
});

test('custom path - invalid', async () => {
await using fixture = await createFixture({
'package.json': createPackageJson({ type: 'module' }),
'register.mjs': `
import { register } from ${JSON.stringify(tsxEsmApiPath)};
register({
tsconfig: './doesnt-exist',
});
await import('./tsx.tsx');
`,
'tsx.tsx': `
console.log(<>hi</>);
`,
});

const { exitCode, stderr } = await execaNode('register.mjs', [], {
reject: false,
cwd: fixture.path,
nodePath: node.path,
nodeOptions: [],
});
expect(exitCode).toBe(1);
expect(stderr).toMatch('Cannot resolve tsconfig at path');
});

test('fallsback to env var', async () => {
await using fixture = await createFixture({
'package.json': createPackageJson({ type: 'module' }),
'tsconfig.json': createTsconfig({
extends: 'doesnt-exist',
}),
'tsconfig-custom.json': createTsconfig({
compilerOptions: {
jsxFactory: 'Array',
jsxFragmentFactory: 'null',
},
}),
'register.mjs': `
import { register } from ${JSON.stringify(tsxEsmApiPath)};
register();
await import('./tsx.tsx');
`,
'tsx.tsx': `
console.log(<>hi</>);
`,
});

const { stdout } = await execaNode('register.mjs', [], {
cwd: fixture.path,
nodePath: node.path,
nodeOptions: [],
env: {
TSX_TSCONFIG_PATH: 'tsconfig-custom.json',
},
});
expect(stdout).toBe('[ null, null, \'hi\' ]');
});
});
});

// add CJS test
describe('tsImport()', ({ test }) => {
test('module', async () => {
await using fixture = await createFixture({
Expand Down Expand Up @@ -436,6 +568,28 @@ export default testSuite(({ describe }, node: NodeApis) => {
});
expect(stdout).toBe('foo\nfoo');
});

test('tsconfig disable', async () => {
await using fixture = await createFixture({
'package.json': createPackageJson({ type: 'module' }),
'tsconfig.json': createTsconfig({ extends: 'doesnt-exist' }),
'import.mjs': `
import { tsImport } from ${JSON.stringify(tsxEsmApiPath)};
await tsImport('./file.ts', {
parentURL: import.meta.url,
tsconfig: false,
});
`,
...tsFiles,
});

await execaNode('import.mjs', [], {
cwd: fixture.path,
nodePath: node.path,
nodeOptions: [],
});
});
});
} else {
test('no module.register error', async () => {
Expand Down

0 comments on commit 3f42ae3

Please sign in to comment.