Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[code-infra] Add use-sync-external-store shim for useMediaQuery #43476

Closed
wants to merge 3 commits into from

Conversation

Janpot
Copy link
Member

@Janpot Janpot commented Aug 27, 2024

We can keep this as long as we're supporting React <18.

I noticed a bundle size increase in #43264. Caused by a webpack interop helper that gets included when you access module bindings in a weird way. The reason is a backwards compatible shim that was created around the absent useSyncExternalStore hook.

const maybeReactUseSyncExternalStore: undefined | any = (React as any)['useSyncExternalStore' + ''];

This PR to assess the impact of using this shim. It looks like it's an increase, but the results need to be interpreted. use-sync-external-store is used by many well known packages. There is a great chance it already appears in our user's bundles. In which case we may as well choose to ignore it in our bundle size calculations. It's coincidence, but the increase that use-sync-external-store brings in this change is about equal to the increase the webpack interop helper brings in #43264.

Pretty sure the useId shim is causing the same problem

const maybeReactUseId: undefined | (() => string) = (React as any)['useId'.toString()];

@mui/code-infra What's your opinion on this? I'm inclined to abandon this PR and instead accept the bundle size increase of #43264 as a bundler specific implementation detail.

@Janpot Janpot added the scope: code-infra Specific to the core-infra product label Aug 27, 2024
@mui-bot
Copy link

mui-bot commented Aug 27, 2024

Netlify deploy preview

https://deploy-preview-43476--material-ui.netlify.app/

Hidden: parsed: +0.99% , gzip: +1.16%
@material-ui/core/useMediaQuery: parsed: +5.37% , gzip: +5.52%
PigmentHidden: parsed: +1.14% , gzip: +1.24%
@material-ui/core: parsed: +0.09% , gzip: +0.15%
@material-ui/system: parsed: +0.56% , gzip: +0.71%

Bundle size report

Details of bundle changes (Toolpad)
Details of bundle changes

Generated by 🚫 dangerJS against d368562

@Janpot Janpot changed the title [code-infra] Add use-sync-external-store shim [code-infra] Add use-sync-external-store shim for useMediaQuery Aug 27, 2024
@oliviertassinari
Copy link
Member

Pretty sure the useId shim is causing the same problem

It's changing in #43360.

let query = typeof queryInput === 'function' ? queryInput(theme) : queryInput;
query = query.replace(/^@media( ?)/m, '');

// TODO: Drop `useMediaQueryOld` and use `use-sync-external-store` shim in `useMediaQueryNew` once the package is stable
Copy link
Member

@oliviertassinari oliviertassinari Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand this comment Sebastian left, is useMediaQueryOld bigger than use-sync-external-store?

Copy link
Member Author

@Janpot Janpot Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useMediaQueryOld is smaller. But use-sync-external-store is not huge(*), and it's likely already present in our user's bundles. which in that specific case means us depending on it doesn't add extra cost.

(*)
Screenshot 2024-08-28 at 05 13 58

Copy link
Member

@oliviertassinari oliviertassinari Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tricky, it's used a lot indeed: https://npm-stat.com/charts.html?package=use-sync-external-store,react-dom.

I'm worried about harming people who make the effort to keep their stack up to date, for the benefit of those who want to use the latest features without paying the price for it. It will take a while for use-sync-external-store to be gone from the bundle of end-users.

We could have:

Suggested change
// TODO: Drop `useMediaQueryOld` and use `use-sync-external-store` shim in `useMediaQueryNew` once the package is stable
// TODO: Remove `useMediaQueryOld` once React 17 support is removed

Copy link
Member Author

@Janpot Janpot Aug 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's why I'm ultimately abandoning this. I just wanted to capture the impact.
One solution I was pondering is to provide a shim for older older React APIs. Users would have to manually import a module in the top-level if they want to use modern @mui/material on outdated react. e.g.

// import this before anything else in user code if you're on legacy react
import '@mui/shims/setup-react-17'

implementation:

// we import this where we need `useSyncExternalStore`
import { useSyncExternalStore } from '@mui/shims/useSyncExternalStore'
// @mui/shims/setup-react-17
import { useSyncExternalStore } from 'use-sync-external-store'
globalThis._muiShims ?= {}
globalThis._muiShims.useSyncExternalStore = useSyncExternalStore
globalThis._muiShims.useId = () => {
  // ...
}
// @mui/shims/useSyncExternalStore
import * as React from 'react'
import invariant from 'invariant'
const _React = { ...React }
const useSyncExternalStore = _React.useSyncExternalStore || globalThis._muiShims?.useSyncExternalStore
invariant(useSyncExternalStore, "MUI shims not installed, If you're on legacy React, please follow the guide at https://mui.com/...")
export { useSyncExternalStore }

That way you only pay the cost of a shim if you choose to stay on react 17. Have to say though that it's probably way overkill just to save 0.5kB.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have to say though that it's probably way overkill just to save 0.5kB.

😄

@Janpot
Copy link
Member Author

Janpot commented Aug 28, 2024

It's changing in #43360.

That solution causes the same webpack helper to be used. I think it's unavoidable. We can close this PR and just wait until we deprecate React 17.

@Janpot Janpot closed this Aug 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope: code-infra Specific to the core-infra product
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants