Repository navigation
Material Plus 1.3.0
Changed
-
The package now says
sideEffects, and that one line is most of this release. Without the field a bundler has to assume every module in a package might do something on import, so a barrel ofexport *— which is whatsrc/index.tsis — pulls all 172 of them in and keeps whatever it cannot prove inert. Measured with esbuild and with Rollup, against React and@base-ui/reactas externals: importingMPBox, which renders a<div>with three classes on it, produced a 203-module, 62.3 kB gzipped bundle. So did importingMPButton. So did importing five components, and ten. Using one component out of ninety-four cost 86% of using all of them, which is another way of saying the library was not tree-shakeable at all and had been telling people it was. It is["**/*.css"]rather thanfalse, because a barefalseis what lets a bundler dropimport 'material-plus-ui/styles.css'on the grounds that nothing reads its exports. -
The build marks every
React.forwardRefandReact.createContextcall pure — 86 of them — andterser.config.jsonkeeps the annotations rather than consuming them.sideEffectslets a bundler drop a whole module; this is what lets it drop one export from a module that has several. Nine files hold more than one component, andMPTabon its own went from 4.48 kB to 2.19 kB. It isscripts/annotate-pure.mjsover the emitted JavaScript rather than a comment in the source, because the annotation sits in the middle of a declaration line that is already 87 columns against a limit of 100 — written insrc/, Prettier's answer to the overflow re-indents thirty component bodies, which is four thousand lines of diff and the blame history of every file it touches in exchange for a comment that says the same thing sixty-eight times. The script counts what it annotated against what the source declares and fails the build if the two disagree. -
resolveMessagesreads a registry instead of a built-in table, and the eighteen translations moved tosrc/locales/. This is the breaking change. A component that says a single word —MPButtonsays Loading — held a static import chain down to every string in every language, and a bundler cannot drop data that something imports: 32 kB of the 38 kB anMPButtonbundle weighed was Thai, Hindi and sixteen others. The tables are modules now and nothing in the library imports them, so an application hands over the ones it speaks:import { registerMPMessages } from 'material-plus-ui'; import { ko } from 'material-plus-ui/locales'; registerMPMessages(ko);
After that call
locale="ko"resolves exactly as it did before, on a provider or on a component, because it is the same table.registerMPMessages(...LOCALES)restores the old behaviour in one line and costs exactly what the old behaviour cost. A tag nobody registered falls back to English the way an unsupported tag always has, so the failure mode of forgetting the line is English words and never a broken render — andMPLocaleis a plain object, so a language this library does not ship is now a supported case rather than eighteenlabelsprops.
Added
material-plus-ui/locales, andmaterial-plus-ui/locales/<tag>for one language at a time. Named exports under their tags, hyphenated ones camel-cased (zhHans,zhHant), plusLOCALESfor all eighteen as an array. English is about a kilobyte and each language after it about six hundred bytes gzipped.registerMPMessages,MPLocaleandMPPartialMessagesare public. The first is the whole of the new API; the other two are what you write a table of your own against.aliaseson anMPLocaleis how one table answers to several tags — it is whatzh-hantuses forzh-TW,zh-HKandzh-MO, and it replaces the alias map that used to live ininternal/i18n.ts.material-plus-ui/components/<name>reaches a component without going through the barrel.import { MPButton } from 'material-plus-ui/components/button'and the root import now produce byte-identical bundles, so this is insurance rather than a recommendation: a bundler that gets tree-shaking wrong, or a build that does not readsideEffects, has a path that cannot go wrong.material-plus-ui/styles/tokens.cssand a sheet per component.dist/styles.cssis unchanged and is still the right answer for most projects, but it is 109 kB whether a page renders one component or all of them, because Tailwind generates from a file scan and not from an import graph. The same rules are now also cut along the seams the components are: the tokens once, andmaterial-plus-ui/styles/button.cssbeside it. One component is 3.8 kB gzipped against 15.6 kB for the whole sheet, five is 5.5 kB, ten is 8.0 kB. The sheets repeat each other's utilities, so the total climbs faster than the whole sheet's does and passes it at around thirty components —npm run buildmeasures where that crossing is and prints it, so the figure in the docs cannot quietly stop being true.scripts/build-split-styles.mjs, which fails the build rather than trusting itself. A component's sheet is decided from its own import graph and from whichmp-class names its files spell, both read out of the source. Two things are then checked: that every selector a scan of all the sources produces exists in some split sheet, and that no sheet reads a custom property neither it nortokens.cssdefines. Both failures would otherwise be invisible — the component renders, the page is merely wrong — and neither can survive a build now.
Notes on the measurements
Every figure above is gzip, from a real bundler over a real install, with React external. The marginal column is what this library costs on top of @base-ui/react; the total column adds Base UI in.
| Scenario | Before | After | Before, with Base UI | After, with Base UI |
|---|---|---|---|---|
MPBox |
62.3 kB | 0.5 kB | 153.2 kB | 2.3 kB |
MPButton |
62.3 kB | 3.4 kB | 153.3 kB | 6.2 kB |
| Five components | 63.1 kB | 7.4 kB | 154.1 kB | 33.6 kB |
| Ten components | 64.0 kB | 11.8 kB | 161.6 kB | 81.1 kB |
| All ninety-four | 72.4 kB | 64.3 kB | 202.1 kB | 193.6 kB |
The last row is the one that did not move much, and that is the point: nothing was removed. What changed is that the bill is now itemised.
Every release of this package is in the changelog on the docs site and in CHANGELOG.md at this tag.