Skip to content

Conversation

@marco-ippolito
Copy link
Member

@marco-ippolito marco-ippolito commented Sep 10, 2025

Fixes: #59565

This PR adds the flag --experimental-ext=ext.
This flag overrides the entrypoint extension module resolution.
Immagine you are trying to run an extensionless file with typescript content.

marcoippolito@marcos-MacBook-Pro-3 node % node test/fixtures/ext/extensionless-mts
file:///Users/marcoippolito/Documents/projects/forks/node/test/fixtures/ext/extensionless-mts:3
const foo: string = 'Hello World!';
      ^^^

SyntaxError: Missing initializer in const declaration
    at compileSourceTextModule (node:internal/modules/esm/utils:346:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:107:18)
    at #translate (node:internal/modules/esm/loader:536:12)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:583:27)
    at async ModuleJob._link (node:internal/modules/esm/module_job:162:19)

Node.js v22.18.0

By default extensionless files or unknown extensions are never treated as ts so it's not possible to run it.
With this flag it is possible to override it:

marcoippolito@marcos-MacBook-Pro-3 node % ./node --experimental-ext=ts test/fixtures/ext/extensionless-mts
(node:89705) ExperimentalWarning: --experimental-ext is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Hello World!

The override only applies to the entrypoint and not for the whole graph.
With typescript support this becomes necessary while a few years ago when there was some discussion #23868 (comment) this wasnt the case.

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/loaders

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Sep 10, 2025
@marco-ippolito marco-ippolito force-pushed the experimental-ext branch 2 times, most recently from 62bee79 to 2587b9a Compare September 10, 2025 14:16
@codecov
Copy link

codecov bot commented Sep 10, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.29%. Comparing base (8ec29f2) to head (d5a9da7).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #59840      +/-   ##
==========================================
+ Coverage   88.28%   88.29%   +0.01%     
==========================================
  Files         701      701              
  Lines      206774   206841      +67     
  Branches    39772    39792      +20     
==========================================
+ Hits       182545   182635      +90     
+ Misses      16234    16233       -1     
+ Partials     7995     7973      -22     
Files with missing lines Coverage Δ
lib/internal/modules/cjs/loader.js 98.43% <100.00%> (+0.51%) ⬆️
lib/internal/modules/esm/get_format.js 93.21% <100.00%> (+0.06%) ⬆️
lib/internal/modules/esm/translators.js 92.30% <100.00%> (+0.02%) ⬆️
lib/internal/modules/run_main.js 97.81% <100.00%> (+0.16%) ⬆️
src/node_options.cc 78.07% <100.00%> (+0.20%) ⬆️
src/node_options.h 97.89% <ø> (ø)

... and 45 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@marco-ippolito marco-ippolito added the module Issues and PRs related to the module subsystem. label Sep 10, 2025
@joyeecheung
Copy link
Member

joyeecheung commented Sep 11, 2025

Shouldn't this be more about "the format" instead of "the extension"? The extension does not necessarily accurately identify the format (in the case of .js and .ts, they are both ambiguous extensions that lead to the hoops of module format detections)

On that note this seems to be overlapping with --input-type, or some flag to "assume the default format for an extension-less file". (Though both of them can also be an alias of some inline JavaScript to use a hook to do that). Or just do format detection on extension-less files that include TypeStripping like how we handle string inputs.

Copy link
Member

@GeoffreyBooth GeoffreyBooth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this approach is misguided, for the reasons I gave in #59565 (comment) and in previous linked issues: many platforms don’t support flags in shebang lines, so for all those platforms the goal of this flag—to allow extensionless TypeScript files—won’t be achieved, and the feature will feel broken.

A better solution is to build on what we achieved with syntax detection, where if a file fails to evaluate as CommonJS and then fails to evaluate as ESM and then succeeds as TypeScript, it runs as TypeScript. This not only will work everywhere, it doesn’t require a new flag or opting in.

@marco-ippolito
Copy link
Member Author

marco-ippolito commented Sep 11, 2025

@GeoffreyBooth what you are saying is unfortunately not possible to do smoothly for Typescript.
Running

fetch<Object>("example.org")

is valid js and produces false.
Running it as TypeScript, strips and performs an http call.
There is syntax ambiguity and the user must be able to explicitly set the format. We had the same problem with eval, but in that case the user can pass --input-type to remove ambiguity.
We can still do the detection and always treat it as js in case of ambiguity but we need a flag like this regardless.
This would also add performance overhead because you have to compile -> if throws strip and compile again -> if its a ts error like unsupported_syntax throw it else throw the original error. While overhead is reasonable for --eval, I think it's a bit less reasonable for files. Also we would need to create a special case for '.js' file to skip the detection because right now are treated (afaik) like extensionless or whatever extension.
In short even if we had detection we still need a similar flag.

To answer @joyeecheung this should be pretty similar to input-type with the caveaut that by forcing extension rather then the format (ex: ts, js) you can let node perfom the syntax detection for you (but you have to specificy ts so you already must know the content🤔). So you dont have to specificy commonjs or module just js or ts. Anyways I'm open to rewrite the feature with formats instead of extensions if thats makes this PR move forward.

@aduh95
Copy link
Contributor

aduh95 commented Sep 11, 2025

what you are saying is unfortunately not possible to do smoothly for Typescript.

tbh the same argument can be made for ESM detection (e.g. console.log(this === undefined) is valid CJS code, valid ESM code, and produces different output), yet the ecosystem has adapted (i.e. if you are using extensionless files, you must use unambiguous syntax, or expect your file to be interpreted as default syntax).
Maybe there are good reasons not to apply auto-fallback to type stripping, but I wanted to point out that syntax ambiguity does not seem enough of a justification.

@marco-ippolito
Copy link
Member Author

I'm not saying it is not possible, we are already doing this for eval. I'm saying that a flag to change the default behavior "saying execute this as ts and not as js" is still needed. So the detection can come first (I might open a PR for it) but we still can discuss how to implement this format ovveride

@aduh95
Copy link
Contributor

aduh95 commented Sep 11, 2025

It sounds like the --experimental-default-type flag, maybe we should reuse that instead of creating a new one.

@marco-ippolito
Copy link
Member Author

If someone wants to pickup ts detection I started looking into it marco-ippolito@995b47d but going on PTO for the next 2 weeks

@joyeecheung
Copy link
Member

joyeecheung commented Sep 11, 2025

FWIW I think no matter what we do, the new hoops should only apply to files without any extension. For example, it should not apply TypeScript syntax detection on .js files - that does have an extension. Or forcing .js files to get executed as TypeScript. That sounds like a can of worms.

@GeoffreyBooth
Copy link
Member

a flag to change the default behavior "saying execute this as ts and not as js" is still needed. So the detection can come first (I might open a PR for it) but we still can discuss how to implement this format ovveride

I don't think a flag for this purpose is needed. The only case that's not currently already handled is extensionless TypeScript files, and I think it's okay to ask people to use unambiguously TypeScript syntax in extensionless files if they want their extensionless TypeScript file to work. That's better than asking them to try to use a flag but then the flag doesn't work in their shebang line and they open a bug with us and we tell them that their operating system doesn't support flags in shebang lines, and the user is frustrated. It's a very unusual TypeScript file that could be ambiguously JavaScript, so the case where the user would need to go back and add something unambiguously TypeScript in order to force the correct detection is unlikely to happen in real world scenarios.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs related to general changes in the lib or src directory. module Issues and PRs related to the module subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support TypeScript executable without extension when flag is set

5 participants