Broken npm package

Some npm packages contain invalid JavaScript code.

See Workaround.

The reason why some npm packages contain invalid code is because the transpilation process of webpack-based frameworks, most notably Next.js, isn't strict and tolerates erroneous code: thus an npm package can work with Next.js while not work with Vite-based frameworks. You can learn more at Why.

The situation is going to improve a lot and eventually be completely resolved as Vite-based frameworks gain popularity, which is happening quickly as all frameworks other than Next.js are now Vite-based.

Workaround

You can workaround problematic npm packages by using on of the following:

  • ssr.noExternal
  • vite-plugin-cjs-interop
  • SSR opt-out

The workarounds usually work. But if you struggle working around a broken npm package then feel free to reach out for help.

ssr.noExternal

// vite.config.js
 
export default {
  ssr: {
    // Add problematic npm package here:
    noExternal: ['some-npm-package']
  }
}

See Vite > ssr.noExternal.

You may need to add nested npm packages to ssr.noExternal, because CJS/ESM issues somtimes cascade down along the "noExternal boundary" as you add npm packages to ssr.noExternal.

The section Why explains why ssr.noExternal is a workaround.

vite-plugin-cjs-interop

Upon certain errors, we recommend trying the vite-plugin-cjs-interop plugin before trying ssr.noExternal.

// vite.config.js
 
import { cjsInterop } from "vite-plugin-cjs-interop";
 
export default {
  plugins: [
    cjsInterop({
      // Add broken npm package here
      dependencies: [
        // Apply patch to root import:
        //   import someImport from 'some-package'
        "some-package",
 
        // Apply patch to all sub imports:
        //   import someImport from 'some-package/path'
        //   import someImport from 'some-package/sub/path'
        //   ...
        "some-package/**",
      ]
    })
  ]
}

See GitHub > cyco130/vite-plugin-cjs-interop.

Errors for which we recommend trying vite-plugin-cjs-interop before trying ssr.noExternal:

React invalid component
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: something-else.
Named export not found
SyntaxError: Named export 'SomeImport' not found. The requested module 'some-library' is a CommonJS module, which may not support all module.exports as named exports.

SSR opt-out

As a last resort, if both ssr.noExternal and vite-plugin-cjs-interop doesn't work, you can opt out of SSR:

Opting out of SSR almost always works, see explanation at Why.

Why

You may ask yourself how it's possible that an npm package can publish invalid JavaScript code that doesn't get fixed for months.

The main reason is that some frameworks such as Next.js transpile the server-side code of npm packages, whereas Vite transpiles only the client-side code of npm packages. When server-side code contains invalid JavaScript then Node.js crashes and throws one of these errors, while transpilers are more tolerant and transform invalid JavaScript (that Node.js isn't able to execute) into valid JavaScript (that Node.js is able to execute).

By default, Vite doesn't transpile the server-side code of npm packages for a faster development DX, so that Node.js directly executes server-side code without involving a slow transpilition process.

That's why adding an npm package to ssr.noExternal is usually a workaround when the npm package contains invalid JavaScript. By adding an npm package to ssr.noExternal, you replicate the behavior of frameworks like Next.js.

The issue is most widespread in the React ecosystem because of Next.js's prevalence, but this is going to less and less of an issue as Vite-based React frameworks gain popularity. All frameworks other than Next.js are now Vite-based (e.g. Remix), thus the situation will quickly improve.

Common errors

Common invalid JavaScript code published by npm packages.

Cannot use import statement outside a module

(node:30335) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
node_modules/some-library/dist/esm/index.js:1
SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1033:15)
    at Module._compile (node:internal/modules/cjs/loader:1069:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Module._load (node:internal/modules/cjs/loader:827:12)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:170:29)
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:409:24)

Node.js v18.0.0

Node.js's message set "type": "module" in package.json or use the .mjs extension is misleading because it means the library's node_modules/some-library/package.json, not your package.json. It isn't really actionable (unless you patch the library).

Recommended workaround:

Alternatively, you can try to patch the broken npm package with:

Named export not found

import { SomeImport } from "some-library";
         ^^^^^^^^^^
SyntaxError: Named export 'SomeImport' not found. The requested module 'some-library' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from 'some-library';
const { SomeImport } = pkg;

    at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:190:5)

