cefn
cefn

Reputation: 3341

Resolving packages directly to Typescript source in monorepo package.json files

What is the best answer for configuring package.json within an npm monorepo, (e.g. the "main", "module" or "exports" field), so that packages referenced locally can resolve directly to their .ts files within IDE and build tooling, (vscode, tsx, ts-node, vite, jest, tsc etc.)?

I don't want local Typescript-based tasks (bundling, ts-jest testing, debugging) resolving to local transpiled .js files from dist or to the .d.ts artefacts alongside them. These will often be out of date if the author hasn't remembered to re-run the build after editing some .ts files in src, or to have a permanent watch procedure in place. Rather those tasks should resolve directly to the original .ts files

I have created a reference starter monorepo to try and understand how to properly point to Typescript source files within an npm-compatible package.json (not pnpm). This link takes you to an example package.json for "@starter/multiply" which references "@starter/sum" from within the same repo to prove out local resolutions in vscode, tsc, vitest, jest.

Until this recent attempt, I have been relying on pnpm tooling which explicitly allows the locally-defined "main" field of package.json to be overridden during publishing.

In the pnpm approach "main" is permanently set to "src/index.ts" within the repo, which works amazingly during local development and main is swapped out to point to "dist/index.cjs" later in the release process to make Node, CommonJS and bundlers happy.

I don't know if there is a preferred approach to achieve something similar with npm. Npm doesn't have publishConfig override support for the main field. I don't know if there is a npm postpack step where I can script changes to the tar, maybe, so that I can have my "main" set permanently to src/index.ts locally, but it ends up pointing to .js by changing it just before it's published.

The reference npm monorepo I linked to is functional (tests pass, there are cached build tasks, you can debug with sourcemapping and so on) but the actual loaded source is not the .ts files, even within the monorepo. Instead main resolves to src/index.mjs that in turn imports from dist/index.js which has sourcemapping back to src/index.ts. So there is a typical issue when the background build hasn't been re-run so the .js files are out of date with the .ts and therefore Typescript code changes have no effect.

I raised a related ticket at https://github.com/microsoft/TypeScript/issues/51750 to propose a solution, but I keep wondering if there already is one conventional approach that I've missed, or some trick that people are using out there.

Upvotes: 9

Views: 4393

Answers (1)

cefn
cefn

Reputation: 3341

OK, since https://github.com/cefn/starter/commit/c74cb50ea67aa171fadbd84347b92b1fb8a49f1a it looks like the configuration achieves what is needed.

All conventional package.json "main" "module" and "exports" fields are pointing appropriately to transpiled source artefacts suited for npm publishing (e.g. ./dist/index.cjs and ./dist/index.mjs) and there are no references in any package.json to index.ts.

Nevertheless cding to packages/multiply you get the result below as hoped. At least tsc is able to resolve the package to its src/index.ts (which is immediately alongside the src/index.mjs alias). So if that's the case, then anything that uses tsc (or conforms to tsc) should hopefully be able to do the same.

packages/multiply]$ npx tsc --traceResolution | grep '@starter/sum' | more
======== Resolving module '@starter/sum' from '/home/cefn/Documents/github/starter/packages/multiply/src/index.ts'. ========
======== Module name '@starter/sum' was successfully resolved to '/home/cefn/Documents/github/starter/packages/sum/src/index.ts'. ========

Probably it's mostly the work of the top level shared tsconfig.json paths resolution at https://github.com/cefn/starter/blob/c74cb50ea67aa171fadbd84347b92b1fb8a49f1a/tsconfig.json#L103-L105 so maybe there will be important exceptions if e.g. esbuild or jest don't respect the tsconfig resolution for any reason and use node's package.json resolutions instead.

I won't mark this as the answer immediately since I'm sure there are others with insights that can be shared around exceptions and potential problems.

Upvotes: 1

Related Questions