Fix ESM import resolution in dist output#5
Open
chad-loder wants to merge 2 commits into
Open
Conversation
tsc emits import specifiers verbatim, so with moduleResolution: "bundler" the compiled dist/ files contain extensionless relative imports like from ./core/ZMachine. Node ESM loader rejects these at runtime. Add tsc-esm-fix as a postbuild step to rewrite relative imports in dist/ with correct .js extensions (and /index.js for directory imports). Fixes daniellockard#4
When installed as a git-hosted dependency, npm/pnpm runs the prepare script. Previously this only ran husky, so the dist/ directory was never built and consumers got MODULE_NOT_FOUND errors. Run the build in prepare and guard husky so it does not fail when the package is installed outside its own repo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4.
Problem
The compiled
dist/files contain extensionless relative imports likefrom './core/ZMachine'and bare directory imports likefrom './core/errors'. Since the package declares"type": "module", Node's ESM loader requires explicit file extensions and rejects these at runtime:This doesn't surface in development or CI because Vitest (via Vite) handles extensionless resolution internally, but it breaks any consumer that loads the package through Node's native module system.
The root cause is
moduleResolution: "bundler"in tsconfig.json — TypeScript allows extensionless imports under this mode (since bundlers resolve them), buttscemits the specifiers verbatim into the JS output.Fix
Rather than modifying every import across the source tree or changing the module resolution strategy, this adds
tsc-esm-fixas a postbuild step. It rewrites the relative imports indist/to include proper.jsextensions (and/index.jsfor directory barrel imports like./core/errors→./core/errors/index.js).The change is two lines in
package.json:Plus
tsc-esm-fixas a devDependency.About tsc-esm-fix
tsc-esm-fix is an established utility (~62K weekly downloads, 85 stars, MIT licensed, provenance-signed) that specifically addresses this gap in TypeScript's ESM output. It's been actively maintained since 2021 with 75 releases.
Testing
node -e "import('./dist/index.js')"loads successfully under Node's native ESM — the exact scenario that was previously broken.