Skip to content
🦖A devolution gun for your bundle!
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

DEvolution 🦖


de-evolution gun, as seen in Mario Bros, to help you ship modern, and de-modernized bundles.


  • ship more modern, more compact and more fast code to 85+% of your customers

  • do not worry about transpiling node_modules - use as modern code as you can everywhere

  • don't be bound to the bundler

  • uses swc to be a blazing 🔥 fast! (actually it's disabled right now)

  • uses jest-worker to consume all your CPU cores

  • uses terser without mangling to compress the result

TWO bundles to rule the world

  • One for "esm"(modern) browsers, which you may load using type=module
  • Another for an "old"(any non modern) browser, which you may load using nomodule
Targets for "esm"
  • edge: "16+",
  • firefox: "60+",
  • chrome: "61+",
  • safari: "10.1+", (2017+)
Targets for "ie11"
  • ie: "11-"

That's is the oldest living browser, and can be used as a base line.


  1. Compile your code to the esmodules target with, or without polyfills. This is your "base layer". The modern browser baseline, with
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "esmodules": true
      useBuiltIns: "usage",
  1. Fire devolution to produce de-modernized bundles
// if use have used `useBuiltIns: usage`, thus "esm" polyfills are already inside and could be skipped
yarn devolution ./dist ./dist index.js true

// all the nessesary polyfills would be bundled
yarn devolution ./dist ./dist index.js

It will produce esm and ie11 target (nothing more is supported right now) by applying Babel one more time to the already created bundle; then prepending required polyfills.

  1. (webpack) setup public-path, somewhere close to the script start
__webpack_public_path__ = devolutionBundle + '/';

Parcel will configure public path automatically.

3.1 (webpack) symlink resources to sub-bundles Will be done automatically

  1. Ship the right script to the browser Please dont use code like this
<script type="module" src="esm/index.js"></script>
<script type="text/javascript" src="ie11/index.js" nomodule></script>

It does not for the really "old" browsers - IE11 will download both bundles, but execute only the right one. Probably that would made things even worse.

Use feature detection to pick the right bundle:

  var script = document.createElement('script');
  var prefix = (!('noModule' in check)) ? "/ie11" : "/esm"; 
  script.src = prefix + "/index.js";

This "prefix" is all you need.

  1. Done!

A few seconds to setup, a few seconds to build

Why two separate folders?

In the most articles, you might find online, ES5 and ES6 bundles are generated independently, and ES5 uses .js extension, while ES6 uses .mjs.

That requires two real bundling steps as long as "hashes" of files and "chunk names", bundles inside runtime-chunk would be different. That's why we generate two folders - to be able just to use prefix, to enable switching between bundles just using __webpack_public_path__ or parcel script location autodetection.

  • !! doesn't play well with script prefetching - you have to manually specify to prefetch esm version, not the "original" one.
  • may duplicate polyfills across the chunks. Not a big deal.


You may file devolution manually

import {devolute} from 'devolution';
await devolute(sourceDist, destDist, mainBundle, polyfillsAreBundled)

// for example

await devolute(
  'dist', // the default webpack output
  'dist', // the same directory could be used as well
  'index.js', // the main bundle (polyfills "base")
  true, // yes - some polyfills (assumed esm) are bundled already,
You can’t perform that action at this time.