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
When TypeScript project is configured to ESM, projen
commands gets ERR_UNKNOWN_FILE_EXTENSION
#3388
Comments
Thanks for reporting this! Sounds like an important thing to resolve one way or the other!
For purpose of reproducing this, how do you configure your project to use ESM?
For the fix, this seems to do quite a bit more than making ts-node use ESM? Is all of this needed? What am I missing? |
Ignoring I believe the minimum to reproduce is the first of those lines: this.package.addField("type", "module"); And what’s all happening: The # instead of
ts-node --project tsconfig.special.json src/index.ts
# use
tsc .projenrc.ts && \
TS_NODE_PROJECT=tsconfig.special.json node --loader ts-node/esm --no-warnings=ExperimentalWarning src/index.ts If there are any type errors, the The We don’t emit from things compiled with There are other options. We could use |
Okay, so it seems like This is the minimal change I did come up with:
I don't really see how we can resolve this without addressing |
That does indeed reproduce it for me, but it also breaks it more, sadly. Last I looked, and it's a moving target, It doesn't appear to fix it when I add Running the command as follows gets past that but, but makes another: TS_NODE_PROJECT=tsconfig.dev.json npx \
node --loader ts-node/esm --no-warnings=ExperimentalWarning .projenrc.ts To get it to work with ESM, minimally (not the project, just const project = new typescript.TypeScriptAppProject({
//...
tsconfigDev: { compilerOptions: { module: 'node16' } }, // ← had to use 'node16'
});
project.package.addField('type', 'module');
project.tsconfigDev.file.addOverride('ts-node.esm', true); // ← 'tsnode.esm' vs 'esm' So, as a workaround, add this before the synth: if (
project.defaultTask
) {
project.defaultTask.reset(
'tsc .projenrc.ts && node --loader ts-node/esm --no-warnings=ExperimentalWarning .projenrc.ts',
);
project.defaultTask.env('TS_NODE_PROJECT', project.tsconfigDev.fileName);
project.defaultTask.description =
'Run projen with ts-node/esm (workaround for Node 18.19+ applied)';
} And you can still run And you're right, @mrgrain, more work is needed for ESM to work fully. But I thought I'd make smaller issues (with smaller PRs) that are easier to tackle to pave the way. Another option, if we're willing to move away from const project = new typescript.TypeScriptAppProject({
//...
devDeps: ['tsx'],
tsconfigDev: {
compilerOptions: {
module: 'node16',
},
},
});
project.package.addField('type', 'module');
if (
project.defaultTask
) {
project.defaultTask.reset(
`tsc .projenrc.ts && tsx --tsconfig ${project.tsconfigDev.fileName} .projenrc.ts`,
);
} |
I just retested and the However, the const project = new typescript.TypeScriptAppProject({
//...
// No tsconfig overrides here
devDeps: ['tsx'],
});
// optional - works with or without type: "module", and no other tsconfig changes are needed
project.package.addField('type', 'module');
if (
project.defaultTask
) {
project.defaultTask.reset(
`tsc .projenrc.ts && tsx --tsconfig ${project.tsconfigDev.fileName} .projenrc.ts`,
);
} |
Ha you are right, apologies! I think I misread the ts-node docs. I can conform this works for me now: const project = new typescript.TypeScriptAppProject({
//...
tsconfigDev: { compilerOptions: { module: 'nodenext' } }, // ← or 'node16'
});
project.package.addField('type', 'module');
project.tsconfigDev.file.addOverride('ts-node.esm', true);
It feels odd to me to say that no tsconfig changes are required. You still need to change I'm not super keen on changing |
I'm not sure I follow completely, but I'll take a swing at it: I was just saying that
I've been using
That said, I don't think we necessarily need to switch - they do provide the same basic features. Referencing Here's an untested rough sketch to make export interface ProjenrcOptions {
// ...
/**
* Whether to use `tsx` instead of `ts-node`.
*
* Ignored `swc` option if `tsx` is set to `true`.
*
* @default false
*/
readonly tsx?: boolean;
}
// ...
export class Projenrc extends ProjenrcFile {
// ...
private addDefaultTask() {
// this is the task projen executes when running `projen` without a
// specific task (if this task is not defined, projen falls back to
// running "node .projenrc.js").
// we use "tsconfig.dev.json" here to allow projen source files to reside
// anywhere in the project tree.
if (this._tsx) {
// Use tsx
this._tsProject.addDevDeps("tsx");
this._tsProject.defaultTask?.exec(
`tsc ${this.filePath} && tsx --tsconfig ${this._tsProject.tsconfigDev.fileName} ${this.filePath}`
);
} else {
// Use ts-node
const deps = [];
if (this._swc) {
deps.push("@swc/core");
}
deps.push("ts-node");
this._tsProject.addDevDeps(...deps);
const tsNode = this._swc ? "ts-node --swc" : "ts-node";
this._tsProject.defaultTask?.exec(
`${tsNode} --project ${this._tsProject.tsconfigDev.fileName} ${this.filePath}`
);
}
}
//...
} Note that this wouldn't be replacing or effecting other uses of |
You can verify if
In fact, when |
My point really just is that for ESM to work you need to make changes to your tsconfig. One change more doesn't seem like a big issue to me. |
That confused me for a while, since Then I realized that there's confusion due to poor naming: With the On a slightly different note, how should I proceed? My eventual goal is to chip away at ESM support with several smaller Issue→PR steps. I have several projects that use ESM mode already (optionally), and projen itself works fine in/with ESM-based projects, so no changes are needed in projen itself, just how it configures the tsconfig, package, ts-node, and ts-jest. Those projects are here: https://github.com/10mi2/tms-projen-projects |
Oh no, I see the confusion now. Sorry for being unspecific. What I meant is:
Reason being that there is a in almost infinite permutation of ways how we can deal with Now saying that, I'm not sure if it's currently possible to write this |
Please start with writing an RFC issue on ESM support. It's really hard to judge smaller PRs without seeing the bigger pictures. Once we have Community agreement on the bigger picture, I'm happy to accept smaller PRs. |
I'm not sure how 'ProjenrcUsingTsx' would work. Or why, really. Nothing in the I guess I could extend On the ESM score, I'll work something up. Will likely take a few days to get the time. |
That would probably be the best approach. You could write it from scratch, but like you've said some parts would end up being copy and paste.
Thank you! |
Just wanted to add that we ran into the same issue and solved it very similar to what you did. This worked for us: const project = new typescript.TypeScriptAppProject({
//...
tsconfigDev: { compilerOptions: {
module: 'ES2022', // <-- starting from ES2015, everything works
moduleResolution: TypeScriptModuleResolution.NODE,
} },
});
project.tsconfigDev!.file.addOverride('ts-node', {
esm: true,
experimentalSpecifierResolution: 'node',
}); For us it didn't work without node as moduleResolution and epxerimentalSpecifierResolution. Not 100% sure why (I think it was something about file extensions in import statements or something like this), but I wanted to mention this. |
@dj-rabel This is very dependent on node version. Were you on node 18.18 or older, or something newer? |
@giseburt sure. I just wanted to contribute our experience to the discussion. For the project I'm referring to, we're on 18.14. |
@dj-rabel 👍. Watch out for upgrading to node 18.19 or newer, you'll run into the |
@giseburt thanks for the hint. This error sounds very familiar to me. I'm not 100% sure, but this might be the reason why we ended up adding this experimentalSpecifierResolution setting 🤔🧐 will check with the colleges tomorrow. |
EDITED: I'm working on a new RFC for general ESM support, but for those running into this issue, here's the escape hatch I'm using ATM. See edit in original comment above, I put the code there. What's happening here is, since If we use So, we make a new Also, |
When the project is configured to use ESM, the
projen
command usually maps to "exec":Due to TypeStrong/ts-node#2094 when using
node
>= 18.19 (18.18 works fine, 20 does not) yields an error like:Session here show using node 20.10.0, node 18.19.0, and node 18.18.2:
By default,
projen
does not configure a TypeScript project for ESM (that's a separate issue), but when it does, we run into this.Can be recreated with the following command using node 18.19 or newer:
Setting
--ts-node-unknown-file-extension-workaround=true
(or leaving it out) will apply the workaround (found mostly from the ts-node issue)EDIT 2: I recommend the following on projects that run into this, or might in the future:
This solution is better than the other suggestions I've made since it will work in CJS and ESM mode, avoids all the other
ts-jest
issues, and does full type-checking with clear (normal) typescript errors. See explanation hereSee complete RFC for ESM mode here: #3447
NOTE ON EDIT: Previous version of this comment had
tsc .projenrc.ts
but I missed that when you passtsc
a file list, it ignoredtsconfig.json
files and only uses default configs. The second edit switch fromts-node
totsx
to fix even more issues.The text was updated successfully, but these errors were encountered: