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
RFC: ESM project support in NodeProject
and TypeScriptProject
#3447
Comments
NodeProject
and TypeScriptProject
support for creating ESM projects
Nice work! Would love to see this happing. Can you include a proposal for the required API changes (high-level) in the RFC. Open questions:
|
Thank you.
I did an experiment and added the code below to the I checked and the Here's the code changes in the test: (You'll need // set "esm mode" for the project
project.package.addField("type", "module");
// add `tsx` to avoid issues with `ts-node`
project.addDevDeps("tsx");
const PROJEN_TSCONFIG_FILENAME = "tsconfig.projenrc.json";
if (project.defaultTask) {
// add a secondary projenrc-specific tsconfig file that doesn't emit JS
const projenTsconfig = new TypescriptConfig(project, {
fileName: PROJEN_TSCONFIG_FILENAME,
include: [
".projenrc.ts",
"projenrc/**/*.ts", // added by projen for tsconfig.dev - gives a place for projenrc included files
],
extends: TypescriptConfigExtends.fromTypescriptConfigs([
project.tsconfigDev,
]),
compilerOptions: {
...RESET_COMPILER_OPTIONS,
noEmit: true,
emitDeclarationOnly: false,
},
});
// adjust the projen command to:
// 1. run tsc to typecheck and syntax check the file
project.defaultTask.reset(`tsc --project ${projenTsconfig.fileName}`);
// 2. use the projenrc - specific tsconfig and tsx
project.defaultTask.exec(
`tsx --tsconfig ${projenTsconfig.fileName} .projenrc.ts`,
);
project.defaultTask.description =
"Run projen with ts-node/esm (workaround for Node 18.19+ applied)";
}
if (project.jest) {
project.testTask.env(
"NODE_OPTIONS",
"$NODE_OPTIONS --experimental-vm-modules",
);
} |
I edited the description with the new proposal. Spoiler: it's an ENUM! |
Interesting. My assumption was that |
I have been using ESM-style code in my Jsii project. The taconfig settings say what to compile to, and the package.json+some tsconfig settings tell it what it's coming from. So this might not have worked even six months ago. The tooling and compatibility just wasn't there. But things have improved rapidly. That's why I was saying the Jsii project might not even know how close they are to ESM support. And having this type of support where they can just enable it and test might help them and others update. Any thoughts on the api suggestions I edited into the original post? I can work on a PR (or multiple) over the weekend. |
Can you try and gather more feedback from the community on this RFC? |
IMO package type shall not have a direct relation with the output format. Because many tools (esp. esbuild-based ones, such as tsup) can transpile (CJS, ESM) => (CJS, ESM). |
However, the biggest problem I ran into when trying to make an ESM-compatible package, is the fact that all imports now must be imported with a import { foo } from `projen/lib/foo.js` |
And then because projen itself is not ESM, you actually cannot import it into an ESM package. The only way to make a projen-based ESM package that I found was to have a monorepo with a root projen project being CJS, and then a child monorepo package being an ESM one. If you try to combine it all into a single package, it'll not work, unless projen itself is converted into ESM. I've spent countless hours shaving this yak, and I have failed to make it work. Would be happy to help in any way I can on this one, including knowledge sharing, as I have many mental note on this problem. |
One awesome tool that helped to figure out the ESM/CJS exports mess was this one: https://github.com/arethetypeswrong/arethetypeswrong.github.io It's comprehensive and is made by one of the core TS project members. It's really not a walk in the park to get a single package to be CJS and ESM compatible without the dual package hazard. But it is possible, especially when using tools like |
Agreed. I think it would be folly to presume they are the same.
Again, agreed. Total pain. You can alleviate some of that pain with tooling, such as
I don’t believe this to be the case anymore. Not with node 18+ and the right configs (outlined above). I know it wasn’t long ago that that was absolutely the case, and this has been a rough transition period and we all have wasted so much time on the exact things your outlining, but I do believe the tooling is finally there and good enough to use. I’ll make a branch at https://github.com/10mi2/tms-projen-projects/ with the changes I outline in my earlier comment for you to play with and follow up on this comment shortly. For now, if you run |
I wish!! 😁
This is exactly the same crap I was fighting for days. I gave up. I rarely do. But this time I said it's just not worth it (yet). |
This was on Node |
Here's a clean repro inside a Docker container: docker run --rm node:20.7.0 sh -c 'cd ~ && npx --yes projen new --from @10mi2/tms-projen-projects tms-apollo-graphql-app --sample-type=pothos-prisma' |
Just tried it in Node |
I think if we really want to add ESM support to projen, we need to do the following:
Otherwise, I think we are running the risk of introducing some changes that might not be backward compatible, and/or not compatible in all Node environments. This is also fine if we decide to do so, but then we need to document those both in docs and perhaps, by setting specific baseline |
@moltar You hit the core issue in #3388 - specifically cited as a risk above. That was my code using Please try again. To ensure you get the corrected version: npx projen new --from '@10mi2/tms-projen-projects@^v0.0.33' tms-apollo-graphql-app --sample-type=pothos-prisma I was able to then: npm test
npm run start Having the GitHub pipeline run tests on multiple node versions would be great, and likely for other reasons than just ESM. |
That should be a straight forward PR. We will need matrix builds anyway to run tests against Windows. |
Yes, that works! However, the project does use import { decodeUserID } from "./users.js"; Which is, of course, a matter of pure correctness vs taste. For me, this was an unacceptable trade-off and one of the biggest annoyances that made me quit in rage this whole yak-shaving debacle. |
I don't think we'll find that to be true. Any case where we hit TypeStrong/ts-node#2094 with for sure fail (node 18.19+, ESM, and All of the CDK projects use Some projects types will not yet work with ESM in certain combinations, we expect this. PS: As promised, I made an ESM-flavored version of my
Again, I consider this more of a reason to do this, so we can all move forward fixing this type of issue. |
That's way outside the scope of this conversation, as far as I can tell that ship has fully sailed. Opinion-wise, I agree with you, it's ugly. But it's really irrelevant. All the projects the we depend on (Typescript, node, etc.) have decided that the extension should be there. I believe the general concuss was that not having the extension there was a mistake in the first place. 🤷 I see on point of value: Apparently it helps with tree-shaking, and potentially help with CJS/ESM inter as well. Found in the node docs: import './legacy-file.cjs'; There are tools that'll add the extensions, it's a one-time thing per-project. At this point I consider that a minor battle I lost (one more small scar), and move on. 😄 |
BTW, @moltar, thanks for the discussion and chipping in. It's very helpful. I think we've all been beaten by this ESM thing at least a couple of times. It was rough going there for a while, and we're not completely out of the storm of this transition, but I think there's light on the horizon. I started this RFC because I have hope, I was finally able to make Typescript projects that reliably support top-level await and other such modern capabilities, using all the standard tools like I have hope still. |
I meant that "by default", as in TDD approach. Make it fail (w/o the change) then make it pass (w/ the change).
I use It works great. But there's one nasty caveat. After Node versions v19.0.0, v18.18.0 need to use https://nodejs.org/api/cli.html#--importmodule Before that, need to use |
In general, I agree in terms of "opinion" - it is outside the scope of this. But it might not be 100% out of the scope of the project for some corner cases. Because, in some instances, SampleFile or SampleDir can produce code that uses imports. Which may then need to be adjusted to match the project type. |
A yes, I avoid using it that way in general. The whole In |
Oh yeah. The fact of the extensions needing to conditionally be added is certainly in scope. 👍 |
There are some cases I found this unavoidable. For example, using with NODE_OPTIONS="--import tsx --no-warnings" plop ... Maybe we do not have this kind of limitation with |
NodeProject
and TypeScriptProject
support for creating ESM projectsNodeProject
and TypeScriptProject
@mrgrain Would you consider that a requirement for this effort? |
I don't see it as a requirement per se, but it does seem like there are some annoying version issues to work out with ESM |
Problem: It should be trivial to create a project from
NodeProject
orTypeScriptProject
that supports ESM, because yell and scream all you want, ESM is the future. 🤓> Note: This is not about the
projen
project itself, except there are a few bugs in other tools that need to be worked around (see #3388). But I'm not suggesting that the projen code itself be converted to ESM (that would be a different issue, and probably is, I haven't looked).Proposed high-level API change:
Requirements:
NodePackage
a way to settype: module
in thepackage.json
project.package.addField("type", "module");
, but a non-escape-hatch method would be nice, see proposal aboveesbuild
,jest
,ts-jest
, andts-node
all have special configuration for ESM-modeesbuild
: The bundler controllingesbuild
now has the controls necessary, from There are no means of setting theesbuild
format to ESM #3102 and There are only a small subset ofesbuild
options exposed #3364, which is specifically support for--format
and--banner
jest
has docs explaining what's needed--experimental-vm-modules
to theNODE_OPTIONS
environment variable, currently using this escape hatch:ts-jest
has docs explaining what's neededtsJestOptions.transformOptions.useESM
to truets-node
, see When TypeScript project is configured to ESM,projen
commands getsERR_UNKNOWN_FILE_EXTENSION
#3388):ts-node
: This is the only issue that involvesprojen
itself, and it only happens in ESM mode when using node 18.19 or newer. Covered in When TypeScript project is configured to ESM,projen
commands getsERR_UNKNOWN_FILE_EXTENSION
#3388 and a workaround is described in this commentts-node
project, since it's effecting projects all over that use it, and are both modernizing their codebase and supporting the latestnode
versionstsx
, but that doesn't do type checking (this is explained in the comment on that issue, along with a workaround)All the above as tasks, from highest to lowest priority:
packageType
enum toNodePackageOptions
to support thetype
options ofESM = "module"
andCJS = "commonjs"
NodePackage
to defaultpackageType
toCJS
and settype
in thepackage.json
whenpackageType
is NOTCJS
NodePackage
to addNODE_OPTIONS=$NODE_OPTIONS --experimental-vm-modules
to thetest
task whenpackageType === ESM
TypeScriptProject
to adjustts-jest
configuration whenpackageType === ESM
esbuild
) controls for--format
and--banner
projen
commands getsERR_UNKNOWN_FILE_EXTENSION
#3388Future work, identified from discussion below, but not required to be part of this effort:
GitHub pipeline run tests on multiple node versions, with and without ESM (might be a requirement for this effort)Update: See Make GitHub pipeline run tests on multiple node versions #3499 and fix: add missing build workflow strategy matrix controls #3500 - just need to add ESM optionsThe text was updated successfully, but these errors were encountered: