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

[material-ui][mui-system] Add support for version runtime checks #43190

Merged
merged 6 commits into from
Aug 7, 2024

Conversation

DiegoAndai
Copy link
Member

@DiegoAndai DiegoAndai commented Aug 5, 2024

Add a version const export that matches the current version for runtime checks on package consumers. This version is added in compilation time (at build). I only added this const to released stable packages Material and System, but let me know if I should add it for any others.

Use case: For MUI X and Toolpad to be able to support multiple versions of Material UI.

Example usage: https://codesandbox.io/p/sandbox/43190-version-exports-example-q9fkt6?file=%2Fsrc%2FApp.tsx%3A9%2C1

@DiegoAndai DiegoAndai added package: system Specific to @mui/system package: material-ui Specific to @mui/material labels Aug 5, 2024
@DiegoAndai DiegoAndai self-assigned this Aug 5, 2024
@DiegoAndai DiegoAndai requested a review from DanailH August 5, 2024 21:43
@mui-bot
Copy link

mui-bot commented Aug 5, 2024

Netlify deploy preview

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

Bundle size report

Details of bundle changes (Toolpad)
Details of bundle changes

Generated by 🚫 dangerJS against e06ebc8

@DiegoAndai DiegoAndai changed the title [material-ui][mui-system] Add support for version const [material-ui][mui-system] Add support for version runtime checks Aug 5, 2024
@Janpot
Copy link
Member

Janpot commented Aug 5, 2024

Usually something like this would be done in the compilation step. All transpilers that I know of have the ability to replace "env vars" at build time. For babel: https://babeljs.io/docs/babel-plugin-transform-inline-environment-variables

i.e.

  • read package.json in babel.config.js. extract version
  • pass version as MUI_VERSION to this plugin
  • export const version = process.env.MUI_VERSION

@DiegoAndai
Copy link
Member Author

Thanks for pointing this out @Janpot! I refactored the code accordingly

@DiegoAndai
Copy link
Member Author

Looking into the test failure

@Janpot
Copy link
Member

Janpot commented Aug 6, 2024

This looks great. Gonna add a few more reflections that don't have to be added in this PR. They can perfectly be addressed in a backwards compatible manner if accepted.

As I understand, one use-case of this value would be feature detection by dependent libraries that have @mui/material as a peer dependency.

  • By only exporting it from the top-level, dependants will have to import the whole module to get access to a simple string constant. Perhaps it makes sense to additionally export only this value from a subpath. e.g.

    import version from '@mui/material/version'
  • To effectively feature detect, I'd likely want to compare the major version with a specific range. Likely I'll have to

    import version from '@mui/material/version'
    const [major] = version.split('.')
    const requiresCompatibilityMode = major < 6

    If this is the main use-case it may make sense to export separate semver parts as well (or instead). This avoids an unnecessary string parse.

    import { major } from '@mui/material/version'
    const requiresCompatibilityMode = major < 6

@DiegoAndai
Copy link
Member Author

@Janpot this makes sense, I'll implement it 👌🏼

@DiegoAndai
Copy link
Member Author

DiegoAndai commented Aug 6, 2024

@Janpot do you know why eslint is throwing here: https://app.circleci.com/pipelines/github/mui/material-ui/135447/workflows/6af808b2-8a69-469d-8943-61a357050586/jobs/730150

Edit: Nvm, I figured it out

Copy link
Member

@Janpot Janpot left a comment

Choose a reason for hiding this comment

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

Great 👍

@DiegoAndai DiegoAndai merged commit f8a6e3c into mui:next Aug 7, 2024
19 checks passed
@DiegoAndai DiegoAndai deleted the support-version-const branch August 7, 2024 12:44
DiegoAndai added a commit to DiegoAndai/material-ui that referenced this pull request Aug 8, 2024
@oliviertassinari
Copy link
Member

oliviertassinari commented Aug 10, 2024

Is there context on the why behind this change?

Looking at this from the outside, I'm confused. I see we add a bit of bundle size/complexity for something that it seems we can't start to use after 2 major versions, so before some time (for example, it can't be used by MUI X v7 with Material UI v5 as it's a breaking change to import a module that doesn't exist, it can't be used with Material UI v6 as we need two majors to compare to have it useful)

