Bundle size with ES modules
See original GitHub issueWhen 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.
- I have searched the issues of this repository and believe that this is not a duplicate.
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:
{
"compilerOptions": {
"target": "es6",
"module": "es6",
...
}
}
Then I tell to Babel to not convert ES6 modules to CommonJS:
{
"presets": [
[
"env",
{
"modules": false
}
]
]
}
Inside my app I’m importing components I need:
import { AppBar, Toolbar, Typography } from "material-ui";
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 when target
is set to web
(or unspecified): ["browser", "module", "main"]
. Currently material-ui
has module
and main
:
"main": "./index.js",
"module": "./index.es.js"
So when I import some components from material-ui
using barrel it should be using “index.es.js”. If I open index.es.js
I get:
/** @license Material-UI v1.0.0-beta.45
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export { default as AppBar } from './AppBar';
export { default as Avatar } from './Avatar';
export { default as Badge } from './Badge';
...
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:
/** @license Material-UI v1.0.0-beta.45
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export { default as AppBar } from './es/AppBar';
export { default as Avatar } from './es/Avatar';
export { default as Badge } from './es/Badge';
...
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:
module.exports = {
resolve: {
extensions: ['.js', '.ts', '.tsx'],
alias: {
'material-ui': 'material-ui/es',
}
},
...
};
But I think this bundle size issue can be solved from inside this lib 👍 pointing components to es
folder on index.es.js
.
Steps to Reproduce (for bugs)
- Clone branch “issue-tree-shaking” from this repo:
git clone -b "issue-tree-shaking" https://github.com/Lemoncode/treeshaking-samples.git
cd
intotypescript/03\ barrels
- make
npm install
- make
npm run build
and seematerial-ui
size (It should be same as first posted image) - Uncomment alias in
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) - Comment out alias in
webpack.config.js
and editnode_modules/material-ui/index.es.js
pointing each export toes
folder as described above. - Make another
npm run build
. Size should be same as step 5.
Context
Bundle size is aumented when it shouldn’t.
Your Environment
Tech | Version |
---|---|
Material-UI | v1.0.0-beta.45 |
React | 16.3.2 |
browser | - |
etc | - |
Issue Analytics
- State:
- Created 5 years ago
- Reactions:15
- Comments:27 (20 by maintainers)
Top GitHub Comments
esm/
foldernext-magic
, included full build invendor/
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” :
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 :
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 :
Without “babel-plugin-import” (tree-shaking off) :
With “babel-plugin-import” (tree-shaking on) :
"babel-plugin-import will not work properly if you add the library to the webpack config vendor"
is not true for latest version of WebPack. I use this strategy (you can see the vendor/chunk distribution above) :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.
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.