diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a04a0c..2c64785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.18 + +- Add `sourceDialects` + # 1.17 - Add `module` field if a top-level `esm` export exists for diff --git a/README.md b/README.md index 472ae33..1989255 100644 --- a/README.md +++ b/README.md @@ -874,10 +874,12 @@ for code hints in VSCode, neovim, tests, etc. ### Loading from Source -If you are using tshy in a monorepo environment, you can -configure TypeScript (and other tools) to resolve to the _source_ -files rather than built artifacts by telling them to use the -`"source"` export condition. +To facilitate jumping directly to source definitions in some +tools, you can define custom `"sourceDialects"` which will be +resolved to the original TypeScript source. These custom dialects +can then be configured to allow build tools (such as tsc) and +editors (such as VS Code and neovim/CoC) to jump directly to +source definitions. For example, in editors such as VS Code and neovim/CoC that use the TypeScript language services, you can give them a `tsconfig` @@ -886,7 +888,17 @@ that contains this: ```json { "compilerOptions": { - "customConditions": ["source"] + "customConditions": ["@my-project/source"] + } +} +``` + +And then add this to your `package.json` file: + +```json +{ + "tshy": { + "sourceDialects": ["@my-project/source"] } } ``` @@ -896,7 +908,7 @@ like [`tsx`](https://npm.im/tsx) that can load TypeScript directly, you can specify it like this: ```bash -node --import=tsx --conditions=source ./script.ts +node --import=tsx --conditions=@my-project/source ./script.ts ``` Other TypeScript-aware tools may have other mechanisms for @@ -905,6 +917,16 @@ more information. See also: "Live Dev", above. +#### Why not just use `"source"` as the target? + +Tshy always builds the `"source"` target referencing the location +of the TypeScript file that was built. However, this can cause +problems if tools use this to resolve into dependencies, which +may not ship their TypeScript source. + +But if you can configure your tools to _only_ use this import +condition outside of `node_modules`, then it's safe to use. + ### Custom `project` Configure `tshy.project` if you want tshy to extend from a custom diff --git a/src/exports.ts b/src/exports.ts index 497e05a..aba1e8d 100644 --- a/src/exports.ts +++ b/src/exports.ts @@ -91,6 +91,9 @@ const getExports = ( continue } + /* c8 ignore next - already guarded */ + if (s === null) continue + const impTarget = getImpTarget(s, polyfills) const reqTarget = getReqTarget(s, polyfills) @@ -116,10 +119,12 @@ const getExports = ( liveDev ? { source, + ...getSourceDialects(source, c), default: target, } : { source, + ...getSourceDialects(source, c), types: target.replace(/\.js$/, '.d.ts'), default: target, } @@ -142,10 +147,12 @@ const getExports = ( liveDev ? { source, + ...getSourceDialects(source, c), default: target, } : { source, + ...getSourceDialects(source, c), types: target.replace(/\.js$/, '.d.ts'), default: target, } @@ -183,6 +190,12 @@ const getExports = ( return e } +const getSourceDialects = (source: string, c: TshyConfig) => { + const { sourceDialects } = c + if (!sourceDialects) return {} + return Object.fromEntries(sourceDialects.map(s => [s, source])) +} + export const setMain = ( c: TshyConfig | undefined, pkg: Package & { exports: ExportsSubpaths }, diff --git a/src/tsconfig.ts b/src/tsconfig.ts index 7e357e5..4e3d9e1 100644 --- a/src/tsconfig.ts +++ b/src/tsconfig.ts @@ -23,7 +23,9 @@ const { exclude = [], } = config -const relativeExclude = exclude.map(e => `../${e.replace(/^\.\//, '')}`) +const relativeExclude = exclude.map( + e => `../${e.replace(/^\.\//, '')}`, +) const recommended: Record = { compilerOptions: { diff --git a/src/types.ts b/src/types.ts index fda64d8..4ce44c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,6 +12,7 @@ export type TshyConfigMaybeGlobExports = { module?: boolean commonjsDialects?: string[] esmDialects?: string[] + sourceDialects?: string[] project?: string exclude?: string[] liveDev?: boolean diff --git a/src/valid-extra-dialects.ts b/src/valid-extra-dialects.ts index 07e5f56..c80edfb 100644 --- a/src/valid-extra-dialects.ts +++ b/src/valid-extra-dialects.ts @@ -8,16 +8,14 @@ const arrayOverlap = ( ): string | false => { if (!a || !b) return false for (const av of a) { - if (b.includes(av)) { - return av - } + if (b.includes(av)) return av } return false } const validExtraDialectSet = ( e: string[], - which: 'commonjs' | 'esm', + which: 'commonjs' | 'esm' | 'source', ) => { for (const d of e) { if (typeof d !== 'string') { @@ -31,17 +29,28 @@ const validExtraDialectSet = ( d === 'require' || d === 'import' || d === 'node' || + d === 'source' || d === 'default' ) { - fail(`${which} must not contain ${d}`) + fail( + `tshy.${which}Dialects must not contain ${JSON.stringify(d)}`, + ) return process.exit(1) } } return true } -export default ({ commonjsDialects, esmDialects }: TshyConfig) => { - if (commonjsDialects === undefined && esmDialects === undefined) { +export default ({ + commonjsDialects, + esmDialects, + sourceDialects, +}: TshyConfig) => { + if ( + commonjsDialects === undefined && + esmDialects === undefined && + sourceDialects === undefined + ) { return true } if ( @@ -53,11 +62,31 @@ export default ({ commonjsDialects, esmDialects }: TshyConfig) => { if (esmDialects && !validExtraDialectSet(esmDialects, 'esm')) { return false } - const overlap = arrayOverlap(commonjsDialects, esmDialects) - if (overlap) { + if ( + sourceDialects && + !validExtraDialectSet(sourceDialects, 'source') + ) { + return false + } + for (const [aname, bname, a, b] of [ + [ + 'commonjsDialects', + 'esmDialects', + commonjsDialects, + esmDialects, + ], + [ + 'commonjsDialects', + 'sourceDialects', + commonjsDialects, + sourceDialects, + ], + ['esmDialects', 'sourceDialects', esmDialects, sourceDialects], + ] as const) { + const overlap = arrayOverlap(a, b) + if (!overlap) continue fail( - 'commonjsDialects and esmDialects must be unique, ' + - `found ${overlap} in both lists`, + `${aname} and ${bname} must be unique, found ${overlap} in both lists`, ) return process.exit(1) } diff --git a/tap-snapshots/test/exports.ts.test.cjs b/tap-snapshots/test/exports.ts.test.cjs index c520e44..5b7a937 100644 --- a/tap-snapshots/test/exports.ts.test.cjs +++ b/tap-snapshots/test/exports.ts.test.cjs @@ -29,6 +29,7 @@ Object { ".": Object { "blah": Object { "source": "./src/index.ts", + "my-source": "./src/index.ts", "types": "./dist/blah/index.d.ts", "default": "./dist/blah/index.js", }, @@ -41,6 +42,7 @@ Object { "./foo": Object { "blah": Object { "source": "./src/foo.ts", + "my-source": "./src/foo.ts", "types": "./dist/blah/foo.d.ts", "default": "./dist/blah/foo.js", }, @@ -77,11 +79,13 @@ Object { ".": Object { "deno": Object { "source": "./src/index.ts", + "my-source": "./src/index.ts", "types": "./dist/deno/index.d.ts", "default": "./dist/deno/index.js", }, "no-overrides": Object { "source": "./src/index.ts", + "my-source": "./src/index.ts", "types": "./dist/no-overrides/index.d.ts", "default": "./dist/no-overrides/index.js", }, @@ -94,11 +98,13 @@ Object { "./foo": Object { "deno": Object { "source": "./src/foo.ts", + "my-source": "./src/foo.ts", "types": "./dist/deno/foo.d.ts", "default": "./dist/deno/foo.js", }, "no-overrides": Object { "source": "./src/foo.ts", + "my-source": "./src/foo.ts", "types": "./dist/no-overrides/foo.d.ts", "default": "./dist/no-overrides/foo.js", }, @@ -145,16 +151,19 @@ Object { ".": Object { "deno": Object { "source": "./src/index.ts", + "my-source": "./src/index.ts", "types": "./dist/deno/index.d.ts", "default": "./dist/deno/index.js", }, "no-overrides": Object { "source": "./src/index.ts", + "my-source": "./src/index.ts", "types": "./dist/no-overrides/index.d.ts", "default": "./dist/no-overrides/index.js", }, "blah": Object { "source": "./src/index.ts", + "my-source": "./src/index.ts", "types": "./dist/blah/index.d.ts", "default": "./dist/blah/index.js", }, @@ -172,16 +181,19 @@ Object { "./foo": Object { "deno": Object { "source": "./src/foo.ts", + "my-source": "./src/foo.ts", "types": "./dist/deno/foo.d.ts", "default": "./dist/deno/foo.js", }, "no-overrides": Object { "source": "./src/foo.ts", + "my-source": "./src/foo.ts", "types": "./dist/no-overrides/foo.d.ts", "default": "./dist/no-overrides/foo.js", }, "blah": Object { "source": "./src/foo.ts", + "my-source": "./src/foo.ts", "types": "./dist/blah/foo.d.ts", "default": "./dist/blah/foo.js", }, diff --git a/tap-snapshots/test/valid-extra-dialects.ts.test.cjs b/tap-snapshots/test/valid-extra-dialects.ts.test.cjs index deb43f8..76b8ec9 100644 --- a/tap-snapshots/test/valid-extra-dialects.ts.test.cjs +++ b/tap-snapshots/test/valid-extra-dialects.ts.test.cjs @@ -21,10 +21,18 @@ Array [ ] ` +exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["blah"],"sourceDialects":["blah"]} > failure message 1`] = ` +Array [ + Array [ + "esmDialects and sourceDialects must be unique, found blah in both lists", + ], +] +` + exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["cjs"]} > failure message 1`] = ` Array [ Array [ - "esm must not contain cjs", + "tshy.esmDialects must not contain \\"cjs\\"", ], ] ` @@ -32,7 +40,7 @@ Array [ exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["commonjs"]} > failure message 1`] = ` Array [ Array [ - "esm must not contain commonjs", + "tshy.esmDialects must not contain \\"commonjs\\"", ], ] ` @@ -40,7 +48,7 @@ Array [ exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["default"]} > failure message 1`] = ` Array [ Array [ - "esm must not contain default", + "tshy.esmDialects must not contain \\"default\\"", ], ] ` @@ -48,7 +56,7 @@ Array [ exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["import"]} > failure message 1`] = ` Array [ Array [ - "esm must not contain import", + "tshy.esmDialects must not contain \\"import\\"", ], ] ` @@ -56,7 +64,7 @@ Array [ exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["node"]} > failure message 1`] = ` Array [ Array [ - "esm must not contain node", + "tshy.esmDialects must not contain \\"node\\"", ], ] ` @@ -64,7 +72,15 @@ Array [ exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["require"]} > failure message 1`] = ` Array [ Array [ - "esm must not contain require", + "tshy.esmDialects must not contain \\"require\\"", + ], +] +` + +exports[`test/valid-extra-dialects.ts > TAP > {"esmDialects":["source"]} > failure message 1`] = ` +Array [ + Array [ + "tshy.esmDialects must not contain \\"source\\"", ], ] ` @@ -76,3 +92,19 @@ Array [ ], ] ` + +exports[`test/valid-extra-dialects.ts > TAP > {"sourceDialects":["blah"],"commonjsDialects":["blah"]} > failure message 1`] = ` +Array [ + Array [ + "commonjsDialects and sourceDialects must be unique, found blah in both lists", + ], +] +` + +exports[`test/valid-extra-dialects.ts > TAP > {"sourceDialects":["source"]} > failure message 1`] = ` +Array [ + Array [ + "tshy.sourceDialects must not contain \\"source\\"", + ], +] +` diff --git a/test/exports.ts b/test/exports.ts index 578f33b..71c8c34 100644 --- a/test/exports.ts +++ b/test/exports.ts @@ -342,6 +342,7 @@ t.test('extra dialects', async t => { default: { tshy: { ...(extras && { esmDialects, commonjsDialects }), + sourceDialects: ['my-source'], dialects, exports: { '.': './src/index.ts', diff --git a/test/valid-extra-dialects.ts b/test/valid-extra-dialects.ts index 9bfb84f..664f136 100644 --- a/test/valid-extra-dialects.ts +++ b/test/valid-extra-dialects.ts @@ -19,12 +19,18 @@ const cases: [config: TshyConfig, ok: boolean][] = [ [{ esmDialects: ['blah'] }, true], [{ esmDialects: ['blah'], commonjsDialects: ['blah'] }, false], [{ esmDialects: ['blah'], commonjsDialects: ['bloo'] }, true], + [{ sourceDialects: ['blah'], commonjsDialects: ['blah'] }, false], + [{ sourceDialects: ['blah'], commonjsDialects: ['bloo'] }, true], + [{ esmDialects: ['blah'], sourceDialects: ['blah'] }, false], + [{ esmDialects: ['blah'], sourceDialects: ['bloo'] }, true], [{ esmDialects: ['default'] }, false], [{ esmDialects: ['import'] }, false], [{ esmDialects: ['require'] }, false], [{ esmDialects: ['node'] }, false], [{ esmDialects: ['commonjs'] }, false], [{ esmDialects: ['cjs'] }, false], + [{ esmDialects: ['source'] }, false], + [{ sourceDialects: ['source'] }, false], //@ts-expect-error [{ esmDialects: [123] }, false], //@ts-expect-error