We faced similar problems in the past, for React, for Material UI v4 vs. v5, etc. Usually, feature detection was enough, e.g. https://github.com/mui/mui-x/pull/2108/files#diff-b4487b0ccf1b0178520560e5e9b18efbd0d4030106ae794bda6638be8cbdd526R44. Back then, we discussed making a PR close to this one, we didn't because of lack of clear visibility on a use case.

But I imagine there was a different thought process? Related to mui/mui-x#14055?


More importantly, let's make it so that all PRs are self-sustaining. Meaning to not depend on private conversions: Notion, Email, meeting, Slack, etc to be understood. This is important:

  • for maintainers (author of the PR included, memory tends to not be reliable 😄) so they can audit the changes we did many years in the past and decide how to move from them
  • for the community to trust our work (so use it)
  • for the community to falsify what we do (raise inefficiencies we can fix)

@DiegoAndai
Copy link
Member Author

Is there context on the why behind this change?

The context was that it was required for MUI X components to support Material UI v5 and v6. Sorry for not including it from the beginning, I've updated the PR's description.

Besides that, this seems useful for any developers supporting more than one Material UI version. This is modeled after React's version const, so developers are familiar with the model. It does no harm and it is useful, and the amount of bundle size and complexity added is minimal.

it seems we can't start to use after 2 major versions

This was backported to v5, so it can be used right away.

@Janpot
Copy link
Member

Janpot commented Aug 24, 2024

Usually, feature detection was enough

Just want to add that this is not always the case. For example: the signature of Grid2 has changed between v5 and v6. The ThemeProvider only accepts css vars themes in v6. These are things that you can't feature detect.

Copy link
Member

@oliviertassinari oliviertassinari left a comment

Choose a reason for hiding this comment

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

@DiegoAndai Thanks for updating the PR description. I can see how people extending Material UI could benefit this in the future, I guess we will see. It's a change with a long time horizon payoff 😄

This was backported in #43233, so it can be used right away.

#43233 was released under @mui/material@5.16.7. I don't think MUI X v7 can use it, it has "@mui/material": "^5.15.14", as a peer dependency. Increasing the minimum range is a breaking change, so possible from MUI X v8.0.0. Until then it will need to fallback to the old hacky ways, e.g.: https://github.com/mui/mui-x/pull/2108/files#diff-b4487b0ccf1b0178520560e5e9b18efbd0d4030106ae794bda6638be8cbdd526R44

Comment on lines +2 to +6
export const major = Number(process.env.MUI_MAJOR_VERSION);
export const minor = Number(process.env.MUI_MINOR_VERSION);
export const patch = Number(process.env.MUI_PATCH_VERSION);
export const preReleaseLabel = process.env.MUI_PRERELEASE_LABEL || null;
export const preReleaseNumber = Number(process.env.MUI_PRERELEASE_NUMBER) || null;
Copy link
Member

@oliviertassinari oliviertassinari Aug 26, 2024

Choose a reason for hiding this comment

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

I wonder, what if we were to only expose the version? Less bundle size:

Suggested change
export const major = Number(process.env.MUI_MAJOR_VERSION);
export const minor = Number(process.env.MUI_MINOR_VERSION);
export const patch = Number(process.env.MUI_PATCH_VERSION);
export const preReleaseLabel = process.env.MUI_PRERELEASE_LABEL || null;
export const preReleaseNumber = Number(process.env.MUI_PRERELEASE_NUMBER) || null;

e.g. React https://github.com/facebook/react/blob/main/packages/shared/ReactVersion.js

SCR-20240826-ujde

or jQuery, I'm not aware they export more.

SCR-20240826-uibg

But fair enough #43190 (comment), then just

Suggested change
export const major = Number(process.env.MUI_MAJOR_VERSION);
export const minor = Number(process.env.MUI_MINOR_VERSION);
export const patch = Number(process.env.MUI_PATCH_VERSION);
export const preReleaseLabel = process.env.MUI_PRERELEASE_LABEL || null;
export const preReleaseNumber = Number(process.env.MUI_PRERELEASE_NUMBER) || null;
export const major = Number(process.env.MUI_MAJOR_VERSION);
export const minor = Number(process.env.MUI_MINOR_VERSION);
export const patch = Number(process.env.MUI_PATCH_VERSION);

