Description
When a React app with "type": "module" in its package.json is bundled by Vite 8 (which ships rolldown as its bundler), import ReactJWPlayer from 'react-jw-player' resolves to the CJS namespace object {default: ReactJWPlayer, __esModule: true} at runtime — not the ReactJWPlayer class. Any <ReactJWPlayer /> render site then receives an object as its component type and throws:
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
(minified: React error #130; visit https://react.dev/errors/130?args[]=object&args[]=)
Root cause
dist/react-jw-player.js ends with:
ReactJWPlayer.defaultProps = _defaultProps2.default;
ReactJWPlayer.displayName = displayName;
ReactJWPlayer.propTypes = _playerPropTypes2.default;
exports.default = ReactJWPlayer;
It sets exports.default and flags __esModule: true (via the Object.defineProperty(exports, "__esModule", {value: true}) at the top), but it never re-assigns module.exports = exports["default"]. When the consumer is flagged "type": "module", rolldown follows Node's ESM-for-CJS convention and treats the default import as module.exports itself — which is the whole namespace {default: ReactJWPlayer, __esModule: true}, not the class.
This is documented / intentional behavior on rolldown's side:
The de-facto workaround in the ecosystem is for the CJS package itself to re-export its default on module.exports — e.g. react-modal's own lib/index.js ends with:
module.exports = exports["default"];
So module.exports equals the class at runtime regardless of which CJS-default-import heuristic the bundler picks.
Reproduction
Minimal setup (any Vite 8 project with "type": "module" works):
// src/main.tsx
import ReactJWPlayer from 'react-jw-player';
import { createRoot } from 'react-dom/client';
console.log('typeof ReactJWPlayer =', typeof ReactJWPlayer); // 'object' under vite 8 — should be 'function'
createRoot(document.getElementById('root')!).render(
<ReactJWPlayer
playerId="test"
playerScript="https://content.jwplatform.com/libraries/<YOUR_LIB_ID>.js"
file="https://example.com/video.mp4"
/>
);
// package.json excerpt
{
"type": "module",
"dependencies": {
"react": "19.2.5",
"react-dom": "19.2.5",
"react-jw-player": "1.19.1",
"vite": "8.0.8"
}
}
$ pnpm build && pnpm preview
# open http://localhost:4173
# Console: Uncaught Error: Minified React error #130; ... args[]=object
Reproduces under Vite 8.0.x (rolldown). Works fine under Vite 7.x (pre-rolldown) and under webpack / Rollup / esbuild.
Suggested fix
The compiled entry should end up with module.exports pointing at the ReactJWPlayer class directly. Two equivalent ways to get there — PR #208 uses the first:
1. Source-level (proposed in #208) — in src/react-jw-player.jsx:
ReactJWPlayer.defaultProps = defaultProps;
ReactJWPlayer.displayName = displayName;
ReactJWPlayer.propTypes = propTypes;
-export default ReactJWPlayer;
+module.exports = ReactJWPlayer;
+module.exports.default = ReactJWPlayer;
After babel compiles, dist/react-jw-player.js ends with:
module.exports = ReactJWPlayer;
module.exports.default = ReactJWPlayer;
No new dependency, no build-config change.
2. Babel-plugin alternative — keep src/react-jw-player.jsx as export default ReactJWPlayer; and add babel-plugin-add-module-exports to .babelrc:
{
- "presets": ["es2015", "react"],
- "plugins": ["transform-object-assign"]
+ "presets": ["es2015", "react"],
+ "plugins": ["transform-object-assign", "add-module-exports"]
}
That plugin is widely used for this exact interop fix but hasn't had a release since 2019, which is why I went with the source-level approach in #208. Either works functionally.
Workaround for consumers today
For pnpm users:
# patches/react-jw-player@1.19.1.patch
diff --git a/dist/react-jw-player.js b/dist/react-jw-player.js
--- a/dist/react-jw-player.js
+++ b/dist/react-jw-player.js
@@ -180,3 +180,4 @@ ReactJWPlayer.defaultProps = _defaultProps2.default;
ReactJWPlayer.displayName = displayName;
ReactJWPlayer.propTypes = _playerPropTypes2.default;
exports.default = ReactJWPlayer;
+module.exports = exports["default"];
For npm/yarn users, the equivalent with patch-package.
Description
When a React app with
"type": "module"in itspackage.jsonis bundled by Vite 8 (which ships rolldown as its bundler),import ReactJWPlayer from 'react-jw-player'resolves to the CJS namespace object{default: ReactJWPlayer, __esModule: true}at runtime — not theReactJWPlayerclass. Any<ReactJWPlayer />render site then receives an object as its component type and throws:(minified:
React error #130; visit https://react.dev/errors/130?args[]=object&args[]=)Root cause
dist/react-jw-player.jsends with:It sets
exports.defaultand flags__esModule: true(via theObject.defineProperty(exports, "__esModule", {value: true})at the top), but it never re-assignsmodule.exports = exports["default"]. When the consumer is flagged"type": "module", rolldown follows Node's ESM-for-CJS convention and treats the default import asmodule.exportsitself — which is the whole namespace{default: ReactJWPlayer, __esModule: true}, not the class.This is documented / intentional behavior on rolldown's side:
The de-facto workaround in the ecosystem is for the CJS package itself to re-export its default on
module.exports— e.g.react-modal's ownlib/index.jsends with:So
module.exportsequals the class at runtime regardless of which CJS-default-import heuristic the bundler picks.Reproduction
Minimal setup (any Vite 8 project with
"type": "module"works):Reproduces under Vite 8.0.x (rolldown). Works fine under Vite 7.x (pre-rolldown) and under webpack / Rollup / esbuild.
Suggested fix
The compiled entry should end up with
module.exportspointing at theReactJWPlayerclass directly. Two equivalent ways to get there — PR #208 uses the first:1. Source-level (proposed in #208) — in
src/react-jw-player.jsx:After babel compiles,
dist/react-jw-player.jsends with:No new dependency, no build-config change.
2. Babel-plugin alternative — keep
src/react-jw-player.jsxasexport default ReactJWPlayer;and addbabel-plugin-add-module-exportsto.babelrc:{ - "presets": ["es2015", "react"], - "plugins": ["transform-object-assign"] + "presets": ["es2015", "react"], + "plugins": ["transform-object-assign", "add-module-exports"] }That plugin is widely used for this exact interop fix but hasn't had a release since 2019, which is why I went with the source-level approach in #208. Either works functionally.
Workaround for consumers today
For pnpm users:
For npm/yarn users, the equivalent with
patch-package.