You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Vite is pushing for ESM adoption, which is a great thing.
However, there are a few common import patterns that are known to produce bundles that would crash once executed as ESM. With no baked-in handling of these patterns, there is a certain amount of risk that comes with adopting Vite that does not seem to exist with older non-ESM bundlers such as webpack.
This ticket is opened to highlight three of these patterns for reference and inspiration, in case Vite can be made safer.
--
Mixing require() in ESM
While invalid ESM, it is not that unusual to find require() calls in ESM code.
require('lodash');
^
ReferenceError: require is not defined in ES module scope, you can use import instead
This can happen as developers try to find a synchronous way to conditionally import another module, something ESM does not allow for. This is also something that was (sadly) handled by webpack, so new Vite users migrating from webpack can be surprised by it and not immediately understand when require() and CJS is safe to use or not with Vite.
Workaround
It is possible to use ESLint (e.g. unicorn/prefer-module) to partially mitigate this risk. However linting does not reach in node_modules where packages could also be mixing ESM and CJS.
--
(SSR) Missing interop for externalized CJS modules
While ESM is technically able to import CJS libraries under node.js, named imports can be missing when they cannot be statically evaluated by node.js.
In such cases, the named imports must be extracted from the default export, otherwise a runtime crash will occur:
import { once } from "lodash";
^^^^
SyntaxError: Named export 'once' not found. The requested module 'lodash' 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 'lodash';
const { once } = pkg;
Historically, bundlers such as webpack and Vite have abstracted this problem in client bundles. However, Vite is not providing a similar interop support for externalized dependencies in SSR bundles. And since it is commonplace for package types to assume interop, developers cannot rely on typescript to know that using named imports from a library like lodash is unsafe.
Nested default exports (UMD, __esModule) are also not resolved by node.js:
import styled from 'styled-components';
styled(() => {})``;
styled(() => {})``;
^
TypeError: styled is not a function
Workaround
The detection of this pattern is difficult. However once a problematic library is found, it is possible to use vite-plugin-cjs-interop against it. Ideally this would be part of Vite and applied automatically.
--
(SSR) Missing default export for internalized CJS modules
Internalizing a CJS library and then running it as ESM in node.js can crash.
Once CJS code is turned into ESM and run as ESM, node.js may now resolve all imports of that library to the ESM version of other libraries, which may no longer have anything but named exports.
Example CJS library code in node_modules:
var framerMotion = require('framer-motion');
function noop() {
console.log(framerMotion.useAnimation);
}
module.exports = { noop };
Before bundling:
import { noop } from 'library-above';
console.log(noop);
After bundling:
import require$$1 from "framer-motion";
var _framerMotion = require$$1;
function noop(source, excluded) {
console.log(_framerMotion.useTransform);
}
var noop_1 = noop;
console.log(noop_1);
import require$$1 from 'framer-motion';
^^^^^^^^^^
SyntaxError: The requested module 'framer-motion' does not provide an export named 'default'
The framer-motion library, when resolved as ESM, does not have a default export, only named exports. The bundler's translation of CJS to ESM seems right on paper, but the existence of a default export cannot be safely taken for granted.
Workaround
The detection of this pattern is difficult. However once a problematic library is found, either avoiding internalization, or conversely, internalizing the problematic library can help. However, internalization is risky as it can accidentally lead to bundling one library twice, something that can cause crashes in certain cases (e.g. React context providers).
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Vite is pushing for ESM adoption, which is a great thing.
However, there are a few common import patterns that are known to produce bundles that would crash once executed as ESM. With no baked-in handling of these patterns, there is a certain amount of risk that comes with adopting Vite that does not seem to exist with older non-ESM bundlers such as webpack.
This ticket is opened to highlight three of these patterns for reference and inspiration, in case Vite can be made safer.
--
Mixing require() in ESM
While invalid ESM, it is not that unusual to find require() calls in ESM code.
This can happen as developers try to find a synchronous way to conditionally import another module, something ESM does not allow for. This is also something that was (sadly) handled by webpack, so new Vite users migrating from webpack can be surprised by it and not immediately understand when require() and CJS is safe to use or not with Vite.
Workaround
It is possible to use ESLint (e.g. unicorn/prefer-module) to partially mitigate this risk. However linting does not reach in node_modules where packages could also be mixing ESM and CJS.
--
(SSR) Missing interop for externalized CJS modules
While ESM is technically able to import CJS libraries under node.js, named imports can be missing when they cannot be statically evaluated by node.js.
In such cases, the named imports must be extracted from the default export, otherwise a runtime crash will occur:
Historically, bundlers such as webpack and Vite have abstracted this problem in client bundles. However, Vite is not providing a similar interop support for externalized dependencies in SSR bundles. And since it is commonplace for package types to assume interop, developers cannot rely on typescript to know that using named imports from a library like lodash is unsafe.
Nested default exports (UMD, __esModule) are also not resolved by node.js:
Workaround
The detection of this pattern is difficult. However once a problematic library is found, it is possible to use
vite-plugin-cjs-interop
against it. Ideally this would be part of Vite and applied automatically.--
(SSR) Missing default export for internalized CJS modules
Internalizing a CJS library and then running it as ESM in node.js can crash.
Once CJS code is turned into ESM and run as ESM, node.js may now resolve all imports of that library to the ESM version of other libraries, which may no longer have anything but named exports.
Example CJS library code in node_modules:
Before bundling:
After bundling:
The framer-motion library, when resolved as ESM, does not have a default export, only named exports. The bundler's translation of CJS to ESM seems right on paper, but the existence of a default export cannot be safely taken for granted.
Workaround
The detection of this pattern is difficult. However once a problematic library is found, either avoiding internalization, or conversely, internalizing the problematic library can help. However, internalization is risky as it can accidentally lead to bundling one library twice, something that can cause crashes in certain cases (e.g. React context providers).
Beta Was this translation helpful? Give feedback.
All reactions