1
+ import { createRequire } from 'module'
1
2
import path from 'path'
3
+ import { pathToFileURL } from 'url'
2
4
import {
3
5
MessageChannel ,
4
6
Worker ,
7
9
parentPort ,
8
10
} from 'worker_threads'
9
11
12
+ import { findUp , tryExtensions } from '@pkgr/utils'
13
+
10
14
import {
11
15
AnyAsyncFn ,
12
16
AnyFn ,
@@ -18,14 +22,7 @@ import {
18
22
19
23
export * from './types.js'
20
24
21
- const {
22
- SYNCKIT_BUFFER_SIZE ,
23
- SYNCKIT_TIMEOUT ,
24
- SYNCKIT_TS_ESM ,
25
- SYNCKIT_EXEC_ARV ,
26
- } = process . env
27
-
28
- const TS_USE_ESM = ! ! SYNCKIT_TS_ESM && [ '1' , 'true' ] . includes ( SYNCKIT_TS_ESM )
25
+ const { SYNCKIT_BUFFER_SIZE , SYNCKIT_TIMEOUT , SYNCKIT_EXEC_ARV } = process . env
29
26
30
27
export const DEFAULT_BUFFER_SIZE = SYNCKIT_BUFFER_SIZE
31
28
? + SYNCKIT_BUFFER_SIZE
@@ -35,6 +32,7 @@ export const DEFAULT_TIMEOUT = SYNCKIT_TIMEOUT ? +SYNCKIT_TIMEOUT : undefined
35
32
36
33
export const DEFAULT_WORKER_BUFFER_SIZE = DEFAULT_BUFFER_SIZE || 1024
37
34
35
+ /* istanbul ignore next */
38
36
export const DEFAULT_EXEC_ARGV = SYNCKIT_EXEC_ARV ?. split ( ',' ) ?? [ ]
39
37
40
38
const syncFnCache = new Map < string , AnyFn > ( )
@@ -50,7 +48,7 @@ export interface SynckitOptions {
50
48
// property copying manually.
51
49
export const extractProperties = < T > ( object ?: T ) : T | undefined => {
52
50
if ( object && typeof object === 'object' ) {
53
- const properties = { } as T
51
+ const properties = { } as unknown as T
54
52
for ( const key in object ) {
55
53
properties [ key as keyof T ] = object [ key ]
56
54
}
@@ -84,7 +82,7 @@ export function createSyncFn<R, T extends AnyAsyncFn<R>>(
84
82
85
83
const syncFn = startWorkerThread < R , T > (
86
84
workerPath ,
87
- typeof bufferSizeOrOptions === 'number'
85
+ /* istanbul ignore next */ typeof bufferSizeOrOptions === 'number'
88
86
? { bufferSize : bufferSizeOrOptions , timeout }
89
87
: bufferSizeOrOptions ,
90
88
)
@@ -94,8 +92,55 @@ export function createSyncFn<R, T extends AnyAsyncFn<R>>(
94
92
return syncFn
95
93
}
96
94
97
- const throwError = ( msg : string ) => {
98
- throw new Error ( msg )
95
+ const cjsRequire =
96
+ typeof require === 'undefined'
97
+ ? createRequire ( import . meta. url )
98
+ : /* istanbul ignore next */ require
99
+
100
+ const dataUrl = ( code : string ) =>
101
+ new URL ( `data:text/javascript,${ encodeURIComponent ( code ) } ` )
102
+
103
+ // eslint-disable-next-line sonarjs/cognitive-complexity
104
+ const setupTsNode = ( workerPath : string , execArgv : string [ ] ) => {
105
+ if ( ! / [ / \\ ] n o d e _ m o d u l e s [ / \\ ] / . test ( workerPath ) ) {
106
+ const ext = path . extname ( workerPath )
107
+ // TODO: support `.cts` and `.mts` automatically
108
+ if ( ! ext || ext === '.js' ) {
109
+ const found = tryExtensions (
110
+ ext ? workerPath . replace ( / \. j s $ / , '' ) : workerPath ,
111
+ [ '.ts' , '.js' ] ,
112
+ )
113
+ if ( found ) {
114
+ workerPath = found
115
+ }
116
+ }
117
+ }
118
+
119
+ const isTs = / \. [ c m ] ? t s $ / . test ( workerPath )
120
+
121
+ // TODO: it does not work for `ts-node` for now
122
+ let tsUseEsm = workerPath . endsWith ( '.mts' )
123
+
124
+ if ( isTs ) {
125
+ if ( ! tsUseEsm ) {
126
+ const pkg = findUp ( workerPath )
127
+ if ( pkg ) {
128
+ tsUseEsm =
129
+ ( cjsRequire ( pkg ) as { type ?: 'commonjs' | 'module' } ) . type ===
130
+ 'module'
131
+ }
132
+ }
133
+ if ( tsUseEsm && ! execArgv . includes ( '--loader' ) ) {
134
+ execArgv = [ '--loader' , 'ts-node/esm' , ...execArgv ]
135
+ }
136
+ }
137
+
138
+ return {
139
+ isTs,
140
+ tsUseEsm,
141
+ workerPath,
142
+ execArgv,
143
+ }
99
144
}
100
145
101
146
function startWorkerThread < R , T extends AnyAsyncFn < R > > (
@@ -108,21 +153,24 @@ function startWorkerThread<R, T extends AnyAsyncFn<R>>(
108
153
) {
109
154
const { port1 : mainPort , port2 : workerPort } = new MessageChannel ( )
110
155
111
- const isTs = workerPath . endsWith ( '.ts' )
156
+ const {
157
+ isTs,
158
+ tsUseEsm,
159
+ workerPath : finalWorkerPath ,
160
+ execArgv : finalExecArgv ,
161
+ } = setupTsNode ( workerPath , execArgv )
112
162
113
163
const worker = new Worker (
114
164
isTs
115
- ? TS_USE_ESM
116
- ? throwError (
117
- 'Native esm in `.ts` file is not supported yet, please use `.cjs` instead' ,
118
- )
119
- : `require('ts-node/register');require('${ workerPath } ')`
120
- : workerPath ,
165
+ ? tsUseEsm
166
+ ? dataUrl ( `import '${ String ( pathToFileURL ( finalWorkerPath ) ) } '` )
167
+ : `require('ts-node/register');require('${ finalWorkerPath } ')`
168
+ : finalWorkerPath ,
121
169
{
122
- eval : isTs ,
170
+ eval : isTs && ! tsUseEsm ,
123
171
workerData : { workerPort } ,
124
172
transferList : [ workerPort ] ,
125
- execArgv,
173
+ execArgv : finalExecArgv ,
126
174
} ,
127
175
)
128
176
@@ -158,7 +206,7 @@ function startWorkerThread<R, T extends AnyAsyncFn<R>>(
158
206
}
159
207
160
208
if ( error ) {
161
- throw Object . assign ( error , properties )
209
+ throw Object . assign ( error as object , properties )
162
210
}
163
211
164
212
return result !
0 commit comments