Last 12 weeks ยท 19 commits
2 of 6 standards met
Closes #71 When emitting the CJS (and ) build, in renamed the output path (โ, โ, and the matching files) but wrote the contents unchanged, so: / kept / the companion / kept / Every CJS consumer then resolved to the ESM map (different ) and misreported positions. This rewrites those in-file references whenever the extension is renamed. [x] T1 Rewrite comment and map field in hook [x] T2 Add regression test: CJS/dts outputs reference their own / [x] T3 Regenerate committed fixtures with corrected map references [x] T4 Anchor rewrite to end-of-file so a sourceMappingURL-like string in user code is left alone (review)
When emitting the CJS build, the / files keep the comment from the original TS emit, so they reference / instead of their own / . The companion map files also carry the wrong field (the / name). Because the ESM and CJS maps have different , every CJS consumer โ Node , , ts-node/tsx, Jest/Mocha, IDE "go to definition" โ resolves to the wrong map and misreports positions. Repro zshy's own published package exhibits it: Same for โ . Cause In , the hook renames the output path (โ, etc.) but writes unmodified, so the in-file comment and the map field still reference the original names. Fix When renaming, also rewrite the contents โ the comment in /, and the field in /. Version zshy 0.7.3
Related Zod issue: https://github.com/colinhacks/zod/issues/5686 The README states regarding always using : This warning does not cause any resolution issues (unlike "Masquerading as ESM"). Technically, we're tricking TypeScript into thinking our code is CommonJS; when in fact it may be ESM. The ATTW tool is very rigorous and flags this; in practice, this has no real consequences and maximizes compatibility (Zod has relied on the CJS masquerading trick since it's earliest days.) I believe this may no longer be true. Now that TypeScript has pushed for module writers to use module type, there is a real consequence for "masquerading as CJS" when operating in an ESM environment. TypeScript assumes the default export of a CJS always contains the entire , which means it will reference things like in the declaration file that are incorrect. This causes breaking behavior when packages compiled with one module type are used in a TS environment with another (e.g. vs ). The workaround is to never use the default export from a package in ESM code, but that requires special enforcement/lint rules.
CJS interop declaration transform produces invalid when file has type-only named exports When a source file has type-only named exports (, , ) alongside , the CJS interop declaration transformer rewrites โ but leaves the type-only exports with their keyword. This produces an invalid : Reproduction Any source file with this common pattern: Expected behavior The .d.cts should either: Keep export default (valid when esModuleInterop is enabled), or Not apply the CJS interop transform at all for files with type-only exports Actual behavior The .d.cts has export = mixed with export type / export interface, which is invalid TypeScript.
Summary Fix CJS interop declaration transform producing invalid files when source files have type-only named exports (, , ) alongside . Problem When a TypeScript source file has type-only named exports alongside , zshy's CJS interop declaration transformer rewrites โ but leaves the type-only named exports unchanged. This produces an invalid declaration file: TypeScript rejects this with TS2309: An export assignment cannot be used in a module with other exported elements. This affects any library built with zshy that has source files following this common pattern: Fix Added a flag to analyzeExports() that detects export type, export interface, export enum, and type-only re-exports (). When the declaration transformer sees type-only exports alongside export default, it now skips the export = rewrite entirely, leaving the .d.cts in its original export default form. This is valid TypeScript and works correctly with / . The runtime CJS interop ( files) is unaffected โ it still correctly appends module.exports = exports.default for these files, since type-only exports are erased at runtime. Before / After Source file Before (invalid .d.cts) After (valid .d.cts) Files with only export default (no type exports) โ unchanged Possible breaking change For source files that have both export default and type-only exports, the output changes from export = to export default. This means: No impact if consumers have or (the vast majority of modern TypeScript projects, including @tsconfig/strictest) Potential impact if a consumer has and was relying on export = semantics from these specific files โ but those files were already producing TS2309 errors, so they were broken regardless Files with only export default (no type-only exports) are completely unaffected and continue to use export =. T##est plan [x] Added default-with-type-exports.ts fixture covering export type, export interface, export const enum, and export type { ... } from '...' alongside export default [x] Added default-with-same-name-type.ts fixture covering export type Foo = ...; export default Foo; pattern [x] Verified .d.cts keeps export default for files with type-only exports [x] Verified .d.cts keeps export = for files with only export default [x] Verified .cjs runtime output is unaffected [x] All 18 tests pass
Repository: colinhacks/zshy. Description: ๐ Bundler-free build tool for TypeScript libraries. Powered by tsc. Stars: 1109, Forks: 21. Primary language: TypeScript. Languages: TypeScript (98.9%), CSS (0.6%), JavaScript (0.5%). License: MIT. Latest release: v0.7.3 (2w ago). Open PRs: 1, open issues: 5. Last activity: 2w ago. Community health: 42%. Top contributors: colinhacks, DallasHoff, marcalexiei, jandolezal71, noritaka1166, djhi, Ehesp, 43081j, KirillTregubov, pullfrog[bot].