Skip to content
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?) custom loader stops receiving specifiers after resolving imports in an entry file #54665

Closed
wojtekmaj opened this issue Aug 30, 2024 · 6 comments
Labels
arm Issues and PRs related to the ARM platform. duplicate Issues and PRs that are duplicates of other issues or PRs. loaders Issues and PRs related to ES module loaders strip-types Issues or PRs related to strip-types support windows Issues and PRs related to the Windows platform.

Comments

@wojtekmaj
Copy link

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.js --experimental-strip-types ./src/index.js

register.js

import { register } from 'node:module';

register('./hooks.js', import.meta.url);

hooks.js

import path from 'node:path';

const tsExts = new Set(['.ts', '.tsx', '.mts', '.cts']);

export const resolve = async function resolve(specifier, context, nextResolve) {
  const ext = path.extname(specifier);

  if (ext !== '.js') {
    return nextResolve(specifier, context);
  }

  for (const tsExt of tsExts) {
    const specifierWithExtReplaced = specifier.replace(/\.js$/, tsExt);

    try {
      return await nextResolve(specifierWithExtReplaced, context);
    } catch {}
  }

  return nextResolve(specifier, context);
};

src/index.ts

import app from './app.js';

import setupWebsocket from './websocket.js';

import { MODE, PORT, PROD } from './env.js';

import setupStartup from './startup/index.js';
import setupCron from './cron/index.js';

// File continues with irrelevant content

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?

The entire module tree to be resolved

What do you see instead?

On macOS, this works flawlessly.

On Windows, however, this does not execute start the app, custom loader stops receiving specifiers after resolving imports in an entry file, and just silently exits after producing two ExperimentalWarnings:

