modules
modules copied to clipboard
Node 13.9.0 drop support for extensionless npm scripts ("bin")
package.json:
{
"scripts": {
"test": "tsc"
},
"dependencies": {
"typescript": "^3.7.5"
}
}
.npmrc:
node-options="--loader=./loader.js"
loader.js:
// Custom loader. May be empty for test
Command:
npm test
Result:
Node 13.8.0:
tsc
Node 13.9.0:
tsc
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for c:\workspace\node_13.9.0\node_modules\typescript\bin\tsc
at Loader.defaultGetFormat [as _getFormat] (internal/modules/esm/get_format.js:70:15)
Why ? Becacuse a lot of tools has bin
specification without scripts extension: ex.
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
Why did you introduce a change that blocks npm scripts?
Can you run node --version
locally. There was never a Node.js version 3, as version three was part of the IO.js project. Currently supported versions of Node.js are 10, 12, 13.
Are you running code as CommonJS or ESM? If you are using ESM you need to include the full path to the resource you are trying to run.
There is nothing that we have done in Node.js to block npm scripts, which still are actively used across the ecosystem, and if I recall even internally in Node.js core for some of our tooling.
Can you run
node --version
locally. There was never a Node.js version 3, as version three was part of the IO.js project. Currently supported versions of Node.js are 10, 12, 13.
$ node --version
v13.9.0
Are you running code as CommonJS or ESM? If you are using ESM you need to include the full path to the resource you are trying to run.
I'm not running "code" but npm
script which found bin
definded in ./node_modules/typescript/package.json
file.
There is nothing that we have done in Node.js to block npm scripts, which still are actively used across the ecosystem, and if I recall even internally in Node.js core for some of our tooling.
There is a "broken" change: https://github.com/nodejs/node/pull/31415
This is because --loader
forces the ESM resolution algorithm not CJS resolution, and extension-less files are not resolved by default for ESM, see https://github.com/nodejs/node/pull/31415
Overall, this is the intended behavior we got to after problems with having support for files lacking extensions in the ESM resolver; so I wouldn't think we need to fix this. My recommendation is you can have tsc
map to a file with an extension, or expand the custom --loader
supplied to support loading files without extensions (since it is overwriting how tsc
works anyway).
You should be able to work around this by implementing your custom loader to support a get_format hook which allows extensionless files.
Something like:
import { extname } from 'path';
export async function getFormat(url, context, defaultGetFormat) {
if (!extname(url.pathname)) {
return {
format: 'commonjs'
};
}
return defaultGetFormat(url, context, defaultGetFormat);
}
Note it would still be an option for us to allow files without an extension for the Node.js CLI entry point only, as we had previously, which was specially implemented before for backwards compatibility cases like this.
You should be able to work around this by implementing your custom loader to support a get_format hook which allows extensionless files.
Thanks !. It's a duct-tape but it works!
@guybedford backwards compatibility breakage came from --loader, without --loader it still worked fine
I read the Node documentation to confirm my ignorance of the loader (https://nodejs.org/dist/latest-v13.x/docs/api/esm.html#esm_experimental_loaders).
When hooks are used they only apply to ES module loading and not to any CommonJS modules loaded.
Again, I feel that this is a mistake becasuse defaultGetFormat
incorrectly recognized Typescript module as esm
when in reality he is cjs
.
@guybedford
Finally i added test for (also!) extensionless internal modules which defaultGetFormat
recognize correctly:
export async function getFormat(urlString, context, defaultGetFormat) {
// support for extensionless files in "bin" scripts
if (/^file:\/\/\/.*\/bin\//.test(url) && !path.extname(url)) {
return {
format: 'commonjs',
};
}
return defaultGetFormat(urlString, context, defaultGetFormat);
}
Fixed some typos in sla100's loader fix and added the import
:
import path from 'path';
export async function getFormat(urlString, context, defaultGetFormat) {
// support for extensionless files in "bin" scripts
if (/^file:\/\/\/.*\/bin\//.test(urlString) && !path.extname(urlString)) {
return {
format: 'module',
};
}
return defaultGetFormat(urlString, context, defaultGetFormat);
}
To use this, node --experimental-loader loader.mjs ...
though this does (currently) result in ugly warning messages.
Is there a more transparent way to include this?
To use this, node --experimental-loader loader.mjs ... though this does (currently) result in ugly warning messages.
@tadman it silences all warnings, but i've been using NODE_NO_WARNINGS=1