Repository navigation
On Windows arm64 TS-based custom loader sometimes causes the process to exit silently when used with --experimental-strip-types #54645
Description
Version
22.7.0
Platform
Microsoft Windows NT 10.0.26100.0 x64
Subsystem
No response
What steps will reproduce the bug?
I was trying to run my TypeScript + node16 resolution app using --experimental-strip-types.
To cope with --experimental-strip-types not working when using .js extension in import paths, I've decided to use --import, so this is the command I came up with:
node --import=./register.ts --experimental-strip-types ./test.js
register.ts
import { register } from 'node:module';
register('./hooks.ts', import.meta.url);hooks.ts
import path from 'node:path';
import type { ResolveHook } from 'node:module';
const jsExts = new Set(['.js', '.jsx', '.mjs', '.cjs']);
const tsExts = new Set(['.ts', '.tsx', '.mts', '.cts']);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
// If it's not a JS import, use default behavior
if (!jsExts.has(ext)) {
return nextResolve(specifier, context);
}
for (const tsExt of tsExts) {
const specifierWithExtReplaced = specifier.replace(ext, tsExt);
try {
// Try and resolve file using each of TS extensions
return await nextResolve(specifierWithExtReplaced, context);
} catch {}
}
// If all else fails, fall back to default behavior
return nextResolve(specifier, context);
};test.ts
console.log('Hello, world!');How often does it reproduce? Is there a required condition?
100% of the time, condition: Windows (I tested arm64, can't tell if that happens on other architectures as well).
What is the expected behavior? Why is that the expected behavior?
./test.ts to be executed
What do you see instead?
On macOS, this works flawlessly, executing ./test.ts file (mind the extension).
On Windows, however, this does not execute the file, and just silently exits after producing one ExperimentalWarning written with red text:
PS C:\server> node --import=./register.ts --experimental-strip-types ./test.js
(node:3112) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Additional information
Note 1
If I rename register.ts and hooks.ts files to .js and strip types manually, it works:
PS C:\server> node --import=./register.js --experimental-strip-types ./test.js
(node:1648) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:1648) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Hello, world!
(side note: first ExperimentalWarning is in red, seconds one in white. I guess one would be enough? :D)
Note 2
In addition, the workaround I came up with (stripping types manually from register.ts and hooks.ts), does not work on a larger scale application for me. This, in turn, produces two ExperimentalWarning like above, but no code appears to be executed, so my guess is that resolution still fails at some point. When added logs, I observed that my resolution hook receives all specifiers it found in my app's entry file, but then stopped resolution altogether.
I logged that issue separately in #54665.
Activity
avivkeller commentedon Aug 29, 2024
Is this issue specific to the code you are importing, or is it imports (via --import) in general?
wojtekmaj commentedon Aug 29, 2024
Looks like striping types from loaders issue, the one this issue is primarily about, is not specific to the code I'm importing. The example I provided is literally just a console.log, and it still fails.
The second issue that I experienced when using pure JS loaders instead needs further investigation so I'll report this separately. But so far, it seems it's stopping module resolution on the entry module, without any errors. I'd suspect it's my mistake, I'm no expert in Node.js loaders, but then why it works on macOS 👀
avivkeller commentedon Aug 29, 2024
CC @nodejs/loaders @nodejs/typescript
aduh95 commentedon Aug 30, 2024
Can you run it again with
NODE_DEBUGset to*in your env?marco-ippolito commentedon Aug 30, 2024
I could not reproduce with node v22.7.0 on windows:
Warns are emitted twice because loader is off thread.
you can find the test here: #54657
wojtekmaj commentedon Aug 30, 2024
Sure!
node --import=./register.ts --experimental-strip-types ./test.js
PS C:\server> $Env:NODE_DEBUG = "*" PS C:\server> node --import=./register.ts --experimental-strip-types ./test.js (node:32848) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time (Use `node --trace-warnings ...` to show where the warning was created) ESM 32848: Storing file:///C:/server/register.ts (implicit type) in ModuleLoadMap ESM 32848: async addJobsToDependencyGraph() file:///C:/server/register.ts ModuleJob { importAttributes: [Object: null prototype] {}, isMain: false, inspectBrk: false, url: 'file:///C:/server/register.ts', module: Promise { }, modulePromise: Promise { }, linked: Promise { }, instantiated: undefined } ESM 32848: Translating TypeScript file:///C:/server/register.ts ESM 32848: Translating StandardModule file:///C:/server/register.ts ESM 32848: Storing node:module (implicit type) in ModuleLoadMap ESM 32848: async link() file:///C:/server/register.ts -> node:module ModuleJob { importAttributes: [Object: null prototype] {}, isMain: false, inspectBrk: false, url: 'node:module', module: Promise { }, modulePromise: Promise { }, linked: Promise { }, instantiated: undefined } ESM 32848: Translating BuiltinModule node:module MODULE 32848: load built-in module node:module ESM 32848: Loading BuiltinModule node:module ESM 32848: async addJobsToDependencyGraph() file:///C:/server/register.ts ModuleJob { importAttributes: [Object: null prototype] {}, isMain: false, inspectBrk: false, url: 'node:module', module: ModuleWrap { url: 'node:module' }, modulePromise: Promise { ModuleWrap { url: 'node:module' } }, linked: Promise { [Array(0): null prototype] [] }, instantiated: undefined } WORKER 32848: [0] create new worker internal/modules/esm/worker { stderr: false, stdin: false, stdout: false, trackUnmanagedFds: false, workerData: { lock: SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 } } } isInternal: true WORKER 32848: instantiating Worker. url: node:internal/modules/esm/worker doEval: internal WORKER 32848: [0] created Worker with ID 1 STREAM 32848: pipe count=1 opts=undefined STREAM 32848: resume STREAM 32848: pipe count=1 opts=undefined STREAM 32848: resume ESM 32848: wait for signal from worker ESM 32848: post sync message to worker { method: 'register', args: [ './hooks.ts', 'file:///C:/server/register.ts', undefined, undefined ], transferList: undefined } ESM 32848: wait for sync response from worker { method: 'register', args: [ './hooks.ts', 'file:///C:/server/register.ts', undefined, undefined ] } ESM 32848: wait for sync response from worker { method: 'register', args: [ './hooks.ts', 'file:///C:/server/register.ts', undefined, undefined ] }Which is vastly different output than when using JS loader:
node --import=./register.js --experimental-strip-types ./test.js
PS C:\server> node --import=./register.js --experimental-strip-types ./test.js (node:23116) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time (Use `node --trace-warnings ...` to show where the warning was created) ESM 23116: Storing file:///C:/server/register.js (implicit type) in ModuleLoadMap ESM 23116: async addJobsToDependencyGraph() file:///C:/server/register.js ModuleJob { importAttributes: [Object: null prototype] {}, isMain: false, inspectBrk: false, url: 'file:///C:/server/register.js', module: Promise { }, modulePromise: Promise { }, linked: Promise { }, instantiated: undefined } ESM 23116: Translating StandardModule file:///C:/server/register.js ESM 23116: Storing node:module (implicit type) in ModuleLoadMap ESM 23116: async link() file:///C:/server/register.js -> node:module ModuleJob { importAttributes: [Object: null prototype] {}, isMain: false, inspectBrk: false, url: 'node:module', module: Promise { }, modulePromise: Promise { }, linked: Promise { }, instantiated: undefined } ESM 23116: Translating BuiltinModule node:module MODULE 23116: load built-in module node:module ESM 23116: Loading BuiltinModule node:module ESM 23116: async addJobsToDependencyGraph() file:///C:/server/register.js ModuleJob { importAttributes: [Object: null prototype] {}, isMain: false, inspectBrk: false, url: 'node:module', module: ModuleWrap { url: 'node:module' }, modulePromise: Promise { ModuleWrap { url: 'node:module' } }, linked: Promise { [Array(0): null prototype] [] }, instantiated: undefined } WORKER 23116: [0] create new worker internal/modules/esm/worker { stderr: false, stdin: false, stdout: false, trackUnmanagedFds: false, workerData: { lock: SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 } } } isInternal: true WORKER 23116: instantiating Worker. url: node:internal/modules/esm/worker doEval: internal WORKER 23116: [0] created Worker with ID 1 STREAM 23116: pipe count=1 opts=undefined STREAM 23116: resume STREAM 23116: pipe count=1 opts=undefined STREAM 23116: resume ESM 23116: wait for signal from worker ESM 23116: post sync message to worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ], transferList: undefined } ESM 23116: wait for sync response from worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ] } ESM 23116: wait for sync response from worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ] } ESM 23116: got sync response from worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ] } ESM 23116: post async message to worker { method: 'resolve', args: [ 'file:///C:/server/test.js', undefined, [Object: null prototype] {} ], transferList: undefined } ESM 23116: wait for async response from worker { method: 'resolve', args: [ 'file:///C:/server/test.js', undefined, [Object: null prototype] {} ] } ESM 23116: wait for async response from worker { method: 'resolve', args: [ 'file:///C:/server/test.js', undefined, [Object: null prototype] {} ] } STREAM 23116: resume false STREAM 23116: read 0 STREAM 23116: need readable false STREAM 23116: length less than watermark true STREAM 23116: do read STREAM 23116: flow STREAM 23116: read undefined STREAM 23116: need readable true STREAM 23116: length less than watermark true STREAM 23116: reading, ended or constructing false STREAM 23116: resume false STREAM 23116: read 0 STREAM 23116: need readable false STREAM 23116: length less than watermark true STREAM 23116: do read STREAM 23116: flow STREAM 23116: read undefined STREAM 23116: need readable true STREAM 23116: length less than watermark true STREAM 23116: reading, ended or constructing false STREAM 23116: push WORKER 23116: [1] is setting up worker child environmentSTREAM 23116: ondata
WORKER 23116: [1] is setting up worker child environment
STREAM 23116: dest.write true
STREAM 23116: maybeReadMore read 0
STREAM 23116: read 0
STREAM 23116: need readable true
STREAM 23116: length less than watermark true
STREAM 23116: do read
STREAM 23116: push STREAM 23116: push null
STREAM 23116: ondata
STREAM 23116: push null
STREAM 23116: dest.write true
STREAM 23116: push STREAM 23116: onEofChunk
STREAM 23116: ondata
STREAM 23116: onEofChunk
STREAM 23116: dest.write true
STREAM 23116: push STREAM 23116: emitReadable
STREAM 23116: ondata
STREAM 23116: emitReadable
STREAM 23116: dest.write true
STREAM 23116: push STREAM 23116: emitReadable false
STREAM 23116: ondata
STREAM 23116: emitReadable false
STREAM 23116: dest.write true
STREAM 23116: push WORKER 23116: [1] starts worker script internal/modules/esm/worker (eval = internal) at cwd = C:\server
STREAM 23116: ondata
WORKER 23116: [1] starts worker script internal/modules/esm/worker (eval = internal) at cwd = C:\server
STREAM 23116: dest.write true
STREAM 23116: push (node:23116) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use
node --trace-warnings ...to show where the warning was created)STREAM 23116: ondata
(node:23116) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use
node --trace-warnings ...to show where the warning was created)STREAM 23116: dest.write true
STREAM 23116: push STREAM 23116: emitReadable_
STREAM 23116: ondata
STREAM 23116: emitReadable_
STREAM 23116: dest.write true
STREAM 23116: push STREAM 23116: flow
STREAM 23116: ondata
STREAM 23116: flow
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: Storing file:///C:/server/hooks.js (implicit type) in ModuleLoadMap
STREAM 23116: ondata
ESM 23116: Storing file:///C:/server/hooks.js (implicit type) in ModuleLoadMap
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: async addJobsToDependencyGraph() file:///C:/server/hooks.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'file:///C:/server/hooks.js',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
STREAM 23116: ondata
ESM 23116: async addJobsToDependencyGraph() file:///C:/server/hooks.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'file:///C:/server/hooks.js',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: Translating StandardModule file:///C:/server/hooks.js
STREAM 23116: ondata
ESM 23116: Translating StandardModule file:///C:/server/hooks.js
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: Storing node:path (implicit type) in ModuleLoadMap
STREAM 23116: ondata
ESM 23116: Storing node:path (implicit type) in ModuleLoadMap
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: async link() file:///C:/server/hooks.js -> node:path ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'node:path',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
STREAM 23116: ondata
ESM 23116: async link() file:///C:/server/hooks.js -> node:path ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'node:path',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: Translating BuiltinModule node:path
STREAM 23116: ondata
ESM 23116: Translating BuiltinModule node:path
STREAM 23116: dest.write true
STREAM 23116: push MODULE 23116: load built-in module node:path
STREAM 23116: ondata
MODULE 23116: load built-in module node:path
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: Loading BuiltinModule node:path
STREAM 23116: ondata
ESM 23116: Loading BuiltinModule node:path
STREAM 23116: dest.write true
STREAM 23116: push ESM 23116: async addJobsToDependencyGraph() file:///C:/server/hooks.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'node:path',
module: ModuleWrap { url: 'node:path' },
modulePromise: Promise { ModuleWrap { url: 'node:path' } },
linked: Promise { [Array(0): null prototype] [] },
instantiated: undefined
}
STREAM 23116: ondata
ESM 23116: async addJobsToDependencyGraph() file:///C:/server/hooks.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'node:path',
module: ModuleWrap { url: 'node:path' },
modulePromise: Promise { ModuleWrap { url: 'node:path' } },
linked: Promise { [Array(0): null prototype] [] },
instantiated: undefined
}
STREAM 23116: dest.write true
STREAM 23116: maybeReadMore read 0
STREAM 23116: read 0
STREAM 23116: need readable true
STREAM 23116: length less than watermark true
STREAM 23116: do read
ESM 23116: got async response from worker {
method: 'resolve',
args: [
'file:///C:/server/test.js',
undefined,
[Object: null prototype] {}
]
} Int32Array(1) [ 5 ]
ESM 23116: post async message to worker {
method: 'load',
args: [
'file:///C:/server/test.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
],
transferList: undefined
}
ESM 23116: wait for async response from worker {
method: 'load',
args: [
'file:///C:/server/test.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
}
ESM 23116: Storing file:///C:/server/test.ts (implicit type) in ModuleLoadMap
ESM 23116: async addJobsToDependencyGraph() file:///C:/server/test.ts ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: true,
inspectBrk: false,
url: 'file:///C:/server/test.ts',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
ESM 23116: got async response from worker {
method: 'load',
args: [
'file:///C:/server/test.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
} Int32Array(1) [ 7 ]
ESM 23116: Translating TypeScript file:///C:/server/test.ts
ESM 23116: Translating StandardModule file:///C:/server/test.ts
Hello, world!
wojtekmaj commentedon Aug 30, 2024
On Windows arm64? :)
wojtekmaj commentedon Aug 30, 2024
Fun facts:
register.tsthat registershooks.jsalso works finehooks.tsdoes not work under ANY circumstances, even if I strip types off it so it's essentially pure JavaScript[-]On Windows, unlike macOS --import can't be a TS file when used with --experimental-strip-types[/-][+]On Windows (arm64?) `--import` can't be a TS file when used with `--experimental-strip-types`[/+]marco-ippolito commentedon Aug 31, 2024
try to add change
hook.tstoI tested it on windows x64 I think this an arm64 specific issue.
I noticed weird behavior on arm64 in the past (cc @legendecas) but I dont have a machine to reproduce
wojtekmaj commentedon Aug 31, 2024
@marco-ippolito Both JS and TS throw! O_O
targos commentedon Aug 31, 2024
I confirm it is arm64-specific:
26 remaining items
wojtekmaj commentedon Sep 2, 2024
Well, it's blocked by Amaro, which in turn is blocked by SWC. But after Amaro is eventually updated with a fixed SWC version, a final push is still needed on Node.js side to get this issue resolved.
aduh95 commentedon Sep 2, 2024
What about renaming the issue though?
[-]On Windows arm64 custom loader can't be a TS file when used with `--experimental-strip-types`[/-][+]On Windows arm64 TS-based custom loader sometimes causes the process to exit silently when used with `--experimental-strip-types`[/+]marco-ippolito commentedon Dec 23, 2024
Apparently now its working
#54657
I just got a windows arm64 VM to double check
test: check typescript loader
wojtekmaj commentedon Dec 23, 2024
@marco-ippolito What is the first version on which it's expected to work properly? I'll test on a real machine. Latest LTS, 22.12.0, still has this bug. Moreover, the bug in SWC that I originally filed (and is the cause of this failure) still can be reproduced.
marco-ippolito commentedon Dec 24, 2024
Try the latest 23, on main the issue is fixed. Today I will provide more info
marco-ippolito commentedon Dec 24, 2024
Tested it on main and v23.5.0, everything works fine, commented here: swc-project/swc#9520 (comment)
Whatever it was it has been fixed, it's hard to bisect since compiling on my Windows ARM64 VM takes hours, (I went to sleep and woke up with node compiled). Unless someone wants to invest more money/better machines (which I'm already doing) into finding the commit that caused this issue, I consider this issue fixed.
wojtekmaj commentedon Dec 27, 2024
I can confirm that this problem appears to be fixed on Node.js 23.5.0 on a real Windows arm64 machine. 🥳 Melts my brain a little since the bug in swc-project can still be reproduced, but good news anyway I guess 😅
test: check typescript loader
test: check typescript loader