-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
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
Bundle size with ES modules #11281
Comments
Good job @crsanti ! Exactly, I also believe that pointing to commonJS modules from |
Thanks for opening this issue. It's definitely not the first time we get this feedback. But for the first time, I see tree shaking working 👏 . Related issues:
@fjcalzado The whole point is to avoid bundle bloat. By publishing a second version of the Material-UI modules, we take the risk that they get loaded twice in people bundle (it's what is already happening to people with lodash, lodash/x and lodash.x: 3x). Material-UI total size is 100 kB gzipped. We definitely don't want people to end up with 200 kB gzipped in their bundle. This can happen very quickly, all you need is one third-party library that doesn't follow the convention and loads the wrong version. The list of third-party libraries based on Material-UI grows quickly.
@crsanti Regarding going forwardWe will soon flatten the import path #9532. This will allow the Babel plugins referenced in https://material-ui.com/guides/minimizing-bundle-size/#option-2 to work. We have a much smaller module duplication tolerance to projects like react-router. Both because the size of the |
I can help to solve treeshakability problem if you are interested. |
I had saw this in my project too. The way that I solving is using the default import like @oliviertassinari wrote in that comment: |
@oliviertassinari I think I get your point now. Just let me explain you to confirm. You mean I can end up with an scenario where a 3rd party library I depend on is bringing MUI CJS modules while I intentionally use ES variant modules of MUI, and thus, duplicating same components (with different module formats) in my bundle. Am I right? And by making |
@oliviertassinari About option 2, I have tried using babel-plugin-import, it works fine on a simple scenario (single bundle file), but as soon you have a vendor chunk it does not apply treeshaking. Checking on their github main page they are ware of this limitation: babel-plugin-import will not work properly if you add the library to the webpack config vendor. Should material ui minimizing bundle size guide warn about this limitation and discourage developers using this approach on a real project? I haven't tried with the two other mentioned plugins, will give a try and check if they have the same issue. |
@TrySound I have pinged you on Gitter so we can talk about it :).
@crsanti I haven't tried them out yet. Yes, please :). I have a long-term goal of using the "barrel" approach at the office but I never had the opportunity to prioritize it over the other topics. |
Note : A barrel is a single file who re-export things from other modules. There's no standard of how we can ship modern JS (see https://github.com/renchap/modern-js-in-browsers/). WebPack 4 resolve module via "package.json" fields in this order :
You probably know the "main" field, the standard entrypoint, mostly for CJS module but you can use any format. The new "browser" field aim to target browser only. For universal package, you should not use it (Material-ui is universal, they don't use "browser" field). The new "module" field aim to help tree-shaking and Node.js experimental new module system (mjs), it should point to ES module. Other fields like "jsnext:main" (Rollup) or "source" (Parcel) are not supported by WebPack. Material-ui "package.json" : "main": "./index.js",
"module": "./index.es.js" The issue say "index.es.js" re-export CJS module who use "require" to load module, instead of ES module who use "import/export". So we can't take advantage of tree-shaking, it's true. To enable tree-shaking with WebPack and "babel-plugin-import", we have to rewrite import path : {
"plugins": [
[
"babel-plugin-import",
{
"libraryName": "@material-ui/core",
"libraryDirectory": "",
"camel2DashComponentName": false
},
"tree-shaking-mui-core"
],
[
"babel-plugin-import",
{
"libraryName": "@material-ui/core/styles",
"libraryDirectory": "",
"camel2DashComponentName": false
},
"tree-shaking-mui-styles"
],
[
"babel-plugin-import",
{
"libraryName": "@material-ui/core/colors",
"libraryDirectory": "",
"camel2DashComponentName": false
},
"tree-shaking-mui-colors"
],
[
"babel-plugin-import",
{
"libraryName": "@material-ui/icons",
"libraryDirectory": "",
"camel2DashComponentName": false
},
"tree-shaking-mui-icons"
]
]
} Babel 7 doesn't support an array of object as option so you have to duplicate the plugin and name all instances. Then, you can use all Material-ui barrel, plugins take care of import path rewrite : import { TextField } from '@material-ui/core';
import { createMuiTheme } from '@material-ui/core/styles';
import { blue } from '@material-ui/core/colors';
import { DataUsage } from '@material/icons'; Without "babel-plugin-import" (tree-shaking off) : With "babel-plugin-import" (tree-shaking on) :
{
optimization: {
splitChunks: {
chunks: 'all'
},
namedModules: true
}
} If you want to use Material-ui evergreen (ES instead of CJS), remove the previous "babel-plugin-import" configuration and use WebPack aliasing. Instead of ES5, you will now get ES6 version. {
resolve: {
alias: {
'@material-ui/core': '@material-ui/core/es',
'@material-ui/icons': '@material-ui/icons/es'
}
}
} Be carefull, it come with a cost :
With ES instead of CJS, Material-ui is smaller than ReactDOM :) : I think it's worth to do not use WebPack aliasing + ES and stay with "babel-plugin-import" + CJS for the moment, unlike you really do not want to support old browser and you know what you are doing. |
I haven't upgraded to WebPack 4 yet. But I'll note that with WebPack aliasing (though I use i.e. es/ is still advantageous even when you still have to support old browsers. |
@kMeillet |
Just thought I'd chime in with a note on how the bundle size is affected as of version 1. I started with create-react-app, and analyzed bundle size before and after the
(This issue really helped using Webpack Bundle Analyzer with create-react-app) Code in src/App.jsx:
This is a size increase of 126.73 KB, 34.04 KB Gzipped, but the docs on minimizing bundle size said this difference should be ~20 KB Gzipped. I'm investigating using Material UI in an embedded environment where size really matters, and serving Gzipped is difficult at the moment, so I'm still looking for ways to make this smaller, or to trim out some fat. I see similar metrics in my own project that upgraded to Webpack 4 with default production mode, though it's worth mentioning that create-react-app is still on Webpack 3. |
@jeffvandyke It's getting better with #11492 and some other changes in v1.1.0. |
@jeffvandyke maybe do "two-stage delivery" so the embedded is just a jump-off point and data-source, but gets its libraries from elsewhere (CDN.js etc, webworkers). Or support only evergreen browsers. Or see if parcel is better at tree-shaking (hoisting) than webpack. |
@oliviertassinari From Minimizing Bundle Size
I find the last part misleading. It implies that the end user is supposed to tweak their config to enable tree shaking. However, both Regarding This way we'll have ESM exports that can be tree shaken. Most people use a bundler anyway. I find it strange that the footprint of I wish the docs were a bit clearer on that. From what I see, there's nothing that can be done, except opting in for |
@alex996 Parcel recently announced that it was supporting it. I have never tried it.
@kMeillet I start to believe that we could be leveraging the alias feature of the bundlers to improve the current solution with third-party libraries using Material-UI. "main": "./index.js",
"module": "./index.modules.js", This way, I hope we can match webpack requirements to have a working tree-shaking. Once we release this change, we will most likely have a module duplicate with third-party not releasing a module version pointing to ours. It will break many people code. But hopefully, people should be able to dodge the issue with an alias and third-party components will update to follow the standard. |
@oliviertassinari I agree that we should be publishing the I am unclear on why the |
Let's call it @rosskevin The reason is less verbose interop with cjs reexports. |
So I did some experimenting with webpack aliases and had some success to workaround the current "module" entry point: https://github.com/eps1lon/material-ui-treeshaking/blob/master/config/webpack.config.current-manual-shakeable.js The main offender is that I also tried putting .mjs files next to the .js files so that users could still do So continuing from @TrySound change The biggest issue with this solution is that 3rd party libraries use I like the idea of |
@eps1lon I'm all 💯 for the plan 👍 . Renaming |
Take a look at this guys |
@TrySound They are basically adding a package.json to every folder? That's probably the best idea. |
How does it work 🤔, is this something standard? |
Yep. Node resolve algorithm is able to resolve this. |
https://nodejs.org/api/modules.html#modules_folders_as_modules The good thing is that even if some bundler(version) does not implement this behavior then it won't cause any breakage. It's entirely optional. If your bundler looks at |
It's not treeshaking. It's splitting code by files or path imports. Treeshaking is program analyses of what should be included in bundle. With path imports users choose this not program. |
Sure but that file should also be shakeable which is only possible with the |
|
When creating bundle with webpack (4.8.1) using ES6 syntax + Babel transpilation bundle size (aplying tree shaking) is huge and sweeps along lots of components I've not imported.
Expected Behavior
Bundle size should be smaller and keep only imported components.
Current Behavior
My current project setup uses 2 steps transpilation: TypeScript --> ES6 & Babel --> ES5.
In my
tsconfig.js
I tell to TypeScript to transpile to ES6:Then I tell to Babel to not convert ES6 modules to CommonJS:
Inside my app I'm importing components I need:
I've read about how to reduce bundle size importing directly components from files instead of using barrel but since my setup involves ES6 it should apply tree shaking on components I'm not using since I'm working with ES6 modules and this library has a proper ES build (exposing all components under
es
directory)So the current bundle size is next for material-ui:
From Webpack docs "material-ui" is resolved reading from
package.json
properties in next order whentarget
is set toweb
(or unspecified):["browser", "module", "main"]
. Currentlymaterial-ui
hasmodule
andmain
:So when I import some components from
material-ui
using barrel it should be using "index.es.js". If I openindex.es.js
I get:The real issue here is that inside
index.es.js
all components are imported from compiled files instead of "es" folder.If I make a quick replace and point components to
es
folder:The bundle and build size is next:
From a consumer lib side I know I can use an alias inside webpack config to point to
es
folder like so:But I think this bundle size issue can be solved from inside this lib 👍 pointing components to
es
folder onindex.es.js
.Steps to Reproduce (for bugs)
git clone -b "issue-tree-shaking" https://github.com/Lemoncode/treeshaking-samples.git
cd
intotypescript/03\ barrels
npm install
npm run build
and seematerial-ui
size (It should be same as first posted image)webpack.config.js
and make annpm run build
(material-ui bundle should be reduced since it is pointing toes
folder and result should be like second posted image)webpack.config.js
and editnode_modules/material-ui/index.es.js
pointing each export toes
folder as described above.npm run build
. Size should be same as step 5.Context
Bundle size is aumented when it shouldn't.
Your Environment
The text was updated successfully, but these errors were encountered: