DEV Community

nazha
nazha

Posted on

Polyfills And Helpers - How To Save Your Bundle Size

Yoo, I'm nazha(那吒)

twitter . blog . Notion . Github . 知乎

As web programmers, we'd like to use most recent features. On the other hand, how to make our modern code work on older browsers that don't understand those feature?

There are two tools for that:

  • Polyfills
  • Transpilers

Polyfills vs transpilers

Polyfills

A polyfill is code that updates/adds new funtions or properties.

For example, string.prototype.matchAll was first introduced in ES2020 and can not work on Chrome under 67 according to can-i-use. If your users are using Chrome under 67, there's no string.prototyp.matchAll, so such code will fail.

For this case, "polyfill" will "fills in" the gap and adds missing implmentation by modifying the prototype chain of String, without replacing the source code.

Write code like this:

const matchString = 'abbbbc'.matchAll(/a/, 'd');
Enter fullscreen mode Exit fullscreen mode

Then run it through a transpiler such as Babel:

require("core-js/modules/es.string.match-all.js");

var matchString = 'abbbbc'.matchAll(/a/, 'd');
Enter fullscreen mode Exit fullscreen mode

Transpilers

A transpiler is a tool that translates your source code to another code. It parse modern syntaxs and rewrite it using older syntaxs to make them work in older browsers.

spread operatorasync/await, Class are some good examples of this case.

Do same thing with this code:

const extendsObj = { ...{ a: 'a' } };
Enter fullscreen mode Exit fullscreen mode

You will get:

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }

var extendsObj = _objectSpread({}, {
  a: 'a'
});
Enter fullscreen mode Exit fullscreen mode

Notice that there are some functions imported from @babel/runtime/helpers, which helps to transpil code. They're what I refer to as "Helpers".

Downsides

We're glad to see that our code can run on older browsers (like IE). On the other hand, polyfills and helpers may also make our application a big boat.

Take a React application (within thousands of modules in node_modules) for example, we divide it to two parts to discuss:

  • NPM React Component in node_modules directory
  • The Application itself

Helpers should never be inlined

When transpiling, helper functions will be added to every file that requires it. This duplication is sometimes unnecessary.

In babel, this is where the @babel/plugin-transform-runtime plugin comes in. And you should use it like this way:

"presets": [
  ["@babel/preset-env", {
    // Disable useBuiltIns by default
    "useBuiltIns": false,
  }]
],
"plugins": [
  [
    "@babel/plugin-transform-runtime",
    {
      "absoluteRuntime": false,
      "helpers": true, // Helpers will not be inlined
    }
  ]
]
Enter fullscreen mode Exit fullscreen mode

If you are using swc, enable non-inlined helpers in your .swcrc:

"jsc": {
  "externalHelpers": true
}
Enter fullscreen mode Exit fullscreen mode

Helpers version resolution

If a module is resolved to the same path, bundlers like webpack and rollup will bundle it once.

Let's say you have @swc/helpers@0.2.0 (the helpers be used by swc) installed in NPM package and @swc/helpers@0.3.0 in your application root.

They are considered as different modules for different resolved paths. Sometimes, this duplication is unnecessary.

You can tackle with this by adding resolutions (if you're using yarn), or pnpm.overrides (if you're using pnpm) field in your package.json file of application.

"name": "react-application",
"pnpm": {
  "overrides": {
    "@swc/helpers": "^0.3.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

There also exists a more elegant way: creating modules aliasas for helpers. For example:

// webpack.config.js
module.exports = {
  //...
  resolve: {
    alias: {
      "@swc/helpers": path.resolve(__dirname, 'node_modules'),
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Should my NPM package polyfilled

I prefer not to do this. A react application will not know whether all the NPM packages it depends are fully polyfilled or not. Even though all NPM packages are fully polyfilled, react applications must be fully polyfilled to prevent breakings.

There are two ways to make it more flexible:

  • Pre-bundling (I will cover this in another post)
  • Polyfilled all features based on environment

Polyfilled all features

If you are using babel, it's convenient to add @babel/preset-env. And configure your .babelrc file like this:

"presets": [
  ["@babel/preset-env", {
    "target": "79",
    "useBuiltIns": "entry",
    "corejs": "3.8"
  }]
]
Enter fullscreen mode Exit fullscreen mode

For swc users, configure your .swrrc file like this:

{
  "env": {
    "targets": {
      "chrome": "79"
    },
    "mode": "entry",
    "coreJs": "3.8"
  }
}
Enter fullscreen mode Exit fullscreen mode

But it is not really perfect. There exists a case that a "polyfill" is not used but polyfilled.

To reduce the bundle size, you can only load these polyfills for browsers that require them, so that the majority of the web traffic globally will not download these polyfills.

Further reading

Top comments (0)