Node.js v18.0.0

For default exports, the workaround proposed by Node.js may not work and you may need to do this instead:

import DefaultImport from "some-library"; 
import pkg from 'some-library'; 
const DefaultImport = pkg.default ?? pkg; 

For a workaround that is global (and TypeScript friendly), see vite-plugin-cjs-interop.

ERR_MODULE_NOT_FOUND

node:internal/process/esm_loader:91
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'node_modules/some-library/dist/esm/some-file' imported from node_modules/some-library/dist/esm/index.js
Did you mean to import some-file.js?
    at new NodeError (node:internal/errors:372:5)
    at finalizeResolution (node:internal/modules/esm/resolve:405:11)
    at moduleResolve (node:internal/modules/esm/resolve:966:10)
    at defaultResolve (node:internal/modules/esm/resolve:1176:11)
    at ESMLoader.resolve (node:internal/modules/esm/loader:605:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:318:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
    at link (node:internal/modules/esm/module_job:78:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v18.0.0

The error is usually thrown when the library's ESM code contains import './some-file'. (It should be import './some-file.js' instead, as imports in ESM code are required to include the file extension.)

Recommended workaround: ssr.noExternal.

ERR_UNSUPPORTED_DIR_IMPORT

Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import 'node_modules/some-library/dist/some-dir/' is not supported resolving ES modules imported from node_modules/some-library/dist/index.js
Did you mean to import ./some-dir/index.js?
    at finalizeResolution (node:internal/modules/esm/resolve:412:17)

ESM doesn't allow directory imports: all import paths need to point to a filename instead.

Recommended workaround: ssr.noExternal.

ERR_UNKNOWN_FILE_EXTENSION .css

Another common problem is code importing CSS which makes Node.js crash:

Error: ERR_UNKNOWN_FILE_EXTENSION .css node_modules/some-library/dist/main.css
    at someFunction (node_modules/some-library/dist/main.js)
    at nextLoad (node:internal/modules/esm/loader:163:28)
    at ESMLoader.load (node:internal/modules/esm/loader:605:26)

Recommended workaround: ssr.noExternal.

Cannot read properties of undefined

The error Cannot read properties of undefined is often caused by ESM/CJS issues.

TypeError: Cannot read properties of undefined (reading 'someProp')
    at someFunction (node_modules/some-good-lib/dist/index.js:1000:3)
    at someHook (renderer/+someHook.js:13:37)

The underlying issue is often this:

// node_modules/some-good-lib/dist/index.js
 
// Because of CJS/ESM issues, someImport is undefined
import { someImport } from 'some-broken-lib'
 
// ...
 
function someFunction() {
  // TypeError: Cannot read properties of undefined (reading 'someProp')
  someImport.someProp
}

Sometimes, when dependency injection is used, the import to some-broken-lib isn't in the file in which the exception is being raised, making it harder to understand which library is broken. See here an example of this.

Adding some-broken-lib to ssr.noExternal usually solves the issue.

Alternatively, you can add some-good-lib to ssr.noExternal while adding some-broken-lib to vite-plugin-cjs-interop.

React invalid component

The following is a common React error and the root cause is usually a CJS/ESM issue.

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.
    at renderElement (node_modules/react-dom/...)
    at renderNodeDestructiveImpl (node_modules/react-dom/...)
    at renderNodeDestructive (node_modules/react-dom/...)
    ...

Or got: object.

Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

React usually logs a component trace before throwing the error:

Check your code at +Page.tsx:26.
    at Page
    at div
    at div
    at Layout (/pages/+Layout.tsx:66:19)

Sometimes React doesn't log a component trace. In that case you can use this temporary patch to get a component trace.

You can also use the temporary patch to get a more precise component trace. (For example the component trace above says Check your code at +Page.tsx:26 but there can be hundreds of +Page.tsx files.)

Use the component trace to find out which component is undefined / an object. You'll likely see that the component is imported and that the import value is undefined / an object (instead of a React component) because of CJS/ESM interoperability quirks.

A local workaround is usually this:

import SomeComponent from "some-npm-package"
import pkg from "some-npm-package"
// This:
const { SomeComponent } = pkg 
// Or that:
const SomeComponent = pkg.default 

For a workaround that is global (and TypeScript friendly), see vite-plugin-cjs-interop.