Fix Vite: local module 'does not provide an export named'
Reproduced with Node's own ES module loader: why the browser's loader finds no export in a tsc CommonJS build, and why two of the three moduleResolution values the top answer lists don't compile.
TL;DR#
Vite only converts CommonJS/UMD dependencies to ES modules during its dependency
pre-bundling step, and it skips that step for linked local packages (an npm link,
or a workspace/file: dependency that resolves to a symlink). If that local package's
own tsconfig.json sets "module": "commonjs" — as the question's did — its build
output stays CommonJS, Vite serves it unconverted, and the browser's own ES module
loader finds zero export
statements in it. Any named import from it then fails before a single line of the file
even runs. Recompile the local package with "module": "ESNext" in its own
tsconfig.json (TypeScript's moduleResolution: "Bundler" needs TypeScript 5+), or, if
you cannot touch that
package's build, add it to optimizeDeps.include in vite.config.ts and restart Vite.
This is the failure behind a Stack Overflow question with 10,880 views: a developer migrating a React app from Create React App (webpack) to Vite found that local, hand-written packages stopped exporting anything, while every package installed from the npm registry kept working.
Uncaught SyntaxError:
The requested module '/@fs/Users/...{long_path}.../lib/shared/countries/build/index.js'
does not provide an export named 'countryListAllIsoData'Why it happens#
Vite's dev server serves native ES modules to the browser, and converting dependencies that still ship as CommonJS or UMD into ES modules is exactly what its dependency pre-bundling step is for. The official docs are explicit about the one case this does not cover:
In a monorepo setup, a dependency may be a linked package from the same repo. Vite automatically detects dependencies that are not resolved from
node_modulesand treats the linked dep as source code. It will not attempt to bundle the linked dep, and will analyze the linked dep's dependency list instead.However, this requires the linked dep to be exported as ESM. If not, you can add the dependency to
optimizeDeps.includein your config.
That matches what the question itself already noticed: "these failed imports only
happen on local packages (none installed from the npm registry)". A package pulled from
the registry goes through pre-bundling and gets converted; a package resolved through a
symlink (which is what npm link, and most workspace tools, use to wire a local package
into node_modules) is treated as if it were part of your own app and never converted.
The reason the browser's error is a SyntaxError and not some runtime crash inside the
package is more specific, and it reproduces without Vite or a browser at all: ES module
imports are bound statically. Before any code in a module runs, the engine reads the
target file's literal export statements and builds its list of available names; a
named import that is not on that list fails at that linking stage, ahead of execution.
A tsc build with "module": "commonjs" does not emit export statements — it emits
plain assignments such as exports.countryListAllIsoData = [...] — so an ES module
loader sees a file with no exports at all, no matter what the assignments inside it do.
Reproduced locally (Node 22.23.3, TypeScript 5.9.3, 25 October 2026): compiling a small
package with "module": "commonjs", "moduleResolution": "node", and then loading that
exact output through a native ES module import — bypassing Node's own CommonJS-interop
detection, which a browser's loader does not have — failed with:
SyntaxError: The requested module './lib/build/index.module.mjs' does not provide an export named 'countryListAllIsoData'Recompiling the identical source with only "module" changed to "ESNext" —
"moduleResolution" left at "node" — so tsc emits
export const countryListAllIsoData = [...], and re-running the identical import
succeeded. That one-variable swap is the whole mechanism: the module's syntax, not
its content, decides whether the import can be linked.
This is a different failure from a [commonjs--resolver] Failed to resolve entry
error, where Vite cannot even find the file a package's package.json points to — see
Fix Vite "[commonjs--resolver] Failed to resolve entry"
if that is the message instead. Here, Vite finds the file; it just never converts it.
Fix#
1. Recompile the local package as ES modules (fixes the cause)#
Change the local package's own tsconfig.json so tsc emits export statements
instead of exports.foo = … assignments:
{
"compilerOptions": {
"target": "es2016",
"module": "ESNext",
"moduleResolution": "Bundler",
"declaration": true,
"outDir": "./build",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
}
}"moduleResolution": "Bundler" needs TypeScript 5.0 or later — it was added in that
release (official 5.0 release notes,
section "--moduleResolution bundler"), and was reproduced to compile and fix the
import on TypeScript 5.9.3. On an older TypeScript, pair the same "module": "ESNext"
with "moduleResolution": "node" instead — reproduced to compile and fix the import on
both TypeScript 5.9.3 and 4.9.5 (what the question's own "typescript": "^4.8.4"
resolves to today). The same highest-voted answer also lists "ES6" and "ESNext"
as moduleResolution values; two of its three suggestions, these do not compile —
both TypeScript versions reject either outright, for example on 5.9.3:
tsconfig.json(5,25): error TS6046: Argument for '--moduleResolution' option must be: 'node10', 'classic', 'node16', 'nodenext', 'bundler'."ES6" and "ESNext" are valid values for module (also reproduced), never for
moduleResolution — 4.9.5 rejects them too, with the same TS6046, though its allowed
list reads 'node', 'classic', 'node16', 'nodenext' (no node10, no bundler).
Rebuild the package (npm run build in that package, or tsc directly), then restart
the Vite dev server. The consuming app's own code does not change: it still writes
import { countryListAllIsoData } from "countries".
2. Can't change the package's build? Make Vite pre-bundle it anyway#
When the local package is someone else's code, or its own build pipeline is out of
scope, tell Vite to treat it like a normal dependency instead of linked source, in
vite.config.ts:
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
include: ['countries'],
},
});optimizeDeps.include pulls the package into Vite's normal pre-bundling pass, which
does run the CommonJS-to-ESM conversion. The same section of the official docs adds
that after changing the linked dependency, you should "restart the dev server with the
--force command line option for the changes to take effect". One limit worth knowing:
the docs also note that "dependency pre-bundling only applies in development mode", so
this fix is a dev-server workaround, not something that changes what a production
vite build ships — fix 1 does, because it changes the package's own build output.
Verify the fix#
- Rebuild the local package after fix 1. After fix 2, if you then change the linked
dependency again, restart Vite with
--force, as the docs prescribe. - Open the browser's network tab and find the request for the package's entry file: with
fix 1 its response should now contain a literal
export; with fix 2 the import should no longer come from the raw/@fs/…filesystem path seen in the error above. - The import that used to throw should now just return the value — in the reproduction
above, the same
importstatement against the recompiled file printed the array instead of throwing.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
When module and moduleResolution disagree#
The tsconfig.json pasted in the original question pairs "module": "commonjs" with
"moduleResolution": "nodenext" — a combination that current TypeScript rejects outright,
before Vite or the browser ever get involved. Reproduced with TypeScript 5.9.3 against
that exact pairing:
tsconfig.json(4,15): error TS5110: Option 'module' must be set to 'NodeNext' when option 'moduleResolution' is set to 'NodeNext'.If you hit TS5110 while trying fix 1, it means moduleResolution and module are out
of step — "moduleResolution": "NodeNext" only pairs with "module": "NodeNext". Use
the ESNext/Bundler or ESNext/node pairing from fix 1 instead; both were
reproduced to emit the export syntax Vite needs, and neither touches NodeNext.
Once the local package itself builds and imports cleanly, two more module-format habits
tend to surface. The same project's package.json sets "type": "module" for Vite's
sake, and that choice reaches further than the browser: any relative import that plain
Node resolves directly — a server entry point, a script, a native-ESM test runner —
now needs its
explicit .js extension too; see
Fix ERR_MODULE_NOT_FOUND: relative import missing .js
if that is the next error. Testing tends to surface a different one: @swc/jest and
other default Jest transforms still emit CommonJS, and import.meta syntax is only
valid inside an ES module — so the same file Vite imports fine throws
Cannot use 'import.meta' outside a module the moment Jest's CommonJS transform runs
it; see
Cannot use 'import.meta' outside a module: Jest + Vite fix.
Separately, webpack used to polyfill several Node globals in the browser and Vite does
not, which is a different, unrelated gap — covered in
Fix Vite 'global is not defined'.
Migrating from webpack to Vite trades one set of module-resolution defaults for another.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix: exports is not defined in ES module scope
How Node decides CommonJS vs ES module, and three remedies for aligning source syntax, compiler output and the runtime.
Fix ERR_MODULE_NOT_FOUND: relative import missing .js
Why an Express app with "type": "module" cannot find ./config/app, why the flags from older answers now do nothing, and how TypeScript catches it at compile time.
Cannot use 'import.meta' outside a module: Jest + Vite fix
Three fixes for import.meta.env in Jest tests of a Vite app, each reproduced, plus the secret-leak trap in the popular process.env answer.


