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
Copy link
Copy Markdown
Member

@oliverlaz oliverlaz commented May 27, 2026

🎯 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
Copy link
Copy Markdown

github-actions Bot commented May 27, 2026

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
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants