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.

Add compiler option to allow module types to be checked in non module output files

See original GitHub issue

Search Terms: import type, export {}

Suggestion

In #41513 and #41562 projects are broken by a design change to always include export {}; in the JS output if import or import type is in the TS source, regardless of whether it is in the JS output. This is a breaking change for a lot of developers and painful to fix (and by fix I mean practically get the output of our TS project to run in browsers, not necessarily adhere to ECMA spec compliance and best possible practice).

However, export {}; considered desirable:

You’re free to suggest a new compiler option but this is the intentional behavior. _Originally posted by @RyanCavanaugh in https://github.com/microsoft/TypeScript/issues/41513#issuecomment-727057724_

Please could we have a new compiler option to not force all files that in any way use ES modules to always have to include export {}; in the JS output.

I’m not sure of the best way for this to be applied, but the goal would be

  • Type checking compatible with an ESM project.
  • Output compatible with the current state of module support in browsers.

This could be:

  • A compiler option that causes import type to not implicitly convert output to a module, (my preference because it still avoids any output JS having import without export, which caused #38696) or
  • A compiler option to not add export {}; to all modules, or
  • A //@directive we add to a file to express our intent to not output a module, or
  • A new reference type ... TS-only syntax to signal that we want to use the definition but not output a module

Whatever is easiest to code and causes least friction/confusion for the community.

Use Cases

I have a large TS project that uses numerous web workers, a service worker and web components that load side effects.

In the first two cases including export {}; breaks the resulting JS, as these workers are not modules and are not intended to produce module loaded JS output.

In the case of side effect web components no export is expected (they use customElements.define) so it’s just wasted bytes, but it doesn’t break anything. Across a project with a lot of components the many export {}; that will never be referenced by anything adds up.

In addition during migration between different reference types it may be extremely beneficial to not strictly enforce adherence to one type or the other, at least while not in production. Any modules = all must be modules effectively makes this migration harder, even if it is a sound best practice.

Examples

I have a model MyModel.d.ts.

In worker.ts I want TS to check my code against this model:

import type MyModel from './MyModel';
const test: MyModel = {};
test.propertyInModel = 1; // works and has intellisense
// test.propertyNotInModel = 1; throws compile error!

I want to use the JS output this with a worker in another file:

const worker = new Worker('path/worker.js');

This worked in 3.8, but fails in 4.0 due to #41562

Checklist

My suggestion meets these guidelines:

  • This wouldn’t be a breaking change in existing TypeScript/JavaScript code (it would fix a lot of projects broken by 4.0)
  • This wouldn’t change the runtime behaviour of existing JavaScript code (it would be opt-in)
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.) (we’re removing additional content that wasn’t in 3.8)
  • This feature would agree with the rest of TypeScript’s Design Goals. (particularly 4, 7 and 11)

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:17
  • Comments:26 (9 by maintainers)

github_iconTop GitHub Comments

5reactions
jon49commented, Nov 24, 2021

I don’t know what was decided to do with this. But according to this issue 16577:

  • We are not going to implement features, even under a commandline flag, that imply changing JS semantics by rewriting it during emit …
  • We are 100% committed to not rewriting JavaScript code as part of non-downleveling compilation. This is how everything else in TS works; see also this comment in a related feature request, and this comment in a related feature request. We are not going to implement features, even under a commandline flag, that imply changing JS semantics by rewriting it during emit

And another:

Just like for all other things, you should write the JS the way it should be at runtime, and tell TS how to interpret that.

So, if we are to believe those comments then the export {} value should not be created during compilation. If someone needs export {} in their code after TypeScript processing they should add it themselves. This breaks the behavior of the TypeScript that is written and is surprising to the user. The browser understands that a file is a module with no regard to having export {} there or not as that is defined elsewhere (in the script tag).

So, considering what was said on other threads about not changing the JavaScript. This change in TypeScript breaks it promise that we can just write the JS the way it should be at runtime, and tell TS how to interpret that.

5reactions
KeithHenrycommented, Nov 23, 2020

@andrewbranch I really like this idea! module=none removing all import and export from the emitted JS makes loads more sense than treating CommonJS as default. It always seemed weird to me that specifying module=none would create JS that wouldn’t run in any browsers, it really should output vanilla JS.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - tsc CLI Options - TypeScript
Flag Type Default ‑‑allowJs boolean false ‑‑allowUmdGlobalAccess boolean false ‑‑allowUnreachableCode boolean
Read more >
Compiler Options - TypeScript
Option Type Default ‑‑allowJs boolean false ‑‑allowSyntheticDefaultImports boolean module === "system" or ‑‑esModuleInterop ‑‑allowUmdGlobalAccess boolean false
Read more >
Configuring TypeScript compiler - inDepthDev
... TypeScript configuration options. We'll learn how to configure input and output files location, file types, transpiling and module resolution process.
Read more >
The --strict Compiler Option in TypeScript - Marius Schulz
The idea is that you opt into a strict-by-default mode so that you enjoy all the benefits of better type safety without having...
Read more >
Compiler Options - ClojureScript
If set to :warn or :error , checks inferred types and runtime values passed ... A module needs a name, an individual :output-to...
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