question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Proposal: deprecate `importsNotUsedAsValues` and `preserveValueImports` in favor of single flag

See original GitHub issue

Background: importsNotUsedAsValues

importsNotUsedAsValues was introduced alongside type-only imports in https://github.com/microsoft/TypeScript/pull/35200 as a way to control import elision. In particular, Angular users often experienced runtime errors due to the unintended import elision in files like:

import { MyService } from './MyService';

class MyComponent {
  constructor(private myService: MyService) { }
}

It appears to TypeScript as if the import declaration can be elided from the JS emit, but the ./MyService module contained order-sensitive side effects. By setting the new --importsNotUsedAsValues flag to preserve, import declarations would not be elided, and the module loading order and side effects could be preserved. Type-only imports could then be used to elide specific import declarations.

Background: preserveValueImports

preserveValueImports was added in https://github.com/microsoft/TypeScript/pull/44619 as a way to control elision of individual imported names so that symbols can be referenced from places TypeScript cannot analyze, like eval statements or Vue templates:

import { doSomething } from "./module";

eval("doSomething()");

Under default compiler options, the entire import statement is removed, so the eval’d code fails. Under --importsNotUsedAsValues preserve, the import declaration is preserved as import "./module" since the flag is only concerned with module loading order and potential side effects that may be contained in "./module". Under the new --preserveValueImports option, doSomething would be preserved even though the compiler thinks it is unused.

In the same release, the ability to mark individual import specifiers as type-only was added as a complement to --preserveValueImports.

User feedback

These two flags, along with type-only import syntax, were designed to solve fairly niche problems. Early on, I encouraged users not to use type-only imports unless they were facing one of those problems. But as soon as they were available, and consistently since then, we have seen enthusiasm for adopting type-only imports everywhere possible as an explicit marker of what imports will survive compilation to JS. But since the flags were not designed to support that kind of usage of type-only imports, the enthusiasm has been accompanied by confusion around the configuration space and frustration that auto-imports, error checking, and emit don’t align with users’ mental model of type-only imports.

Further, because the two flags were designed at different times to address different issues, they interact with each other (and with isolatedModules) in ways that are difficult to explain without diving into the background of each flag and the narrow problems they were intended to solve. And the flag names do nothing to clear up this confusion.

Proposal

We can solve the problems addressed by importsNotUsedAsValues and preserveValueImports with a single flag that is

  • easier to explain
  • less complex to implement
  • well-suited to users who want to use type-only imports for stylistic reasons

On the schedule of https://github.com/microsoft/TypeScript/issues/51000, I propose deprecating importsNotUsedAsValues and preserveValueImports, and replacing them with a single flag called (bikesheddable) verbatimModuleSyntax. The effect of verbatimModuleSyntax can be described very simply:

verbatimModuleSyntax: Emits imports and exports to JS outputs exactly as written in input files, minus anything marked as type-only. Includes checks to ensure the resulting output will be valid.

No elision without type

This is a stricter setting than either importsNotUsedAsValues or preserveValueImports (though it’s approximately what you get by combining both with isolatedModules), because it requires that all types be marked as type-only. For example:

import { writeFile, WriteFileOptions } from "fs";

would be an error in --verbatimModuleSyntax because WriteFileOptions is only a type, so would be a runtime error if emitted to JS. This import would have to be written

import { writeFile, type WriteFileOptions } from "fs";

No transformations between module systems

True to its name, verbatimModuleSyntax has another consequence: ESM syntax cannot be used in files that will emit CommonJS syntax. For example:

import { writeFile } from "fs";

This import is legal under --module esnext, but an error in --module commonjs. (In node16 and nodenext, it depends on the file extension and/or the package.json "type" field.) If the file is determined to be a CommonJS module at emit by any of these settings, it must be written as

import fs = require("fs");

instead. Many users have the impression that this syntax is legacy or deprecated, but that’s not the case. It accurately reflects that the output will use a require statement, instead of obscuring the output behind layers of transformations and interop helpers. I think using this syntax is particularly valuable in .cts files under --module nodenext, because in Node’s module system, imports and requires have markedly different semantics, and actually writing out require helps you understand when and why you can’t require an ES module—it’s easier to lose track of this when your require is disguised as an ESM import in the source file.

Issue Analytics

  • State:open
  • Created 10 months ago
  • Reactions:8
  • Comments:8 (6 by maintainers)

github_iconTop GitHub Comments

3reactions
andrewbranchcommented, Dec 2, 2022

We agreed to move forward with this.

  • The flag will be called verbatimModuleSyntax, just because it’s more understandable than isolatedModules. It will imply/supersede isolatesModules.
  • We would like to adopt the flag in our own codebase, but I think that may not be practical until/unless we can move off --module commonjs (I think @jakebailey has investigated this a bit, but I don’t recall the conclusion)
  • We should make sure typescript-eslint can identify imports that can be import type but are not (the functionality we lose from --importsNotUsedAsValues error)
  • The codefix/organize imports/auto imports experience needs to be really good.
1reaction
andrewbranchcommented, Dec 5, 2022

Yeah, I think that’s reasonable.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - TypeScript 3.8
This flag takes 3 different values: remove : this is today's behavior of dropping these imports. It's going to continue to be the...
Read more >
Content Types - ESBuild
Modern bundlers contain an optimization called "scope hoisting" that merges all bundled files into a single file and renames variables to avoid name...
Read more >
@netlify/esbuild-darwin-arm64 | Yarn - Package Manager
The two tsconfig.json settings importsNotUsedAsValues and preserveValueImports let you customize this. ... Add support for the "import assertions" proposal.
Read more >
express build directory needs to go a folder above
Specify a file that bundles all outputs into one JavaScript file. ... "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior ...
Read more >
RFC: proposal to deprecate --record flag - Google Groups
of deprecating --record flag which is used to record invoked command and its arguments in object annotations.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found