Skip to content

React error #130 when bundled by Vite 8 / rolldown: default import is the CJS namespace, not the ReactJWPlayer class #207

@esetnik

Description

@esetnik

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:

// package.json
"pnpm": {
  "patchedDependencies": {
    "react-jw-player@1.19.1": "patches/react-jw-player@1.19.1.patch"
  }
}
# 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions