-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Feature Request: allow change file extension of generated files from .ts
#49462
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
Comments
What are exact use cases and what problem it should resolve? |
Major use-case is building dual-package with |
You shouldn't be dual building packages anyways (use a wrapper), so I prefer the |
i don't wanna make files of .cts and .mts they are same context |
I've met same issue when I building dual package. e.g. https://github.com/azu/check-ends-with-period/tree/v2.0.0 (It is invalid example as dual package)
I've defined
I could not found just works solution without using bundler/post scripts. If
However, this option will need to rewrite import path of source code. It oppsite TypeScript's design goal. ... Edit(2023-01-14): I've created tsconfig-to-dual-package for avoiding this issue. |
Might I suggest packemon: https://packemon.dev/ Also, why exactly are you dual building? You run the risk of the dual package hazard: https://nodejs.org/api/packages.html#dual-package-hazard It's better to use an ES module wrapper. |
i think this only work on nodejs
|
Browsers don't support .cjs/.mjs natively, unless it gets bundled through webpack or a similar tool to .js, and at that point, why even use .cjs/.mjs for browsers? |
The idea is to use native modules when targeting browsers without any transpilation/bundling, and optionally CommonJS when targeting Node. |
Yes of course, but not if you're using .mjs. At least in @azu's example, their ESM code should be shipped to the browser with .js, and CJS code to Node.js with .cjs (or even just .js too). We also just need more information, as we're making many assumptions here. The original post doesn't contain much. |
Wouldn't we be emitting |
I'm pro @bluelovers's suggestion. The use case for me is publishing dual ESM/CommonJS packages for nodejs. There are many reasons why we need to publish dual ESM/CommonJS packages. For instance, not until 4.7, TS doesn't even allow a node application in commonjs to consume a pure ESM library due to the lack of support on |
@milesj the use of an ESM wrapper around CommonJS defeats tree-shaking, doesn't it? Given ESM has broken the whole ecosystem to try and achieve results like tree-shaking, having the recommended way to align with ESM being to throw away its core features is disappointing. Correct me if there is a reasonable way to get both. My expectation is to follow the principle that modules should be stateless - a good practice I don't ever find the need to violate. Then I understand there are no concerns with dual building. |
If you have a dual package, and some other package in CJS context requires your package, and another package in ESM context imports your package, you'll end up with 2 copies of your package. For node this doesn't matter too much unless there's some kind of global/shared state, but for bundlers this is bad. |
@azu @milesj It looks like that tsc-multi might be worth exploring cc @tommy351 #18442 (comment) |
I've created tsconfig-to-dual-package for avoiding my issue that is described in #49462 (comment)
In other words, Both(CJS and ESM) are $ tsc -p . && tsc -p ./tsconfig.cjs.json && tsconfig-to-dual-package
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# \ I want to remove it! This approch pros is that no require addtional build/transpiler tool(no modify output code of 📝 Note TypeScript may change the moduleResolution, but did not likely change the output. I feel like there is confusion everywhere about ESM support. Therefore, I thought that this is not an issue of TypeScript configuration, but rather an ecosystem-wide issue that needs to move forward. |
|
I don't get the "you shouldn't be dual building" opinion, clearly there's a lot of fragmentation in the ecosystem currently and while everyone's moving in the direction of using ESM, there's still a lot of CJS projects and that's not going to change overnight. Because of this reason if you're building a library, it's important to output both in CJS and MJS. Additionally, Thus, it makes perfect sense to me that instead of outputting results with a vague ".js" extension which causes issues and headaches, you'd want to have explicit .cjs and .mjs extensions for CommonJS and ESModule modules respectively. On top of that, outputted declaration files should have d.cjs and .d.mjs extensions respectively, cause another issue I've ran into multiple times recently is library authors outputting results in .mjs and .cjs, but leaving the declaration files with the .d.ts extension which TypeScript then won't be able to pick up on. |
What are people currently using to work around this? Separate build steps with manual file extension rewriting? |
I believe that Dual Package can be achieved broadly as follows.
|
I need this nowadays, but we may not need it in the future, so keep it silence. Let time forget it. |
I would tweak this a bit so that you get |
I think In my understanding your tweak, as you call it, would introduce a slack rule unconditionally evaluating Anyway, other than few thumbs up, I don't see any reaction that suggests the general experience will be improved any time soon. |
@RyanCavanaugh because many people use the default I'm also pretty sure @azu's I haven't seen any TS dual-package authoring tools that seem unproblematic. The popular If |
The whole reason we don't want to do dual-emit is that there doesn't seem to be an unproblematic way to do it. What can TS do that they can't? |
If you set |
@RyanCavanaugh what makes general-purpose tools for dual-emit problematic is they have to either copy the source code to a temporary directory and codemod the import path extensions before compiling, running the risk of something breaking in the temporary location, or they have to destructively modify the source code in place before compiling. Only I use a custom And it's hard to make a majority of the community aware of 3rd-party tools for this the way Modifying the import path extensions is really simple compared to most of the transpilation that And if @morganney are you talking about having both |
No, I’m talking about a build tool that runs tsc twice while updating the type in package.json and rewriting specifiers. Use whatever file extension you want. Which is more or less what you’re saying. The biggest hurdle to this being baked into tsc is that they refuse to rewrite specifiers. Hopefully with the ability to |
Right, there are edge cases (e.g. subpath imports) you can only support by rewriting specifiers, and doing that inside
If I did my research, this still requires an command line flag in Node 22, which doesn't reach end of life until April 2027, so if we want to support all users of LTS Node, we still have to dual build for another 2 1/2 years. The choice between being conservative or helping the JS ecosystem run more smoothly for those 2 1/2 years is in the TS developers' hands. |
I just encountered a situtation where I want to make the output of a |
You are already able to do this by naming your input files |
@RyanCavanaugh I'm sure you can understand that we would already be using To put it another way, why is there so much resistance to building in easier options for transpiling the same source to both CJS and ESM? Do y'all view that as a fundamentally bad idea? |
@RyanCavanaugh: sometimes there's a resistance of the developer/teams to do so. I suggest again an alternative solution which I think it would be cleaner than a brutal
|
Not as much "bad" as "impossible". It's in general not possible to take a valid ESM program and turn it into a valid CJS program, or vice versa, because dependencies can and do present arbitrarily different shapes depending on what resolution mode you're working in. In the cases where it is possible, it's only through a sort of syntax-only processing that any third-party tool can do; TS cannot add useful information to this process. We don't consider ourselves to be in the business of implementing or re-implementing every possible JS-related build transform that can exist (examples: TS does not minify, obfuscate, code-split, tree-shake, or bundle outputs), so we recommend using one of those tools if you're in a situation where a mechanical CJS<->ESM transform would work close enough for your scenario.
Why do you need See also https://www.typescriptlang.org/docs/handbook/modules/appendices/esm-cjs-interop.html#conclusions |
Oh yes, I knew it already. In a project I also ended pushing a
At the time I considered this solution as an acceptable trade-off for the inability of the compiler to unconditionally set a qualified extension. I still think that if the compiler could be told to emit a qualified extension with the rules I suggested above it would be a more elegant solution. |
So, many packages output CJS and ESM to separate directories and use a This works for importing the root of the package, but breaks down when you want to support subpath imports from your package like I mean I guess you could avoid changing extensions if you build CJS into the root directory and then ESM into a If there are any simpler ways to support all module types and resolution modes with subpath imports, I'm all ears. |
I guess you wouldn't want to provide this feature unless it were possible for it to work perfectly, but I wouldn't expect it to figure out what shape the imported module would have (since that depends on the runtime environment anyway). There are cases where I have to call an interop helper in my source code to make the transpiled ESM work in Node. I would have to call that interop helper anyway if my package were pure ESM. I just wish TS would merely transform import sources to resolved paths, since it would be cleaner and more foolproof than what build tools do. Since TS-transpiled ESM importing from CJS is subject to the limitations of how Node and other tools analyze the exports of the CJS file, it would be most reasonable to expect the same limitations if TS provided an option to transform output extensions and import paths. |
I don't understand this statement. One of two things is true:
|
@jedwards1211 all module resolutions except TS will never rewrite extensions or imports so finding a third-party tool is your best bet. |
@morganney isn't classic still the default from |
@RyanCavanaugh sorry it wasn't clear, no module resolution isn't trivial. But it's not what generally causes problems for dual builds -- usually CJS module shapes when imported into ESM causes the problems, as you noted. What I mean is not "foolproof" about third party dual build tools is the fact that they either have to temporarily overwrite the source files before calling |
Right, and what you're proposing - that TS have some notion of how to rewrite import specifiers - makes this problem worse, not better. There's a rich and excellent ecosystem of other tools that can transform TS to JS, many of which can do their jobs very correctly without needing to import/replicate TS, and making the runtime behavior of an output TS program dependent on TS's resolution semantics would make this tools' jobs much much harder. |
@RyanCavanaugh I'm talking about the fact that if TS adds a new syntax in the future, third party tools will lag behind in their support of it. Or if TS changes its internal API, third party tools that use it would suddenly break. Can you understand how that feels unnerving? And it would feel more secure to only have to depend on tsc? Just because there are a lot of good tools out there doesn't make the situation close to ideal |
Accept that ESM and CJS are not generally compatible. For instance no live bindings or TLA in CJS. Then do this:
Have to build twice but now you’re in sync with the current state of tsc api. But, be aware of some issues with how tsc handles differences between module systems. There are cases where the compiler won't error, but the runtime does: #58658 |
Suggestion
🔍 Search Terms
List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.
✅ Viability Checklist
My suggestion meets these guidelines:
⭐ Suggestion
add something like
📃 Motivating Example
targetExtension
is.cjs
, all.ts
will emit as.cjs
, but.mts
still is emit as.mjs
targetExtension
is.mjs
, all.ts
will emit as.mjs
, but.cts
still is emit as.cjs
💻 Use Cases
The text was updated successfully, but these errors were encountered: