TypeScript
                                
                                 TypeScript copied to clipboard
                                
                                    TypeScript copied to clipboard
                            
                            
                            
                        Typescript [4.8.2] is adding invalid javascript for *.cjs files
Bug Report
My project is set to emit ESNext modules. I have a variety of TS files, but I also have a single .cjs file, which needs to remain CommonJS to try and require legacy packages.
My input looks like this:
module.exports = {
  tryRequire(modulePath) {
    return require(modulePath);
  },
};
I expect the output I get from TypeScript 4.7.4:
module.exports = {
  tryRequire(modulePath) {
    return require(modulePath);
  },
};
When I upgrade to TypeScript 4.8.2, it now emits an extra export {} at the end of the .cjs file:
module.exports = {
  tryRequire(modulePath) {
    return require(modulePath);
  },
};
export {}
This causes Node to fail parsing the .cjs file because export does not exist in the .cjs context.
🕗 Version & Regression Information
Worked in 4.7.4, broken in 4.8.2, also broken in current nightly (4.9.0-dev.20220905).
Repro here
https://github.com/dzearing/ts-repro-cjs
--module esnext combined with --moduleResolution nodenext is not super supported... at the very least, it’s playing the game on hard mode. I’m not quite sure why it “worked” in 4.7 or what we should actually be doing in this case, but I’m assuming it works with --module nodenext? That’s the only mode where we actually support emitting some files as ESM and some as CJS.
Yes! That fixed it. I changed to module: "nodenext" and added type: "module" to the package.json to achieve the expected output (ESM by default, CJS for cjs files.)
I am unblocked here. I do still think it's not ideal to be emitting invalid cjs but at least I have a workaround. :)
Agreed. Also related to https://github.com/microsoft/TypeScript/issues/48854. @weswigham thoughts?
I ran into this issue with exactly the same scenario - ESM module with a single .cjs file. But in my case, my tsconfig didn't have any value set for "module" at all, and TS 4.7.4 was totally happy with whatever it was using by default. So it was really unexpected to see the invalid javascript emitted by TS 4.8.2
https://github.com/igordanchenko/sandbox/tree/typescript-50647/main
Well, I spoke too soon. Setting "module" to "nodenext" did not resolve my issue. It did fix the .cjs file, but now all .ts files are emitted as CommonJS (event though the package.json "type" is set to "module"). I tried setting "moduleResolution" to "nodenext", but tsc compilation completely fails in this case.
index.ts:3:43 - error TS2349: This expression is not callable.
  Type 'typeof import(".../node_modules/clsx/clsx")' has no call signatures.
