fix: document ESM resolution workaround for validator and similar packages (#189)#193
Conversation
acf79f1 to
7f935ff
Compare
|
/bigbonk review please |
There was a problem hiding this comment.
Review Summary
This PR adds useful functionality: auto-detecting problematic ESM packages and propagating ssr.noExternal to the RSC environment, which fixes a real issue (#189). The test coverage is thorough and the core approach (merging user config with auto-detected packages, propagating to both rsc and ssr environments) is sound.
However, there are several issues that need to be addressed:
Must Fix
package-lock.jsonmust not be committed — this is a pnpm monorepo. An npm lockfile is incorrect.- Wrong GitHub URL in code comment — references
nicepkg/vinextinstead ofcloudflare/vinext. - Unrelated changes bundled in — The
Space_Groteskexport (issue #190) and its test should be in a separate PR. This PR claims to fix #189 only.
Should Fix
- Questionable package list —
cuidandnanoiddon't have the described ESM issue.uuidv9+ ships proper ESM. Onlyvalidatorhas a verified reproduction. - RegExp items silently dropped — Vite's
resolve.noExternalsupports RegExp; the comment claiming otherwise is inaccurate.
Minor
- Fixture uses wrong Vite version —
^6.0.0instead of the project's^7.3.1. - Documentation references unverified version (
v0.0.16+).
package-lock.json
Outdated
| @@ -0,0 +1,5958 @@ | |||
| { | |||
There was a problem hiding this comment.
Must remove. This is a pnpm monorepo (uses pnpm-lock.yaml). A 5,958-line npm package-lock.json should not be committed — it's the wrong package manager's lockfile and will cause confusion.
packages/vinext/src/index.ts
Outdated
| * Adding them to `resolve.noExternal` forces Vite to bundle them through | ||
| * its transform pipeline, which resolves extensionless imports correctly. | ||
| * | ||
| * See: https://github.com/nicepkg/vinext/issues/189 |
There was a problem hiding this comment.
Wrong repository URL. The repo is cloudflare/vinext, not nicepkg/vinext.
| * See: https://github.com/nicepkg/vinext/issues/189 | |
| * See: https://github.com/cloudflare/vinext/issues/189 |
packages/vinext/src/index.ts
Outdated
| "validator", // validator/es/lib/isEmail.js can't resolve ./util/assertString | ||
| "uuid", // Similar ESM resolution issues | ||
| "date-fns", // date-fns/esm has complex internal structure | ||
| "cuid", |
There was a problem hiding this comment.
cuid and nanoid do not have the ESM resolution issue described in the JSDoc comment above. Both ship clean ESM with proper exports maps. Adding packages speculatively to this allowlist forces Vite to bundle them through its transform pipeline unnecessarily, which:
- Increases dev server startup time
- Can break packages that rely on being externalized
- Erodes trust in the list (users will question every entry)
uuid v9+ also ships proper ESM with file extensions. The comment "Similar ESM resolution issues" is unverified.
Recommendation: Only include packages with verified reproduction cases. Keep validator (verified in #189) and date-fns (known complex ESM internal structure). Remove cuid, nanoid, and verify uuid before including it.
packages/vinext/src/index.ts
Outdated
| } else if (Array.isArray(userSsrNoExternal)) { | ||
| for (const item of userSsrNoExternal) { | ||
| if (typeof item === "string") packages.add(item); | ||
| // RegExp items are skipped — can't propagate to resolve.noExternal |
There was a problem hiding this comment.
The comment says RegExp items "can't propagate to resolve.noExternal" — this is inaccurate. Vite's resolve.noExternal does support RegExp values (it accepts string | RegExp | (string | RegExp)[] | true). If you're choosing to skip them for implementation simplicity, the comment should reflect that, e.g. "RegExp items are not propagated — only string package names are supported".
| export const JetBrains_Mono = createFontLoader("JetBrains Mono"); | ||
| export const Geist = createFontLoader("Geist"); | ||
| export const Geist_Mono = createFontLoader("Geist Mono"); | ||
| export const Space_Grotesk = createFontLoader("Space Grotesk"); |
| }, | ||
| "devDependencies": { | ||
| "vinext": "workspace:*", | ||
| "vite": "^6.0.0" |
There was a problem hiding this comment.
The project uses Vite 7 ("vite": "^7.3.1" in root package.json). This fixture should match the workspace version.
| "vite": "^6.0.0" | |
| "vite": "^7.3.1" |
| ``` | ||
|
|
||
| **Cause:** Some ESM packages have complex internal import structures that Node.js module resolution can't handle when externalized. By default, Vite externalizes dependencies in SSR (loads them via Node.js) instead of transforming them through Vite's pipeline. | ||
|
|
There was a problem hiding this comment.
Where does v0.0.16+ come from? This version number should match the actual release version where this feature will ship. If uncertain, remove the version reference to avoid documenting an inaccurate version number.
|
|
||
| export default defineConfig({ | ||
| plugins: [vinext()], | ||
| // No manual ssr.noExternal needed - vinext auto-detects validator! |
There was a problem hiding this comment.
"auto-detects" is somewhat misleading — it's a hardcoded allowlist check, not runtime detection of ESM issues. Consider:
| // No manual ssr.noExternal needed - vinext auto-detects validator! | |
| // No manual ssr.noExternal needed - vinext includes validator in its known problematic ESM packages list |
|
The review was submitted successfully. Here's a summary of what I flagged: Review Posted on PR #193Verdict: Changes Requested Must Fix
Should Fix
Minor
|
02b9d86 to
69cdd65
Compare
|
@elithrar @southpolesteve kindly review the changes. |
|
kindly review the changes. |
1d83370 to
b7b5402
Compare
commit: |
cloudflare#189) Vinext now automatically detects installed packages with broken ESM resolution and adds them to resolve.noExternal for both the rsc and ssr environments. This fixes 'Cannot find module' errors when using packages like validator that have extensionless internal ESM imports. Root cause: Vite externalizes dependencies for SSR by default, but some packages use extensionless imports (e.g. ./util/assertString) that fail under Node.js strict ESM resolution. Additionally, Vite 7's Environment API means ssr.noExternal only affects the ssr environment, not the rsc environment where server components run. Changes: - Add KNOWN_PROBLEMATIC_ESM_PACKAGES list (validator, date-fns) - Add collectNoExternalPackages() to merge user config with auto-detected packages - Propagate noExternal to both rsc and ssr environments - Add validator ecosystem test fixture and integration tests - Add troubleshooting documentation Fixes cloudflare#189
b7b5402 to
e476e70
Compare
|
@southpolesteve The workflow is all green. |
Summary
Fixes #189 - Documents workaround for third-party ESM packages that fail with
Cannot find moduleerrors in dev server.Problem
Users importing certain ESM packages (like
validator,uuid,date-fns/esm) encounter module resolution errors:Root Cause
These packages have complex internal ESM import structures that Node.js module resolution handles differently than Vite. By default, Vite externalizes dependencies in SSR (loads them via native Node.js
require/import), which can fail for packages with non-standard export patterns.Solution
Add affected packages to
ssr.noExternalinvite.config.tsto force them through Vite's transform pipeline:This is the same pattern already used for
nuqsandbetter-authin the ecosystem test fixtures.Changes
tests/fixtures/ecosystem/validator/fixtureTesting
Note
This is a documentation/guidance issue, not a bug. The workaround exists (Vite's
ssr.noExternal), but wasn't documented. This PR makes it discoverable.