PS C:\server> node --import=./register.js --experimental-strip-types ./src/index.ts
(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)
(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

Output of node --import=./register.js --experimental-strip-types ./test.js with NODE_DEBUG="*" env PS C:\server> node --import=./register.js --experimental-strip-types ./src/index.ts (node:35372) 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 35372: Storing file:///C:/server/register.js (implicit type) in ModuleLoadMap ESM 35372: 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 35372: Translating StandardModule file:///C:/server/register.js ESM 35372: Storing node:module (implicit type) in ModuleLoadMap ESM 35372: 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 35372: Translating BuiltinModule node:module MODULE 35372: load built-in module node:module ESM 35372: Loading BuiltinModule node:module ESM 35372: 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 35372: [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 35372: instantiating Worker. url: node:internal/modules/esm/worker doEval: internal WORKER 35372: [0] created Worker with ID 1 STREAM 35372: pipe count=1 opts=undefined STREAM 35372: resume STREAM 35372: pipe count=1 opts=undefined STREAM 35372: resume ESM 35372: wait for signal from worker ESM 35372: post sync message to worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ], transferList: undefined } ESM 35372: wait for sync response from worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ] } ESM 35372: wait for sync response from worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ] } ESM 35372: got sync response from worker { method: 'register', args: [ './hooks.js', 'file:///C:/server/register.js', undefined, undefined ] } ESM 35372: post async message to worker { method: 'resolve', args: [ 'file:///C:/server/src/index.ts', undefined, [Object: null prototype] {} ], transferList: undefined } ESM 35372: wait for async response from worker { method: 'resolve', args: [ 'file:///C:/server/src/index.ts', undefined, [Object: null prototype] {} ] } ESM 35372: wait for async response from worker { method: 'resolve', args: [ 'file:///C:/server/src/index.ts', undefined, [Object: null prototype] {} ] } STREAM 35372: resume false STREAM 35372: read 0 STREAM 35372: need readable false STREAM 35372: length less than watermark true STREAM 35372: do read STREAM 35372: flow STREAM 35372: read undefined STREAM 35372: need readable true STREAM 35372: length less than watermark true STREAM 35372: reading, ended or constructing false STREAM 35372: resume false STREAM 35372: read 0 STREAM 35372: need readable false STREAM 35372: length less than watermark true STREAM 35372: do read STREAM 35372: flow STREAM 35372: read undefined STREAM 35372: need readable true STREAM 35372: length less than watermark true STREAM 35372: reading, ended or constructing false STREAM 35372: push WORKER 35372: [1] is setting up worker child environment

STREAM 35372: ondata
WORKER 35372: [1] is setting up worker child environment
STREAM 35372: dest.write true
STREAM 35372: maybeReadMore read 0
STREAM 35372: read 0
STREAM 35372: need readable true
STREAM 35372: length less than watermark true
STREAM 35372: do read
STREAM 35372: push STREAM 35372: push null

STREAM 35372: ondata
STREAM 35372: push null
STREAM 35372: dest.write true
STREAM 35372: push STREAM 35372: onEofChunk

STREAM 35372: ondata
STREAM 35372: onEofChunk
STREAM 35372: dest.write true
STREAM 35372: push STREAM 35372: emitReadable

STREAM 35372: ondata
STREAM 35372: emitReadable
STREAM 35372: dest.write true
STREAM 35372: push STREAM 35372: emitReadable false

STREAM 35372: ondata
STREAM 35372: emitReadable false
STREAM 35372: dest.write true
STREAM 35372: push WORKER 35372: [1] starts worker script internal/modules/esm/worker (eval = internal) at cwd = C:\server

STREAM 35372: ondata
WORKER 35372: [1] starts worker script internal/modules/esm/worker (eval = internal) at cwd = C:\server
STREAM 35372: dest.write true
STREAM 35372: push (node:35372) 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 35372: ondata
(node:35372) 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 35372: dest.write true
STREAM 35372: push STREAM 35372: emitReadable_

STREAM 35372: ondata
STREAM 35372: emitReadable_
STREAM 35372: dest.write true
STREAM 35372: push STREAM 35372: flow

STREAM 35372: ondata
STREAM 35372: flow
STREAM 35372: dest.write true
STREAM 35372: push ESM 35372: Storing file:///C:/server/hooks.js (implicit type) in ModuleLoadMap

STREAM 35372: ondata
ESM 35372: Storing file:///C:/server/hooks.js (implicit type) in ModuleLoadMap
STREAM 35372: dest.write true
STREAM 35372: push ESM 35372: 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 35372: ondata
ESM 35372: 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 35372: dest.write true
STREAM 35372: push ESM 35372: Translating StandardModule file:///C:/server/hooks.js

STREAM 35372: ondata
ESM 35372: Translating StandardModule file:///C:/server/hooks.js
STREAM 35372: dest.write true
STREAM 35372: push ESM 35372: Storing node:path (implicit type) in ModuleLoadMap

STREAM 35372: ondata
ESM 35372: Storing node:path (implicit type) in ModuleLoadMap
STREAM 35372: dest.write true
STREAM 35372: push ESM 35372: 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 35372: ondata
ESM 35372: 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 35372: dest.write true
STREAM 35372: push ESM 35372: Translating BuiltinModule node:path

STREAM 35372: ondata
ESM 35372: Translating BuiltinModule node:path
STREAM 35372: dest.write true
STREAM 35372: push MODULE 35372: load built-in module node:path

STREAM 35372: ondata
MODULE 35372: load built-in module node:path
STREAM 35372: dest.write true
STREAM 35372: push ESM 35372: Loading BuiltinModule node:path

STREAM 35372: ondata
ESM 35372: Loading BuiltinModule node:path
STREAM 35372: dest.write true
STREAM 35372: push ESM 35372: 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 35372: ondata
ESM 35372: 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 35372: dest.write true
STREAM 35372: maybeReadMore read 0
STREAM 35372: read 0
STREAM 35372: need readable true
STREAM 35372: length less than watermark true
STREAM 35372: do read
ESM 35372: got async response from worker {
method: 'resolve',
args: [
'file:///C:/server/src/index.ts',
undefined,
[Object: null prototype] {}
]
} Int32Array(1) [ 5 ]
ESM 35372: post async message to worker {
method: 'load',
args: [
'file:///C:/server/src/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'load',
args: [
'file:///C:/server/src/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
}
ESM 35372: Storing file:///C:/server/src/index.ts (implicit type) in ModuleLoadMap
ESM 35372: async addJobsToDependencyGraph() file:///C:/server/src/index.ts ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: true,
inspectBrk: false,
url: 'file:///C:/server/src/index.ts',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
ESM 35372: got async response from worker {
method: 'load',
args: [
'file:///C:/server/src/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
} Int32Array(1) [ 7 ]
ESM 35372: Translating TypeScript file:///C:/server/src/index.ts
ESM 35372: Translating StandardModule file:///C:/server/src/index.ts
ESM 35372: post async message to worker {
method: 'resolve',
args: [
'./app.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'resolve',
args: [
'./app.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
}
ESM 35372: post async message to worker {
method: 'resolve',
args: [
'./websocket.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'resolve',
args: [
'./websocket.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
}
ESM 35372: post async message to worker {
method: 'resolve',
args: [
'./env.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'resolve',
args: [
'./env.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
}
ESM 35372: post async message to worker {
method: 'resolve',
args: [
'./startup/index.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'resolve',
args: [
'./startup/index.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
}
ESM 35372: post async message to worker {
method: 'resolve',
args: [
'./cron/index.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'resolve',
args: [
'./cron/index.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
}
ESM 35372: got async response from worker {
method: 'resolve',
args: [
'./app.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
} Int32Array(1) [ 11 ]
ESM 35372: got async response from worker {
method: 'resolve',
args: [
'./websocket.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
} Int32Array(1) [ 13 ]
ESM 35372: got async response from worker {
method: 'resolve',
args: [
'./env.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
} Int32Array(1) [ 13 ]
ESM 35372: got async response from worker {
method: 'resolve',
args: [
'./startup/index.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
} Int32Array(1) [ 15 ]
ESM 35372: got async response from worker {
method: 'resolve',
args: [
'./cron/index.js',
'file:///C:/server/src/index.ts',
[Object: null prototype] {}
]
} Int32Array(1) [ 15 ]
ESM 35372: post async message to worker {
method: 'load',
args: [
'file:///C:/server/src/app.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'load',
args: [
'file:///C:/server/src/app.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
}
ESM 35372: Storing file:///C:/server/src/app.ts (implicit type) in ModuleLoadMap
ESM 35372: post async message to worker {
method: 'load',
args: [
'file:///C:/server/src/websocket.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'load',
args: [
'file:///C:/server/src/websocket.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
}
ESM 35372: Storing file:///C:/server/src/websocket.ts (implicit type) in ModuleLoadMap
ESM 35372: post async message to worker {
method: 'load',
args: [
'file:///C:/server/src/env.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'load',
args: [
'file:///C:/server/src/env.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
}
ESM 35372: Storing file:///C:/server/src/env.ts (implicit type) in ModuleLoadMap
ESM 35372: post async message to worker {
method: 'load',
args: [
'file:///C:/server/src/startup/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'load',
args: [
'file:///C:/server/src/startup/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
}
ESM 35372: Storing file:///C:/server/src/startup/index.ts (implicit type) in ModuleLoadMap
ESM 35372: post async message to worker {
method: 'load',
args: [
'file:///C:/server/src/cron/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
],
transferList: undefined
}
ESM 35372: wait for async response from worker {
method: 'load',
args: [
'file:///C:/server/src/cron/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
}
ESM 35372: Storing file:///C:/server/src/cron/index.ts (implicit type) in ModuleLoadMap
ESM 35372: got async response from worker {
method: 'load',
args: [
'file:///C:/server/src/app.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
} Int32Array(1) [ 22 ]
ESM 35372: async link() file:///C:/server/src/index.ts -> ./app.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'file:///C:/server/src/app.ts',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
ESM 35372: got async response from worker {
method: 'load',
args: [
'file:///C:/server/src/websocket.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
} Int32Array(1) [ 24 ]
ESM 35372: async link() file:///C:/server/src/index.ts -> ./websocket.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'file:///C:/server/src/websocket.ts',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
ESM 35372: got async response from worker {
method: 'load',
args: [
'file:///C:/server/src/env.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
} Int32Array(1) [ 24 ]
ESM 35372: async link() file:///C:/server/src/index.ts -> ./env.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'file:///C:/server/src/env.ts',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
ESM 35372: got async response from worker {
method: 'load',
args: [
'file:///C:/server/src/startup/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
} Int32Array(1) [ 24 ]
ESM 35372: async link() file:///C:/server/src/index.ts -> ./startup/index.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'file:///C:/server/src/startup/index.ts',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}
ESM 35372: got async response from worker {
method: 'load',
args: [
'file:///C:/server/src/cron/index.ts',
{
format: 'module-typescript',
importAttributes: [Object: null prototype] {}
}
]
} Int32Array(1) [ 24 ]
ESM 35372: async link() file:///C:/server/src/index.ts -> ./cron/index.js ModuleJob {
importAttributes: [Object: null prototype] {},
isMain: false,
inspectBrk: false,
url: 'file:///C:/server/src/cron/index.ts',
module: Promise { },
modulePromise: Promise { },
linked: Promise { },
instantiated: undefined
}

@avivkeller avivkeller added windows Issues and PRs related to the Windows platform. loaders Issues and PRs related to ES module loaders strip-types Issues or PRs related to strip-types support labels Aug 30, 2024
@avivkeller
Copy link
Member

Is this is a duplicate of #54645?

@wojtekmaj
Copy link
Author

No it's not. #54645 is about TS loaders not working at all, this one if about JS loaders technically working, but only one level deep. I did mention this use in #54645 at the end of my OC as a side note, and after collecting data I filed another issue.

@marco-ippolito
Copy link
Member

marco-ippolito commented Aug 31, 2024

No it's not. #54645 is about TS loaders not working at all, this one if about JS loaders technically working, but only one level deep. I did mention this use in #54645 at the end of my OC as a side note, and after collecting data I filed another issue.

I think this is infact a problem with loaders (doesnt matter whether js or ts) on windows arm64, we can probably keep one issue open since the solution is probably the same

@avivkeller avivkeller closed this as not planned Won't fix, can't repro, duplicate, stale Aug 31, 2024
@avivkeller avivkeller added the duplicate Issues and PRs that are duplicates of other issues or PRs. label Aug 31, 2024
@avivkeller
Copy link
Member

Closing as duplicate per the comment above.

@avivkeller avivkeller added the arm Issues and PRs related to the ARM platform. label Aug 31, 2024
@wojtekmaj
Copy link
Author

I'm definitely not an expert here, but I fail to understand how these two issues would be connected.

#54645 seems to be (as per my latest findings in #54645 (comment)) an issue with amaro exiting silently instead of stripping types.

This issue is about loaders technically working, to their fullest extent, but not being even called beyond the entry file. I also tested and it doesn't matter what entry file is - so if I change the command so that e.g. ./app.js' or './cron/index.js' is my entry file - they get evaluated perfectly... So it's not some weird coincidence that my entry file happened to work and then each and every module it imports caused amaro to exit silently or anything.

@avivkeller
Copy link
Member

I think that these issues are related in the sense that they both only appear under very specific (similar) conditions involving similar aspects of the codebase. There's a chance that fixing one may fix the other, or it'll at least narrow done the cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arm Issues and PRs related to the ARM platform. duplicate Issues and PRs that are duplicates of other issues or PRs. loaders Issues and PRs related to ES module loaders strip-types Issues or PRs related to strip-types support windows Issues and PRs related to the Windows platform.
Projects
None yet
Development

No branches or pull requests

3 participants