isaacs / tshy

Other
869 stars 17 forks source link

Proposal: `"sourceDialects"` #66

Closed colinhacks closed 3 months ago

colinhacks commented 4 months ago

This is a second proposal to solve the core problem described here: https://github.com/isaacs/tshy/issues/65. Namely, I'm trying to construct a TypeScript monorepo with "live types" across package boundaries.

To do this, I'm now attempting to a custom "typescript" export condition that points to my TypeScript source code. I can then configure compilerOptions.customConditions such that TypeScript looks at the raw .ts source of my local packages in development instead of the .d.ts artifacts.

My proposal for achieving this is a "sourceDialects" field under "tshy".

  "tshy": {
    "exports": {
      "./package.json": "./package.json",
      ".": "./src/index.ts"
    },
    "sourceDialects": [ "ts" ] 
  },

This would add a reference to the original entrypoint under the listed conditions:

{
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "import": {
        "source": "./src/index.ts",
        "types": "./dist/esm/index.d.ts",
        "default": "./dist/esm/index.js"
      },
      "require": {
        "source": "./src/index.ts",
        "types": "./dist/commonjs/index.d.ts",
        "default": "./dist/commonjs/index.js"
      },
    }
  }
}

Other naming proposals:

As before, happy to put in this PR but looking for a sign off & naming thoughts first.

colinhacks commented 3 months ago

Even with the new support for the "source" export condition, there are still cases where it would be useful to specify a custom condition name using a "sourceDialects" config.

If more and more packages start getting published with a "source" condition in their package.json, it gets harder to use "source" to enable live-updating types in local mono repo development. The idea with custom conditions is that you can use them to make TypeScript resolve local workspace imports ("pkg-a") to their local source code (./packages/pkg-a/src/index.ts). To do this you need to set customConditions in your tsconfig

{
  "compilerOptions": { 
    "customConditions": ["source"] 
  }
}

Now the TS server will use "source" to resolve "exports" according to Node.js resolution rules. The problem is, this now happens both for local workspace dependencies and "real" dependencies. If more and more packages start publishing with "source", TypeScript will read from "source" instead of looking at "types", which is a lot more expensive for a couple reasons:

  1. It has to parse all the source code
  2. It has to run typechecking to determine any inferred return types, etc
  3. Any "go to definition" will open the .ts source code of the imported package, but if the local project's tsconfig doesn't match the package's tsconfig, there will likely be TypeScript errors: https://twitter.com/ssalbdivad/status/1799185166524682737

The recommended solution from @andarist is to always use a specific condition name that is unique to your monorepo: https://twitter.com/AndaristRake/status/1799454720580342097 So for instance I could use "@zod/source". The idea is to use the special condition in all my workspaces' package.json files. I would then have a special export condition that would apply in my own workspace packages, but not to any external dependencies in node_modules (which wouldn't have it defined.

// tsconfig.json
{
  "compilerOptions": { 
    "customConditions": ["@zod/source"] 
  }
}
// package.json
{
  "tshy": {
    "sourceDialects": ["@zod/source"]
  }
}

I still think a default "source" condition is extremely valuable as a way to move the ecosystem towards a standard/conventional way to publish raw source files. And it's existence makes the "sourceDialects" config name quite sensible, I think. But as it turns out, it's not an ideal solution to the problem of "live types" monorepo development.

isaacs commented 3 months ago

Ahh, yes, dependencies. You won't want their source, usually. It might work a lot of the time, but not always, so that's a footgun.