Any advise will be appreciated.
Here is a minimal repro - https://github.com/igordanchenko/sandbox/tree/typescript-50647/nodenext
Have the same issue. Worked on 4.7.4, doesn't work now on 4.8.4 / 4.9.x-rc. Combination of module: "nodenext" and type: "module" doesn't work - compiler starts to complain regarding to import.meta usages in ESM files.
In my use case I need to have a index.cjs (in commonjs) and everything else in ESM (due to commonjs entrypoint requirements in Electron). But now, when index.cjs adds export {} it doesn't work.
But now, when
index.cjsaddsexport {}it doesn't work.
Got the same problem, anyone knows how to avoid TypeScript appending the empty export to .csj files? Does it still on version 4.9.3. It's just the last little issue allowing me to easily mix ESM and a single CommonJS file (auto-generated) in same project. I'll fix it with a manual copy or replace, but would be nice to avoid that.
@sondreb I'm investigating issues with this now in version 4.9.3.  Bumping from 4.7.x to 4.9.x indeed broke the compiled output by injecting export {}; into a .cjs file (generated GRPC output).
@pleerock
In my use case I need to have a
index.cjs(in commonjs) and everything else in ESM (due to commonjs entrypoint requirements in Electron). But now, whenindex.cjsaddsexport {}it doesn't work.
If it expects cjs, you can't reference ESM code under it. CJS can only import ESM using dynamic import.
You have to compile to CJS. You can't use type: module.
Any updates on this? Experiencing the same issue where an empty export {} gets appended to the outputted js when compile a .cts file. My package.json has type: module set, and my tsconfig.json has module: esnext and moduleResolution: node. Like @igordanchenko, my tsc fails to compile with the exact same error when switching to module: nodenext.
.cts file extensions in module: esnext is a contradiction, and there are no plans to make it meaningful at the moment—if anything, it should probably be a program-level error. If you’re running in Node, you need to use --module nodenext and fix the errors. The errors aren’t bugs, they’re real issues that need to be addressed if you expect your code to run safely in Node. For example, this:
index.ts:3:43 - error TS2349: This expression is not callable. Type 'typeof import(".../node_modules/clsx/clsx")' has no call signatures.
probably means that you need to add a .default on the end of your dynamic import of a CJS module, because that’s what’s actually required in Node. If you run the JS files produced by tsc in Node, you must use node16 or nodenext for module and moduleResolution. Anything else is 100% incorrect and you will experience problems.
Ah thank you! You're right; turns out I needed to import clsx as:
import { clsx } from "clsx";
My ts build now works with module: nodenext, and the empty export is gone.
@slimshreydy, nice catch! Importing the named export indeed solves clsx issue.
@andrewbranch, do you have any thoughts on why importing the default clsx export isn't working under "moduleResolution": "NodeNext"?
import clsx from "clsx";
src/index.tsx:5:21 - error TS2349: This expression is not callable.
  Type 'typeof import("[REDACTED]/node_modules/clsx/clsx")' has no call signatures.
5     <div className={clsx("fancy", "div")}>{children}</div>;
                      ~~~~
Found 1 error in src/index.tsx:5
Here is a minimal repro - https://github.com/igordanchenko/sandbox/tree/typescript-50647/nodenext-1
Because its typings are wrong. clsx is a CJS library, but its typings say export default. https://unpkg.com/browse/[email protected]/clsx.d.ts
@weswigham honestly, I think we should consider making export default in CJS-scoped declaration files an error under node16/nodenext, with a message that says the library is probably wrong, similar to #52173. That’s a super common mistake that can often be overlooked in --moduleResolution node.
FYI, the "module": "nodenext" solution does not work if you also need "moduleResolution": "bundler". In my case I'm using bundler because it matches the behavior of my node loader (extensionless and subpath exports).
Option 'bundler' can only be used when 'module' is set to 'es2015' or later.
The compiler is busted in its handling of the new file extension .mts in combination with --module commonjs. You can see my extended rant in the duplicate issue #51990.
This should have been closed by #54567
Well, maybe not until --module commonjs and --module esnext report an error on a file in a conflicting format.
I just ran into this problem with TS 5.3:
Input:
// eslint-disable-next-line import/no-extraneous-dependencies -- This isn't included in the bundle
import { Configuration } from "webpack";
const { resolve } = require("path");
const config: Configuration = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules|\.d\.ts$/,
      },
    ],
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"],
  },
  output: {
    filename: "bundle.js",
    path: resolve(__dirname, "dist"),
    library: "bbcode_ast",
  },
  devtool: "source-map",
};
module.exports = config;
Output:
const { resolve } = require("path");
const config = {
    entry: "./src/index.ts",
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: "ts-loader",
                exclude: /node_modules|\.d\.ts$/,
            },
        ],
    },
    resolve: {
        extensions: [".tsx", ".ts", ".js"],
    },
    output: {
        filename: "bundle.js",
        path: resolve(__dirname, "dist"),
        library: "bbcode_ast",
    },
    devtool: "source-map",
};
module.exports = config;
export {};
//# sourceMappingURL=webpack.config.cjs.map
This was backed out of 5.5—hoping to reintroduce a fix in 5.6 via #58825.