Skip to content

Commit 7a5f1bd

Browse files
nwidynskiJounQin
andauthoredMar 20, 2025··
feat: add new ts runner node for Node22+ (#199)
Co-authored-by: JounQin <admin@1stg.me>
1 parent 3a63f84 commit 7a5f1bd

File tree

5 files changed

+112
-15
lines changed

5 files changed

+112
-15
lines changed
 

‎.changeset/good-walls-brush.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"synckit": minor
3+
---
4+
5+
feat: add support for `--experimental-strip-types`
6+
7+
Introducing the `node` runner, which will replace `ts-node` as the new default:
8+
9+
- when running on Node 22 with the `--experimental-strip-types`
10+
flag enabled via `NODE_OPTIONS` env or cli args
11+
- or when running on Node 23+ without `--no-experimental-strip-types`
12+
flag enabled via `NODE_OPTIONS` env or cli args
13+
14+
An error will be thrown when attempting to run with `node` on unsupported versions (<22).
15+
On these versions, the default runner remains `ts-node` when available.

‎.github/workflows/ci.yml

-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,5 @@ jobs:
5757

5858
- name: Codecov
5959
uses: codecov/codecov-action@v5
60-
if: ${{ matrix.node == 18 || matrix.node == 18.18 }}
6160
with:
6261
token: ${{ secrets.CODECOV_TOKEN }}

‎README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ Perform async work synchronously in Node.js using `worker_threads` with first-cl
2222
- [Options](#options)
2323
- [Envs](#envs)
2424
- [TypeScript](#typescript)
25-
- [`ts-node`](#ts-node)
25+
- [`node` (Default, Node 23+)](#node-default-node-23)
26+
- [`ts-node` (Default)](#ts-node-default)
2627
- [`esbuild-register`](#esbuild-register)
2728
- [`esbuild-runner`](#esbuild-runner)
2829
- [`swc`](#swc)
@@ -53,7 +54,7 @@ import { createSyncFn } from 'synckit'
5354

5455
// the worker path must be absolute
5556
const syncFn = createSyncFn(require.resolve('./worker'), {
56-
tsRunner: 'tsx', // optional, can be `'ts-node' | 'esbuild-register' | 'esbuild-runner' | 'tsx'`
57+
tsRunner: 'tsx', // optional, can be `'node' | 'ts-node' | 'esbuild-register' | 'esbuild-runner' | 'tsx'`
5758
})
5859

5960
// do whatever you want, you will get the result synchronously!
@@ -126,9 +127,17 @@ export interface GlobalShim {
126127

127128
### TypeScript
128129

129-
#### `ts-node`
130+
#### `node` (Default, Node 23+)
130131

131-
If you want to use `ts-node` for worker file (a `.ts` file), it is supported out of box!
132+
On recent Node versions, you may select this runner to execute your worker file (a `.ts` file) in the native runtime.
133+
134+
As of Node v23, this feature is supported out of the box. To enable it in the current LTS, you can pass the [`--experimental-strip-types`](https://nodejs.org/docs/latest-v22.x/api/typescript.html#type-stripping) flag to the process. Visit the [documentation](https://nodejs.org/docs/latest/api/typescript.html#type-stripping) to learn more.
135+
136+
When `synckit` detects the process to be running with this flag, it will execute the worker file with the `node` runner by default.
137+
138+
#### `ts-node` (Default)
139+
140+
Prior to Node v23, you may want to use `ts-node` to execute your worker file (a `.ts` file).
132141

133142
If you want to use a custom tsconfig as project instead of default `tsconfig.json`, use `TS_NODE_PROJECT` env. Please view [ts-node](https://github.com/TypeStrong/ts-node#tsconfig) for more details.
134143

‎src/index.ts

+40-9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const INT32_BYTES = 4
3333
export * from './types.js'
3434

3535
export const TsRunner = {
36+
// https://nodejs.org/docs/latest/api/typescript.html#type-stripping
37+
Node: 'node',
3638
// https://github.com/TypeStrong/ts-node
3739
TsNode: 'ts-node',
3840
// https://github.com/egoist/esbuild-register
@@ -55,7 +57,23 @@ const {
5557
SYNCKIT_TS_RUNNER,
5658
} = process.env
5759

58-
const IS_NODE_20 = Number(process.versions.node.split('.')[0]) >= 20
60+
export const MTS_SUPPORTED_NODE_VERSION = 16
61+
export const LOADER_SUPPORTED_NODE_VERSION = 20
62+
export const STRIP_TYPES_DEFAULT_NODE_VERSION = 23
63+
export const STRIP_TYPES_SUPPORTED_NODE_VERSION = 22
64+
65+
const NODE_VERSION = Number.parseFloat(process.versions.node)
66+
const STRIP_TYPES_FLAG = '--experimental-strip-types'
67+
const NO_STRIP_TYPES_FLAG = '--no-experimental-strip-types'
68+
const IS_TYPE_STRIPPING_ENABLED =
69+
(NODE_VERSION >= STRIP_TYPES_DEFAULT_NODE_VERSION &&
70+
!(
71+
NODE_OPTIONS?.includes(NO_STRIP_TYPES_FLAG) ||
72+
process.argv.includes(NO_STRIP_TYPES_FLAG)
73+
)) ||
74+
(NODE_VERSION >= STRIP_TYPES_SUPPORTED_NODE_VERSION &&
75+
(NODE_OPTIONS?.includes(STRIP_TYPES_FLAG) ||
76+
process.argv.includes(STRIP_TYPES_FLAG)))
5977

6078
export const DEFAULT_TIMEOUT = SYNCKIT_TIMEOUT ? +SYNCKIT_TIMEOUT : undefined
6179

@@ -80,8 +98,6 @@ export const DEFAULT_GLOBAL_SHIMS_PRESET: GlobalShim[] = [
8098
},
8199
]
82100

83-
export const MTS_SUPPORTED_NODE_VERSION = 16
84-
85101
let syncFnCache: Map<string, AnyFn> | undefined
86102

87103
export interface SynckitOptions {
@@ -205,11 +221,27 @@ const setupTsRunner = (
205221
}
206222
}
207223

208-
if (tsRunner == null && isPkgAvailable(TsRunner.TsNode)) {
209-
tsRunner = TsRunner.TsNode
224+
if (tsRunner == null) {
225+
if (IS_TYPE_STRIPPING_ENABLED) {
226+
tsRunner = TsRunner.Node
227+
} else if (isPkgAvailable(TsRunner.TsNode)) {
228+
tsRunner = TsRunner.TsNode
229+
}
210230
}
211231

212232
switch (tsRunner) {
233+
case TsRunner.Node: {
234+
if (NODE_VERSION < STRIP_TYPES_SUPPORTED_NODE_VERSION) {
235+
throw new Error(
236+
'type stripping is not supported in this node version',
237+
)
238+
}
239+
execArgv =
240+
NODE_VERSION >= STRIP_TYPES_DEFAULT_NODE_VERSION
241+
? execArgv.filter(arg => arg !== NO_STRIP_TYPES_FLAG)
242+
: [STRIP_TYPES_FLAG, ...execArgv]
243+
break
244+
}
213245
case TsRunner.TsNode: {
214246
if (tsUseEsm) {
215247
if (!execArgv.includes('--loader')) {
@@ -283,7 +315,7 @@ const setupTsRunner = (
283315
// https://github.com/un-ts/synckit/issues/123
284316
resolvedPnpLoaderPath = pathToFileURL(pnpLoaderPath).toString()
285317

286-
if (!IS_NODE_20) {
318+
if (NODE_VERSION < LOADER_SUPPORTED_NODE_VERSION) {
287319
execArgv = [
288320
'--experimental-loader',
289321
resolvedPnpLoaderPath,
@@ -452,8 +484,7 @@ function startWorkerThread<R, T extends AnyAsyncFn<R>>(
452484

453485
if (/\.[cm]ts$/.test(finalWorkerPath)) {
454486
const isTsxSupported =
455-
!tsUseEsm ||
456-
Number.parseFloat(process.versions.node) >= MTS_SUPPORTED_NODE_VERSION
487+
!tsUseEsm || NODE_VERSION >= MTS_SUPPORTED_NODE_VERSION
457488
/* istanbul ignore if */
458489
if (!finalTsRunner) {
459490
throw new Error('No ts runner specified, ts worker path is not supported')
@@ -604,7 +635,7 @@ export function runAsWorker<
604635

605636
const { workerPort, sharedBuffer, pnpLoaderPath } = workerData as WorkerData
606637

607-
if (pnpLoaderPath && IS_NODE_20) {
638+
if (pnpLoaderPath && NODE_VERSION >= LOADER_SUPPORTED_NODE_VERSION) {
608639
module.register(pnpLoaderPath)
609640
}
610641

‎test/ts-runner.spec.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { jest } from '@jest/globals'
77
import { _dirname, nodeVersion, tsUseEsmSupported } from './helpers.js'
88
import type { AsyncWorkerFn } from './types.js'
99

10-
import { MTS_SUPPORTED_NODE_VERSION, TsRunner } from 'synckit'
10+
import {
11+
MTS_SUPPORTED_NODE_VERSION,
12+
STRIP_TYPES_DEFAULT_NODE_VERSION,
13+
STRIP_TYPES_SUPPORTED_NODE_VERSION,
14+
TsRunner,
15+
} from 'synckit'
1116

1217
beforeEach(() => {
1318
jest.resetModules()
@@ -130,6 +135,44 @@ test(TsRunner.TSX, async () => {
130135
expect(syncFn(5)).toBe(5)
131136
})
132137

138+
test(TsRunner.Node, async () => {
139+
const { createSyncFn } = await import('synckit')
140+
141+
if (nodeVersion < STRIP_TYPES_SUPPORTED_NODE_VERSION) {
142+
// eslint-disable-next-line jest/no-conditional-expect
143+
expect(() =>
144+
createSyncFn<AsyncWorkerFn>(workerMtsPath, {
145+
tsRunner: TsRunner.Node,
146+
}),
147+
).toThrow('type stripping is not supported in this node version')
148+
return
149+
}
150+
151+
let syncFn = createSyncFn<AsyncWorkerFn>(workerJsPath, {
152+
tsRunner:
153+
nodeVersion >= STRIP_TYPES_DEFAULT_NODE_VERSION
154+
? undefined
155+
: TsRunner.Node,
156+
})
157+
expect(syncFn(1)).toBe(1)
158+
expect(syncFn(2)).toBe(2)
159+
expect(syncFn(5)).toBe(5)
160+
161+
if (!tsUseEsmSupported) {
162+
return
163+
}
164+
165+
syncFn = createSyncFn<AsyncWorkerFn>(workerMtsPath, {
166+
tsRunner:
167+
nodeVersion >= STRIP_TYPES_DEFAULT_NODE_VERSION
168+
? undefined
169+
: TsRunner.Node,
170+
})
171+
expect(syncFn(1)).toBe(1)
172+
expect(syncFn(2)).toBe(2)
173+
expect(syncFn(5)).toBe(5)
174+
})
175+
133176
test('unknown ts runner', async () => {
134177
const { createSyncFn } = await import('synckit')
135178

0 commit comments

Comments
 (0)
Please sign in to comment.