Repository: TypeScript
Issue: Design Meeting Notes, 5/28/2024
Labels: Design Notes


# Options for `import cjs from "cjs"` and `require(esm)`

https://github.com/microsoft/TypeScript/issues/54102

* Two options
    * Option 1: `moduleFormatInterop` - https://github.com/microsoft/TypeScript/pull/58480
    * Option 2: `defaultIsModuleExports` and `allowRequireESM` - https://github.com/microsoft/TypeScript/pull/58526
* What is the motivating example?

  ```ts
  // package.json
  {
      "type": "module"
  }

  // index.js
  import pkg from "./vendor/pkg/index.js";
  console.log(pkg);

  // vendor/pkg/index.js
  module.exports.__esModule = true;
  module.exports.default = "pkg";    
  ```
* So there is a divergence here.
    * Lots of bundlers say this just echoes the string `pkg` to the console.
    * esbuild and Webpack do the "Node-specific" behavior by saying that the entirety of `vendor/pkg/index.js` is brought in as a `default` import, and itself is an object with a `default` property. So it logs either `[Object object]` or `{ __esModule: true, default: "pkg" }`.
        * Also, the behavior for Webpack differs between JavaScript and TypeScript.
* Rollup makes this all configurable based on the build, and uses a flag called `defaultIsModuleExports` to control the import cjs from "cjs"` behavior.
    * That's the option name we have in option 2.
* What don't we like?
    * It isn't clear how much impact there is - not a ton of reactions, and linked issues seem unrelated.
        * But if you're bundling, you might not hit this because `"type": "module"` often isn't specified (or people don't use `.mts`).
            * We've even told people not to turn it on because bundlers differ in behavior.
        * Could clarify a bit on how the code ended up in this state.
    * There's also a concern around complexity - more flags.
    * Also, discoverability - how do you know that you need to set it?
        * Also, lots of examples where libraries are resolved as ESM in bundler, but the types don't indicate that.
        * Better error message to say "if you're really trying to do this, you need this flag."
* Could imagine this just being a granular flag for a bigger module flag that specifies a specific behavior for esbuild, swc, etc.
    * If we had a plan there, maybe we'd feel a lot better doing this.
* What if we took the opposite stance - turn these on and people need to turn the flags off.
    * Arguably, every bundler tries to align with Node, so the node-bundler behavior feels like what people want, right?
    * It's hard to fully convince ourselves there. e.g. Bun decided not to adopt the esbuild/webpack behavior, so maybe it's not as clear-cut as we'd want.
* We would like to hold off on this - both awaiting more feedback and want to understand how the original scenarios were hit.


# Checking of string literal union object keys and template types

https://github.com/microsoft/TypeScript/issues/58673

* Union types of computed properties get smooshed into a string index signature.
* Would imagine `{ [... as "x" | "y"]: string }` would be equivalent to `{ x: string } | { y: string }`.
* What about template types?
    * ``{ [... as `foo${string}`]: string }`` - should this be equivalent to ``{ [props: `foo${string}`]: string }``?
    * Uhhh.
* Arguably, it feels rare that you want to get an index signature because you end up with a way-too-permissive object.
    * Only solved via `noUncheckedIndexedAccess`.
* Another perspective - you say that every constant-y value gets a unique type for indexing.
* A few different treatments.

  ```ts
  declare const xy: "x" | "y";
  
  const x1 = {
      [xy]: 123,
  };
  
  // could be
  
  const x1: { [props: string]: number }; // today's behavior
  
  // or
  
  const x1: { x: number } | { y: number };
  
  // or
  
  const x1: { x?: number, y?: number };
  
  // or
  
  const x1: { x: number, y?: number } | { x?: number, y: number };

  // or something where where an index signature actually acts differently,
  // defers some of its computation for different behavior of reading/writing.

  const x1: { [props: "x" | "y"]: number };
  ```
* Today, the last example just desugars to `{ x: number, y: number }` because literal types desugar into individual properties.
    * That largely aligns behavior with mapped types.
* And there's questions of overlap etc., but we have index signature applicability rules, so maybe there's something there.