-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
On Windows arm64 TS-based custom loader sometimes causes the process to exit silently when used with --experimental-strip-types
#54645
Comments
Is this issue specific to the code you are importing, or is it imports (via --import) in general? |
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 👀 |
CC @nodejs/loaders @nodejs/typescript |
Can you run it again with |
I could not reproduce with node v22.7.0 on windows:
Warns are emitted twice because loader is off thread. |
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 environment
|
On Windows arm64? :) |
Fun facts:
|
--import
can't be a TS file when used with --experimental-strip-types
try to add change
I tested it on windows x64 I think this an arm64 specific issue. |
@marco-ippolito Both JS and TS throw! O_O
|
I confirm it is arm64-specific:
|
Okay, this is getting interesting. My code gets evaluated, but only... The first line? import type { ResolveHook } from 'node:module';
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
console.log('One');
console.log('Two');
return nextResolve(specifier, context);
};
BUT: import type { ResolveHook } from 'node:module';
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
console.log('One'); console.log('Two');
return nextResolve(specifier, context);
};
And more:
|
--import
can't be a TS file when used with --experimental-strip-types
--import
can't be a TS file when used with --experimental-strip-types
When you say you can't reproduce on MacOS, is the Mac also arm64? Essentially, is this an arm issue or a windows+arm issue? |
Yup, it seems like so. The other device I'm testing on is a M2 MacBook Air. |
--import
can't be a TS file when used with --experimental-strip-types
--experimental-strip-types
Have you tried without the |
I haven't; the whole point of my custom loader was to run a TS app, so I can't run it without the flag (or another loader like tsx). I'll try to create a test setup, but it will take me some time! |
Apparently tests seem to pass |
@wojtekmaj do the tests @marco-ippolito added pass on your machine? |
@RedYetiDev Yes they do! |
Ok so it's probably not reproducing the issue 🤔 maybe adding a |
Doesn't seem to break it 🫠 ✅ Works: import type { ResolveHook } from "node:module";
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
return nextResolve(specifier, context);
}; ✅ Works: import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
return nextResolve(specifier, context);
}; ✅ Works: import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
console.log(ext);
return nextResolve(specifier, context);
}; ✅ Works: import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
console.log(ext);
console.log("One");
console.log("Two");
console.log("Three");
return nextResolve(specifier, context);
}; ❌ Does NOT work: import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
console.log(ext);
if (true) {
}
return nextResolve(specifier, context);
}; ❌ Does NOT work: import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
console.log(ext);
if (ext !== ".js") {
console.log("WTF?");
}
return nextResolve(specifier, context);
}; ❌ Does NOT work: import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
console.log(ext);
if (ext !== ".js") {
return nextResolve(specifier, context);
}
return nextResolve(specifier, context);
}; ❌ Does NOT work: import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
console.log(ext);
if (ext !== ".js") {
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 {}
}
return nextResolve(specifier, context);
}; ✅ Works (!!!): import path from "node:path";
import type { ResolveHook } from "node:module";
const tsExts = new Set([".ts", ".tsx", ".mts", ".cts"]);
export const resolve: ResolveHook = async function resolve(specifier, context, nextResolve) {
const ext = path.extname(specifier);
console.log(ext);
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 {}
}
return nextResolve(specifier, context);
}; |
Ok, final conclusion: any kind of Please note that the last example from my previous comment does NOT fix #54665 - it's still reproducible even though the removal of |
Wow this is extremely weird 💀 I have no idea why only on Windows arm64 and not on other platforms. Maybe a loaders wizard like @joyeecheung might have an idea |
Guys, I got it. Kind of.
Guess what? Output on
Output on
Yep that's the whole thing. |
Is this weird type-stripping exclusive to windows arm64? |
This is so strange... FYI @wojtekmaj opened an issue against SWC swc-project/swc#9520 |
Maybe try —no-maglev and see if it makes a difference? |
@joyeecheung Absolutely none.
|
Then I guess this needs someone who knows enough about what the off-thread hooks API is doing to debug (I don’t) |
By the way, AFIAK console.log() output is not guaranteed to work in the off-thread loader, it can be lost silently. There was also some recent report about structured clone errors being swallowed silently by @JakobJingleheimer |
I figure there's nothing we can do right now until the SWC bug I reported is fixed. Then, we would need to get SWC updated in Amaro, and finally, get Amaro updated in Node.js. |
The issue should probably be renamed if this has nothing to do with custom loader, and maybe we should transfer it to amaro repo? Or am I missing something? |
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. |
What about renaming the issue though? |
--experimental-strip-types
--experimental-strip-types
Version
22.7.0
Platform
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:register.ts
hooks.ts
test.ts
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 executedWhat 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:
Additional information
Note 1
If I rename register.ts and hooks.ts files to .js and strip types manually, it works:
(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.
The text was updated successfully, but these errors were encountered: