question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Bundle size with ES modules

See original GitHub issue

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.

  • 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:

tree shaking not applied

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:

tree shaking applied

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)

  1. Clone branch “issue-tree-shaking” from this repo:
git clone -b "issue-tree-shaking" https://github.com/Lemoncode/treeshaking-samples.git
  1. cd into typescript/03\ barrels
  2. make npm install
  3. make npm run build and see material-ui size (It should be same as first posted image)
  4. Uncomment alias in webpack.config.js and make an npm run build (material-ui bundle should be reduced since it is pointing to es folder and result should be like second posted image)
  5. Comment out alias in webpack.config.js and edit node_modules/material-ui/index.es.js pointing each export to es folder as described above.
  6. 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:closed
  • Created 5 years ago
  • Reactions:15
  • Comments:27 (20 by maintainers)

github_iconTop GitHub Comments

30reactions
eps1loncommented, Oct 25, 2018
10reactions
kMeilletcommented, May 20, 2018

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 :

  • browser (if target is set to “web” or “webworker” or unspecified).
  • module
  • main

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) : A

With “babel-plugin-import” (tree-shaking on) : B

"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) :

{
    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 :

  • The alias works on WebPack side, not on Babel side, Jest and others tools can not use it.
  • Less browser support (evergreen version).
  • Possible Uglifyjs error (but i think it’s ok, Material-ui do a small transpilation on source to produce ES - JSX, … - and Uglifyjs3 should support ES6 syntax now ? => there’s new alternative like Terser who aim to support ES6+ syntax). If you want to get ride of that, you should transpile “node_modules” like create-react-app/next (see https://github.com/facebook/create-react-app/issues/1125) with preset-env.

With ES instead of CJS, Material-ui is smaller than ReactDOM 😃 :

C

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Decreasing Bundle Sizes with ES Modules in Angular 10
In this article, we'll review how your choice of module formatting systems affect bundling size, issues with CommonJS, how ES modules address ...
Read more >
Reduce JS Bundle Size by Dynamically Importing es6 Modules
One way to reduce JavaScript bundle size is to dynamically import es6 modules which are not required for the initial loading of the...
Read more >
How CommonJS is making your bundles larger - web.dev
In this post, we'll look into what CommonJS is and why it's making your JavaScript bundles larger than necessary.
Read more >
Bundle size with ES modules · Issue #11281 · mui/material-ui
Bundle size should be smaller and keep only imported components. Current Behavior. My current project setup uses 2 steps transpilation: ...
Read more >
ES6 modules and tree shaking - bundle size - Babylon.js Forum
240kb is great from where I sit. The bundle size for my current project is over 3mb before gzip compression. I'm using TS...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found