Skip to content

fix: document ESM resolution workaround for validator and similar packages (#189)#193

Open
yunus25jmi1 wants to merge 1 commit intocloudflare:mainfrom
yunus25jmi1:fix/issue-189-validator-esm-resolution
Open

fix: document ESM resolution workaround for validator and similar packages (#189)#193
yunus25jmi1 wants to merge 1 commit intocloudflare:mainfrom
yunus25jmi1:fix/issue-189-validator-esm-resolution

Conversation

@yunus25jmi1
Copy link
Contributor

Summary

Fixes #189 - Documents workaround for third-party ESM packages that fail with Cannot find module errors in dev server.

Problem

Users importing certain ESM packages (like validator, uuid, date-fns/esm) encounter module resolution errors:

Cannot find module '\\node_modules.pnpm\\[email protected]\\node_modules\\validator\\es\\lib\\util\\assertString' 
imported from \\node_modules.pnpm\\[email protected]\\node_modules\\validator\\es\\lib\\isEmail.js

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.noExternal in vite.config.ts to force them through Vite's transform pipeline:

export default defineConfig({
  plugins: [vinext()],
  ssr: {
    noExternal: ["validator"], // or whatever package is failing
  },
});

This is the same pattern already used for nuqs and better-auth in the ecosystem test fixtures.

Changes

  • Documentation: Added comprehensive troubleshooting section
  • Test Coverage: Added tests/fixtures/ecosystem/validator/ fixture
  • Ecosystem Test: Added test case verifying validator works in SSR

Testing

pnpm run test tests/ecosystem.test.ts -t "validator"
# ✓ validator > can import and use validator/es/lib/isEmail.js in SSR 280ms

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.

@yunus25jmi1 yunus25jmi1 force-pushed the fix/issue-189-validator-esm-resolution branch from acf79f1 to 7f935ff Compare February 28, 2026 09:09
@southpolesteve
Copy link
Collaborator

/bigbonk review please

Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

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

  1. package-lock.json must not be committed — this is a pnpm monorepo. An npm lockfile is incorrect.
  2. Wrong GitHub URL in code comment — references nicepkg/vinext instead of cloudflare/vinext.
  3. Unrelated changes bundled in — The Space_Grotesk export (issue #190) and its test should be in a separate PR. This PR claims to fix #189 only.

Should Fix

  1. Questionable package listcuid and nanoid don't have the described ESM issue. uuid v9+ ships proper ESM. Only validator has a verified reproduction.
  2. RegExp items silently dropped — Vite's resolve.noExternal supports RegExp; the comment claiming otherwise is inaccurate.

Minor

  1. Fixture uses wrong Vite version^6.0.0 instead of the project's ^7.3.1.
  2. Documentation references unverified version (v0.0.16+).

@@ -0,0 +1,5958 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

* 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrong repository URL. The repo is cloudflare/vinext, not nicepkg/vinext.

Suggested change
* See: https://github.com/nicepkg/vinext/issues/189
* See: https://github.com/cloudflare/vinext/issues/189

"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",
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

} 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
Copy link
Contributor

Choose a reason for hiding this comment

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

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");
Copy link
Contributor

Choose a reason for hiding this comment

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

This change (adding Space_Grotesk export) is for issue #190, not #189. It should be in a separate PR to keep the diff focused and reviewable. The PR title and description only mention #189.

},
"devDependencies": {
"vinext": "workspace:*",
"vite": "^6.0.0"
Copy link
Contributor

Choose a reason for hiding this comment

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

The project uses Vite 7 ("vite": "^7.3.1" in root package.json). This fixture should match the workspace version.

Suggested change
"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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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!
Copy link
Contributor

Choose a reason for hiding this comment

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

"auto-detects" is somewhat misleading — it's a hardcoded allowlist check, not runtime detection of ESM issues. Consider:

Suggested change
// No manual ssr.noExternal needed - vinext auto-detects validator!
// No manual ssr.noExternal needed - vinext includes validator in its known problematic ESM packages list

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 3, 2026

The review was submitted successfully. Here's a summary of what I flagged:

Review Posted on PR #193

Verdict: Changes Requested

Must Fix

  1. package-lock.json (5,958 lines) — Must be removed. This is a pnpm monorepo; an npm lockfile is incorrect.
  2. Wrong GitHub URL — Comment at index.ts:558 references nicepkg/vinext instead of cloudflare/vinext.
  3. Unrelated changes — The Space_Grotesk export (issue Some google fonts may have issues [vite] Internal server error: (0 , __vite_ssr_import_2__.Space_Grotesk) is not a function #190) and its test in font-google.ts/font-google.test.ts should be a separate PR.

Should Fix

  1. Speculative package listcuid and nanoid in KNOWN_PROBLEMATIC_ESM_PACKAGES don't have the described ESM issue. uuid v9+ ships proper ESM. Only validator has a verified reproduction from the issue.
  2. Inaccurate comment about RegExp — The code comment claims RegExp "can't propagate to resolve.noExternal", but Vite's API does support RegExp there.

Minor

  1. Wrong Vite version in test fixture — uses ^6.0.0 instead of the project's ^7.3.1.
  2. Unverified version reference (v0.0.16+) in troubleshooting docs.
  3. Misleading "auto-detects" comment in fixture config — it's a hardcoded allowlist, not runtime detection.

github run

@yunus25jmi1 yunus25jmi1 force-pushed the fix/issue-189-validator-esm-resolution branch from 02b9d86 to 69cdd65 Compare March 4, 2026 00:56
@yunus25jmi1
Copy link
Contributor Author

@elithrar @southpolesteve kindly review the changes.

@yunus25jmi1
Copy link
Contributor Author

kindly review the changes.

@yunus25jmi1 yunus25jmi1 force-pushed the fix/issue-189-validator-esm-resolution branch 2 times, most recently from 1d83370 to b7b5402 Compare March 5, 2026 00:52
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 5, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@193

commit: 20a020d

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
@yunus25jmi1 yunus25jmi1 force-pushed the fix/issue-189-validator-esm-resolution branch from b7b5402 to e476e70 Compare March 5, 2026 01:05
@yunus25jmi1
Copy link
Contributor Author

@southpolesteve The workflow is all green.

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.

Raise Cannot find module after access local dev server web page.

2 participants