in this case?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think these five (or two) consts make much difference in bundle size. Unpacking them is useful (#43190 (comment)) and having the pre release consts keeps it consistent.

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.

I get the value of version, major, minor, patch but I don't understand preReleaseLabel, preReleaseNumber.

I do believe that we should care it, bundle size is a fight of every single PR, if done systematically, it starts to add-up, and in small instances I think it sends the right signal to the community.

https://unpkg.com/browse/@mui/material@6.0.0/version/index.js shows:

 export const version = "6.0.0";
 export const major = Number("6");
 export const minor = Number("0");
 export const patch = Number("0");
-export const preReleaseLabel = undefined || null;
-export const preReleaseNumber = Number(undefined) || null;
 export default version;

So 0.1 kB gzipped saving potential 😄? I would be curious to play https://goober.rocks/the-great-shave-off for Material UI

I love Elon's point in https://www.youtube.com/watch?v=tRsxLLghL1k&t=574s: unless you have to add back 10% of what you remove, you are not removing enough.

Copy link
Member

Choose a reason for hiding this comment

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

Bundle-size-wise I don't really mind either way. I expect these to have 0% impact on end-user bundles. These will be fully tree-shaken by minifiers if unused.
But I agree it's not clean (isNaN(Number(undefined))...). In hindsight I'd say just a prerelease: string | undefined export makes more sense to me.

Copy link
Member Author

Choose a reason for hiding this comment

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

bundle size is a fight of every single PR, if done systematically

Yes, this makes sense 👍🏼

I expect these to have 0% impact on end-user bundles. These will be fully tree-shaken by minifiers if unused.

That was my understanding as well.

So what option makes the most sense to you @oliviertassinari @Janpot?:

  1. Replace preReleaseLabel and preReleaseNumber with preRelease: string | undefined
  2. Remove preReleaseLabel and preReleaseNumber

Both could be considered breaking changes, but "not really" as those were only available in pre-release versions. If we want to be extra conservative, we can deprecate and remove them from the next major.

Copy link
Member

@oliviertassinari oliviertassinari Aug 30, 2024

Choose a reason for hiding this comment

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

These will be fully tree-shaken by minifiers if unused.

@Janpot True 🙃, though one could argue that if nobody imports them then we don't need them.

So what option makes the most sense

Happy either way 👍

Copy link
Member

@Janpot Janpot Sep 2, 2024

Choose a reason for hiding this comment

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

From an API point of view changing it prerelease: string | undefined makes the most sense to me. But I don't have a strong opinion, I only see use for the major so far.
From a semver point of view it depends on how pedantic we want to be about this being a breaking change, but I think we can still kill it (edit: preReleaseLabel and preReleaseNumber in favor for prerelease I mean).

though one could argue that if nobody imports them then we don't need them.

In the end we decided we'd just don't support css vars themes in toolpad when you're on v5 (which we initially did support), so we don't need this anymore at the moment. It keeps the door open for feature detection in the future (yagni?). I really have no strong opinion on this one.

Copy link
Member

@oliviertassinari oliviertassinari Sep 2, 2024

Choose a reason for hiding this comment

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

I can see the purpose in having this long term: third party libraries who want to implement backward support between our different majors might appreciate that we have it. We could have it in, and see if this is used long term.

I guess, I'm a bit wired like this 😁: https://youtu.be/tRsxLLghL1k?si=sRhbVRHVLRR83pMX&t=574. "if you are not adding back at least 10% of what you are removing, you know you are not removing enough", why I started this thread.

Copy link
Member

@Janpot Janpot Sep 2, 2024

Choose a reason for hiding this comment

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

Well essentially the 10% that was removed was the CssVarsProvider as a separate theme provider. On toolpad side we couldn't know whether the ThemeProvider from our peer dependency supported css vars themes or not. (In 5 it doesn't and we wanted to fall back to CssVarsProvider in that case, as we were already doing). In the end we decided to break backwards compatibilty so the problem went away altogether. but at that time version was already added to @mui/material. I'm totally fine removing it again. But we saw a clear need at some point that is not unthinkable to return in v7.

Essentially we took (the proverbial) 10% out and had to 0.1% back in. It's even better 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: material-ui Specific to @mui/material package: system Specific to @mui/system
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants