Skip to content

fix(vite-config): publish compiled JS instead of raw TypeScript sources - #1

Merged
losolio merged 2 commits into
mainfrom
fix/vite-config-publish-compiled-js
Jul 31, 2026
Merged

fix(vite-config): publish compiled JS instead of raw TypeScript sources#1
losolio merged 2 commits into
mainfrom
fix/vite-config-publish-compiled-js

Conversation

@losolio

@losolio losolio commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Problem

@eventuras/vite-config mapped its export subpaths straight at ./src/*.ts:

"exports": { "./base": "./src/base.ts", ... },
"files": ["src"]

That works inside origo — pnpm links the package, so Node's realpath lands outside node_modules and built-in type stripping is allowed. It breaks for every consumer installing from the registry, where the realpath is node_modules/.pnpm/@eventuras+vite-config@…/node_modules/@eventuras/vite-config/src/vanilla-lib.ts:

Error [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]: Stripping types is currently
unsupported for files under node_modules

Node's restriction is deliberate and permanent. Reported from losol/historia#1, where it takes down the whole Docker image build; consumers are currently forced into per-package workarounds (NODE_OPTIONS="--import tsx", vite build --configLoader runner).

Change

  • Compiled output. pnpm build runs tsc and emits dist/ — ESM + declarations, no bundling. Runtime deps stay real deps.
  • Repointed exports at dist/*.js with matching "types": "./dist/*.d.ts", "files": ["dist"]. Subpath names are unchanged (./base, ./react-lib, ./vanilla-lib, ./next-lib) — no consumer import changes.
  • Relative specifiers switched from './externals.ts' to './externals.js'. rewriteRelativeImportExtensions alone is not enough: TS 6.0.3 rewrites the JS emit but not the declaration emit, so next-lib.d.ts would have shipped an import of ./react-lib.ts, a file that isn't published. Verified with a minimal repro.
  • getRuntimeDependencyExternals() still reads the consumer's package.json via process.cwd() — nothing resolves relative to the config package.

Two latent bugs found by type-checking the sources for the first time

  • useSWC: true threw require is not defined — a bare require() inside an ES module. Now createRequire, keeping the SWC plugin lazily loaded.
  • dts.outDir and dts.rollupTypes were silently ignored: vite-plugin-dts v5 renamed them to outDirs/bundleTypes. The preset's own option names are unchanged.

Release flow

  • prepack rebuilds dist; pnpm release builds before changeset publish (the release workflow never built at all, so packages/* had the same exposure).
  • CI's inline packaging check is replaced by scripts/verify-packaging.mjs, which packs every public package and asserts each exports/main/types/bin target is in the tarball and is not raw TypeScript. Negative-tested: it flags both "./src/base.ts" and a target missing from files.

Verification

Run outside the monorepo, so no workspace symlink hides the bug — npm packed the built package into scratch projects (Node 24.18, Vite 8.2, "type": "module"), in both npm-flat and pnpm virtual-store layouts:

check result
vite build with ./vanilla-lib, ./base, ./react-lib, ./next-lib passes with plain node — no NODE_OPTIONS, no --configLoader runner
tsc --noEmit importing all four entrypoints + their exported types clean
useSWC: true builds (previously threw)
control: a .ts export added back to the installed package still ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING — the scratch setup does reproduce the bug
pnpm build, pnpm lint, pnpm verify:packaging in origo pass

Also confirmed the runtime dep externalization survives the move to dist/: the scratch project's zod stays an external import.

Sweep

Only config/vite-config exported raw .ts. @eventuras/typescript-config ships JSON, @eventuras/eslint-config ships plain .js, and all of packages/* already export dist. There is no libs/ in this repo. verify-packaging.mjs now enforces this for anything added later.

Release

Changeset included, minor → 0.3.0. Once published, historia bumps @eventuras/vite-config in packages/vipps, packages/payload-vipps-auth and packages/notitia-templates and drops its workarounds.

Note for consumers: the declaration step needs TypeScript 6 — vite-plugin-dts v5 requires the TS JS Compiler API, which TS 7 no longer ships by default. Documented in the README; hit this while testing.

🤖 Generated with Claude Code

The package mapped its export subpaths straight at ./src/*.ts. Inside the
monorepo pnpm links the package, so Node's realpath lands outside node_modules
and type stripping is allowed. Every consumer installing from the registry
instead resolved to a path under node_modules, where Node refuses to strip
types, and loading any preset from a vite.config.ts failed with
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.

The package now emits dist/ with tsc (ESM + declarations) and points exports
there. Subpath names are unchanged, so no consumer import has to change and the
per-package workarounds (NODE_OPTIONS="--import tsx", --configLoader runner) can
be dropped.

Type-checking the sources for the first time surfaced two latent bugs, fixed
here as well: useSWC: true threw "require is not defined" (bare require() in an
ES module, now createRequire), and dts.outDir/dts.rollupTypes were silently
ignored because vite-plugin-dts v5 renamed them to outDirs/bundleTypes.

Wire the build into the release flow so this cannot regress: prepack rebuilds
dist, `pnpm release` builds before changeset publish, and CI's packaging check
is now scripts/verify-packaging.mjs, which packs every public package and
asserts that each exports/main/types target exists in the tarball and is not
raw TypeScript.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 18:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates @eventuras/vite-config to publish compiled JavaScript + declaration files from dist/ (instead of exporting raw src/*.ts), preventing Node’s ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING for registry consumers. It also tightens the release/CI flow to ensure packages are built before publishing and adds a repo-wide packaging verifier to catch manifest/export issues before release.

Changes:

  • Build and publish @eventuras/vite-config from dist/ with updated exports and TS config for ESM + declarations.
  • Fix ESM require usage for the optional SWC React plugin and align vite-plugin-dts option names with v5.
  • Add scripts/verify-packaging.mjs and wire it into CI and the root scripts; ensure pnpm release builds before publishing.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/verify-packaging.mjs New script to pack each public workspace package and validate manifest targets exist in the tarball and aren’t raw TS sources.
pnpm-lock.yaml Lockfile updates for added workspace/dev dependencies (notably for config/vite-config).
package.json Adds verify:packaging script; updates release to build before changeset publish.
config/vite-config/tsconfig.json Enables emitting JS + declarations into dist/ with NodeNext ESM settings.
config/vite-config/src/vanilla-lib.ts Switches relative import specifier to .js to match emitted/published output.
config/vite-config/src/react-lib.ts Uses createRequire for SWC plugin loading in ESM; updates .js specifier; updates dts option names.
config/vite-config/src/next-lib.ts Switches relative import specifier to .js to match emitted/published output.
config/vite-config/README.md Documents Node/Vite/TS requirements and the historical type-stripping failure + guidance.
config/vite-config/package.json Repoints exports to dist/*.js with matching types; publishes only dist; adds build/prepack scripts and dev deps.
.github/workflows/release.yml Notes that pnpm release builds before publishing, preventing publishing without compiled output.
.github/workflows/ci.yml Replaces inline pack check with pnpm verify:packaging.
.changeset/heavy-donkeys-smoke.md Changeset documenting the publish-from-dist change and the two related fixes.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/verify-packaging.mjs Outdated

const relative = posix.normalize(target.replace(/^\.\//, ''));

if (/\.(c|m)?tsx?$/.test(relative) && !relative.endsWith('.d.ts')) {
Comment thread scripts/verify-packaging.mjs Outdated
Comment on lines +81 to +82
` ERROR ${pkg.name}: exports "${target}" points at TypeScript source. ` +
`Node cannot strip types under node_modules — publish compiled JS instead.`
Comment thread config/vite-config/package.json Outdated
- verify-packaging: treat .d.mts and .d.cts as declaration output, not raw
  TypeScript sources. The previous check only exempted .d.ts, so a NodeNext
  package emitting .d.mts would have failed verification for valid output.
- verify-packaging: name the offending manifest field (exports/main/module/
  types/bin) in the error instead of always saying "exports".
- vite-config: make prepack tool-agnostic. It ran `pnpm run` and `rm -rf`, so
  `npm pack`/`npm publish` depended on pnpm being on PATH and on a POSIX shell.
  Now `npm run` (npm always ships with Node) and a Node-based clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@losolio
losolio merged commit 3af2fb5 into main Jul 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants