Repository navigation
Using "allowJs": true and "module": "commonjs" to transform .mjs files should emit .cjs files #54573
Description
Activity
I did a bit of digging and I believe this is the piece of code responsible for determining the extension of the output file:
TypeScript/src/compiler/emitter.ts
Lines 574 to 580 in e49a15f
export function getOutputExtension(fileName: string, options: CompilerOptions): Extension { return fileExtensionIs(fileName, Extension.Json) ? Extension.Json : options.jsx === JsxEmit.Preserve && fileExtensionIsOneOf(fileName, [Extension.Jsx, Extension.Tsx]) ? Extension.Jsx : fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Mjs]) ? Extension.Mjs : fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs]) ? Extension.Cjs : Extension.Js; } Related, I found these minutes from a design meeting on the feature to support
.mjsand.cjsas input files.
Also this issue seem tangental, although I understand it more about the file extension for.tssource files.DanielRosenwasser commented
on Jun 9, 2023 MemberMore actionsI think that #35148 (and #35589) is related; however, I don't recall whether that also did path rewriting - and we didn't have the context of #44442 at the time.
As you pointed out, in #44442 we discussed the issue of
.mjsas an input file under"module": "commonjs"being a weird "undefined behavior" of the compiler. I'm still not sure if the right thing to do here is to convert to.cjsin the output. I could kind of see that, but I don't really know if we have the appetite for a new compiler option to solve this case (as in your PR at #54583).Is there any reason you don't want to just use
.jsas an input file extension?I agree the behavior is bad, but I think the solution we want is either
- .mts files cannot be loaded into a program with
--module commonjs(issue an error), or - .mts files in
--module commonjsoverride themodulesetting and emit with ESM syntax
Reacted by Ryan Cavanaugh- .mts files cannot be loaded into a program with
Is there any reason you don't want to just use .js as an input file extension?
Yes. Since the package has
"type": "commonjs"it would be wrong for the ESM source-file to have a.jsextension, which could trip up static analysis tools and linters relying on the semantics of these extensions in relation to the package.json'stype.Andrew Branch (@andrewbranch) If using
.mtsinstead of.tsis mainly a way to control the output file extension, I can see why that would make sense and would prefer an error instead of an implicit setting override.I assume both suggestions would also apply to
.mjsfiles and I'd expect that to remove the ability to transpile.mjsto commonjs? I think that's a powerful feature, which would be great the the compiler would continue to support, although admittedly on the edge of use-cases that are expected from a TypeScript compiler.RyanCavanaugh commented
on Jun 12, 2023 MemberMore actionsIf the code is intended to always target CJS, it should just be a cts file.
A naive syntactic transform from ESM to CJS is liable to end up importing semantically different modules from when the code was written, causing unpredictable breaks. This isn't something that you can "just" do in the general case.
Reacted by Andrew Branch- addedPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some casesThe current behavior isn't wrong, but it's possible to see that it might be better in some cases
on Jun 12, 2023 RyanCavanaugh commented
on Jun 12, 2023 MemberMore actionsTagging as backlog for either of the proposed behaviors, not the behavior described in the OP
Converting
.mtsfiles to anything other than ESM is bad and breaks things. Specifying an.mtsextension should indicate that this files module system is fixed as ESM without exception and regardless of the--modulesetting.This is the correct approach.
.mts files in --module commonjs override the module setting and emit with ESM syntax
Please fix this.
The javascript.mjs is transformed to CommonJS (as expected)
Why would that be expected? If you specify a file as
.mjsthen it should always be an ES module.tscconverting that to CJS is just wrong..mts files cannot be loaded into a program with --module commonjs (issue an error)
Yes, a compiler error, not user error.
Any
.mts, or.cts(same for the JS variants) should not have its module system changed. Ever.Also, why was this labelled Possible Improvement and not a bug?
The javascript.mjs is transformed to CommonJS (as expected)
Why would that be expected?
Because the tsconfig has
moduleset to"commonjs"in the example I provided, I'd expect all files emitted to be using that, regardless of the module system used by the input file.This is where you and TypeScript differ with the way the Node.js runtime operates against file extensions. Too bad.
Congrats on your newborn.
Reacted by Jake BaileyReacted by Kræn Hansen- addedDomain: Node ESMLike ES Modules, but specific to Node.js support (cts, cts, mjs, mts)Like ES Modules, but specific to Node.js support (cts, cts, mjs, mts)
on Oct 16, 2025
Bug Report
🔎 Search Terms
CommonJS, allowJS, ESM, CJS, file extensions
🕗 Version & Regression Information
⏯ Playground Link
Sandbox link with relevant code (I couldn't use the playground, since this involves using an .mjs file).
💻 Code
🙁 Actual behavior
The
javascript.mjsis transformed to CommonJS (as expected), but the file extension of the emitted file is still.mjs.This breaks the package, since
.mjsis supposed to be use exclusively for JavaScript using ESM and the emitted file now uses CommonJS.🙂 Expected behavior
I would expect
tscto transform thesrc/javascript.mjsto CommonJS and either:dist/javascript.cjsor alternatively"type": "commonjs"in the package.json and emit the file as.jsas it does with thesrc/typescript.tsfile.