Add verbatimModuleSyntax, deprecate importsNotUsedAsValues and preserveValueImports by andrewbranch · Pull Request #52203 · microsoft/TypeScript (original) (raw)

Closes #51479

This implements pretty much exactly what I proposed in #51479 so I won’t fully re-explain it here. Instead, I’ll call attention to a couple things I didn’t discuss in the proposal.

CommonJS module exports are severely limited

CommonJS JavaScript modules use require and module.exports = for importing/exporting code. TypeScript has direct equivalents for the basic forms of these:

// JS const fs = require("fs"); module.exports = {}; // TS import fs = require("fs"); export = {};

However, TypeScript lacks direct equivalents for destructuring a require or doing property assignment exports:

// JS const { writeFile } = require("fs"); module.exports.hello = "world";

It is common to approximate this behavior with ESM syntax in TypeScript:

import { writeFile } from "fs"; export const hello = "world";

But this is, in fact, ESM syntax trying to represent a CommonJS module, and is not allowed under verbatimModuleSyntax. The direct translation is:

import fs = require("fs"); // must use fs.writeFile or assign to a variable export = { hello: "world" };

We knew this would be a bit of a burden for imports, but I didn’t realize what a pain it would be for exports. The problem here is that you can no longer sidecar-export a type:

export type T = {}; export = { hello: "world" }; // ^^^^^^^^^^^^^^^^^^^^^^^^ // An export assignment cannot be used in a module with other exported elements.(2309)

To accomplish this, you’d have to merge a namespace declaration containing the types:

const _exports = { hello: "world" }; namespace _exports { export type T = {}; } export = _exports;

These limitations make it really unattractive to use the flag in CJS-emitting TypeScript. I discussed this with @DanielRosenwasser, and ultimately decided that it’s not worth worrying about too much—the flag is opt-in, and we expect the users interested in using it to write mostly ESM-emitting TypeScript.

Improved auto-imports for CommonJS imports

Despite the above, I did spend a couple minutes to make auto-imports in CJS files work a bit better. Since import { writeFile } from "fs" is not allowed in CJS files, when you start typing writeFile, you now get an auto import of fs and the qualification on writeFile at the same time:

🎥 GIF

Kapture 2023-01-11 at 16 31 39

Pending codefix

I ensured auto-imports work well with the new mode, but I haven’t yet added any codefixes or reviewed the existing ones—I think there are existing ones to add type-only annotations to imports as necessary, but I’ll likely need to add some for transforming imports between CJS and ESM syntaxes. I wanted to get this PR up so it has time to be reviewed before the beta, but I plan to add codefixes so hopefully codebases can be transitioned onto the flag with ts-fix.