Skip to content

fix(interop): unwrap CJS default exports (react-player, @emoji-mart/react) - #3199

Merged
oliverlaz merged 2 commits into
masterfrom
fix/react-player-default-export
May 27, 2026
Merged

fix(interop): unwrap CJS default exports (react-player, @emoji-mart/react)#3199
oliverlaz merged 2 commits into
masterfrom
fix/react-player-default-export

Conversation

@oliverlaz

@oliverlaz oliverlaz commented May 27, 2026

Copy link
Copy Markdown
Member

🎯 Goal

Fix a runtime crash β€” Uncaught Error: Element type is invalid: expected a string ... but got: object β€” when components backed by externalized CommonJS dependencies are rendered under spec-strict ESM↔CJS interop. Two components are affected:

  • VideoPlayer β†’ react-player
  • EmojiPicker β†’ @emoji-mart/react

This first surfaced in the Vite example, which consumes the SDK's built ESM (dist/es) as a linked workspace dependency. Vite 8 swapped its dependency optimizer from esbuild to Rolldown. esbuild honored the Babel __esModule marker and unwrapped .default; Rolldown follows spec-strict interop where a CJS module's ESM default export is module.exports itself. So import ReactPlayer from 'react-player' (and import Picker from '@emoji-mart/react') now resolve to the module namespace { default, __esModule } β€” an object β€” and rendering it throws. Native Node ESM behaves identically, so this was never spec-portable; it only worked under bundlers that honored __esModule.

πŸ›  Implementation details

Both packages ship as CJS with the real export on exports.default. We unwrap the interop default defensively at each import site:

import ReactPlayerImport from 'react-player';
const ReactPlayer =
  (ReactPlayerImport as unknown as { default?: typeof ReactPlayerImport }).default ??
  ReactPlayerImport;
import PickerImport from '@emoji-mart/react';
const Picker =
  (PickerImport as unknown as { default?: typeof PickerImport }).default ?? PickerImport;

When the bundler already provides the component (the normal case β€” esbuild, webpack, pre-Vite-8), .default is undefined and it falls back to the import unchanged. So this is a no-op for existing consumers and resolves the got: object crash only where the namespace leaks through.

Audit. Every default-imported externalized dependency in src/ was checked against native-Node-ESM (≑ Rolldown strict) interop. Only react-player and @emoji-mart/react exhibit the hazard signature (object default carrying { __esModule, default }). All others are safe:

  • Classic CJS exporting the value on module.exports: clsx, dayjs + all dayjs/plugin/*, lodash.debounce/mergewith/throttle/uniqby, emoji-regex, fix-webm-duration, react-fast-compare, moment-timezone
  • Real ESM: react-markdown, remark-gfm
  • Legitimately object-valued defaults used correctly (member access / valid React element type): react (namespace), i18next (instance), react-textarea-autosize (forwardRef)

Files changed: src/components/VideoPlayer/VideoPlayer.tsx, src/plugins/Emojis/EmojiPicker.tsx. tsc, eslint, and prettier are clean; existing VideoAttachment tests pass.

🎨 UI Changes

No visual changes. Restores VideoPlayer and EmojiPicker rendering in environments where they previously crashed (Vite 8 / Rolldown, native Node ESM, strict-interop SSR). No screenshots applicable.

react-player ships as CommonJS with the component on `exports.default`.
Some bundler/interop setups (e.g. Vite serving our built ESM as a linked
workspace dependency) hand back the module namespace `{ default }` instead
of the component, causing React to throw "Element type is invalid ... got:
object". Unwrap the default defensively so VideoPlayer renders regardless
of how the consumer's bundler handles interop.
@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

Size Change: +69 B (+0.01%)

Total Size: 651 kB

πŸ“¦ View Changed
Filename Size Change
dist/cjs/emojis.js 2.54 kB +10 B (+0.4%)
dist/cjs/index.js 256 kB +18 B (+0.01%)
dist/es/emojis.mjs 2.47 kB +16 B (+0.65%)
dist/es/index.mjs 253 kB +21 B (+0.01%)
dist/es/useNotificationApi.mjs 44.9 kB +4 B (+0.01%)
ℹ️ View Unchanged
Filename Size
dist/cjs/audioProcessing.js 1.74 kB
dist/cjs/mp3-encoder.js 814 B
dist/cjs/useNotificationApi.js 46.2 kB
dist/css/emoji-picker.css 178 B
dist/css/emoji-replacement.css 456 B
dist/css/index.css 39.7 kB
dist/es/audioProcessing.mjs 1.65 kB
dist/es/mp3-encoder.mjs 768 B

compressed-size-action

@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.
βœ… Project coverage is 83.71%. Comparing base (ce4352d) to head (54bf3b0).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3199      +/-   ##
==========================================
- Coverage   83.73%   83.71%   -0.03%     
==========================================
  Files         435      435              
  Lines       13127    13129       +2     
  Branches     4249     4251       +2     
==========================================
- Hits        10992    10991       -1     
- Misses       2135     2138       +3     

β˜” View full report in Codecov by Sentry.
πŸ“’ Have feedback on the report? Share it here.

πŸš€ New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • πŸ“¦ JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Same interop hazard as the react-player fix: @emoji-mart/react ships as
CommonJS with the component on `exports.default`. Under spec-strict ESM
interop (Vite 8 / Rolldown, native Node ESM) the default import resolves to
the module namespace `{ default }` instead of the component, causing React
to throw "Element type is invalid ... got: object" when rendering <Picker>.
Unwrap the default defensively.
@oliverlaz oliverlaz changed the title fix(VideoPlayer): unwrap react-player CJS default export fix(interop): unwrap CJS default exports (react-player, @emoji-mart/react) May 27, 2026
@oliverlaz
oliverlaz merged commit 4cddb02 into master May 27, 2026
8 checks passed
@oliverlaz
oliverlaz deleted the fix/react-player-default-export branch May 27, 2026 11:56
github-actions Bot pushed a commit that referenced this pull request Jun 5, 2026
## [14.4.0](v14.3.0...v14.4.0) (2026-06-05)

### Bug Fixes

* **compat:** restore React 17/18 compatibility in certain components ([#3197](#3197)) ([b513b69](b513b69))
* general performance and bug fixes ([#3201](#3201)) ([57c5795](57c5795))
* **interop:** unwrap CJS default exports (react-player, @emoji-mart/react) ([#3199](#3199)) ([4cddb02](4cddb02))
* maintain topmost modal non-inert ([#3206](#3206)) ([7ad98fa](7ad98fa))

### Features

* allow to stack modals on top of each other ([#3203](#3203)) ([4c934ae](4c934ae))
* display notifications above modals ([#3200](#3200)) ([0433090](0433090))
* **Reactions:** send emoji_code with reactions for push notification rendering ([#3209](#3209)) ([2faa620](2faa620))

### Performance Improvements

* **Message:** hoist regex compilation in message text rendering ([#3202](#3202)) ([8c018a4](8c018a4))
* **VideoPlayer:** lazy-load react-player to keep it out of the main bundle ([#3204](#3204)) ([18dc966](18dc966))
@stream-ci-bot

Copy link
Copy Markdown

πŸŽ‰ This PR is included in version 14.4.0 πŸŽ‰

The release is available on:

Your semantic-release bot πŸ“¦πŸš€

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants