Search code examples
reactjsbabeljswebpack-4polyfills

How can I require a few polyfills without requesting the whole core-js package?


So my first time asking a question here so be nice.

I'm building a webpage with react.js and a webpack 4 config and I'm trying to load polyfills dynamically through code-splitting so I can support older browsers like ie11.

I've been looking at this tutorial for bundle splitting and code splitting. (scroll half way down to the headline "polyfill" to get to conditionally load polyfills)

Here's my setup:

webpack.config.js:

const HtmlWebPackPlugin = require('html-webpack-plugin');
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');

const htmlPlugin = new HtmlWebPackPlugin({
  template: './src/index.html',
  filename: './index.html'
});

const cleanWebpack = new CleanWebpackPlugin(['dist']);


module.exports = {
  entry: ['core-js/modules/es6.promise', 'core-js/modules/es6.array.iterator', path.resolve(__dirname, './src/index.js')],
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].js',
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/'
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
  },
  module: {
    rules: [{
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.html$/,
        use: [{
          loader: 'html-loader',
          options: {
            minimize: true
          }
        }]
      },
      {
        test: /\.(css|scss)/,
        use: [{
            loader: 'style-loader'
          },
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 1,
              localIdentName: '[name]_[local]_[hash:base64]',
              sourceMap: true,
              minimize: true
            }
          },
          {
            loader: 'sass-loader'
          }
        ]
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: [{
          loader: 'file-loader?name=assets/[name].[hash].[ext]'
        }]
      },
      {
        test: /\.(png|jpg|svg)$/,
        use: [{
          loader: 'file-loader',
          options: {
            name: '[path][name].[ext]'
          }
        }]
      }
    ]
  },
  devServer: {
    historyApiFallback: true
  },
  plugins: [cleanWebpack, htmlPlugin]
};

package.json:

{
  "name": "new_portfolio",
  "version": "1.0.0",
  "description": "Daniel Dahlmans portfolio site 2019",
  "browserslist": [
    "> 1% in SE",
    " not op_mini all"
  ],
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server --mode development --open",
    "build": "webpack --mode production"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.2.2",
    "@babel/plugin-proposal-class-properties": "^7.2.3",
    "@babel/plugin-proposal-object-rest-spread": "^7.2.0",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/preset-env": "^7.2.3",
    "@babel/preset-react": "^7.0.0",
    "babel-eslint": "^10.0.1",
    "babel-loader": "^8.0.5",
    "clean-webpack-plugin": "^1.0.1",
    "core-js": "^2.6.3",
    "css-loader": "^0.28.11",
    "eslint": "^5.12.0",
    "eslint-plugin-react": "^7.12.4",
    "file-loader": "^1.1.11",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.11.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.21.0",
    "webpack": "^4.28.4",
    "webpack-cli": "^3.2.1",
    "webpack-dev-server": "^3.1.14"
  },
  "dependencies": {
    "prop-types": "^15.6.2",
    "react": "^16.7.0",
    "react-dom": "^16.7.0",
    "react-router-dom": "^4.3.1",
    "typeface-work-sans": "0.0.54"
  }
}

.babelrc:

{
  "presets": [
    ["@babel/preset-env",
      {
        "debug": true
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-proposal-object-rest-spread",
    "@babel/plugin-proposal-class-properties"
  ]
}

I've made a seperate file called 'polyfills.js' where I'm requiring the polyfills I need:

polyfills.js:

require('core-js/es6/array');
require('core-js/es6/string');
require('core-js/es6/map');
require('core-js/es6/object');
require('core-js/es6/symbol');

I'm then importing these conditionally if they're not supported.

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import {
  BrowserRouter as Router
} from 'react-router-dom';
import App from './App';
import 'typeface-work-sans';


const render = () => {
    ReactDOM.render( < Router > < App / > < /Router>, document.getElementById('index'));
    };

    if (
      'Symbol' in window &&
      'Map' in window &&
      'startsWith' in String.prototype &&
      'endsWith' in String.prototype &&
      'includes' in String.prototype &&
      'includes' in Array.prototype &&
      'assign' in Object &&
      'entries' in Object &&
      'keys' in Object
    ) {
      render();
    } else {
      import ( /* webpackChunkName: "polyfills" */ './polyfills').then(() => {
        console.log('I am imported async');
        render();
      });
    }

All works well, so if I'm running my localhost in ie11 my polyfills.js is requested and if I'm in chrome it's not requested.

But to my surprise I'm still seeing that the whole core-js package is still being requested by the browser even if I'm just requiring a few polyfills... and this happens in all of the browsers.

How can I require a few polyfills without requesting the whole core-js package?

image - from terminal (when running devserver)

image - from networks tab in chrome

image - from elements tab in chrome

The whole reason I'm doing this is to reduce my main.js bundle size.


Solution

  • It may seem like that requiring individual polyfill modules will result in a reduced bundle size, but in fact it does not. In your package.json the entire core-js package is installed and since core-js doesn't use ES2015 modules Webpack can't optimize the output.

    However, it's possible to create a version of core-js that only includes the modules that you need. Use core-js's custom build tool to generate a smaller bundle and add it to your code instead of entire core-js.