Home

Awesome

<div align="center"> <h1>🦎 -> DEvolution -> 🦖</h1> <br/> <img src="./assets/devo-logo.jpg" alt="devolution" width="409" align="center"> <br/> <br/> de-evolution gun, as seen in Mario Bros, to help you ship modern, and de-modernized bundles. <br/> <br/> <a href="https://www.npmjs.com/package/devolution"> <img src="https://img.shields.io/npm/v/devolution.svg?style=flat-square" /> </a> </div>

Why?

TWO bundles to rule the world

Usage

1. Compile your code to the modern target and call it a "baseline".

  1. Prefer preset-modules
{
  "presets": [
    "@babel/preset-modules"
  ]
}  
  1. However, feel free to use preset-env with esmodules
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "esmodules": true
      },          
      "useBuiltIns": "usage"
    }]
  ]
}  

useBuiltIns are optional, and plays well with includesPolyfills option in .devolutionrc

  1. sucrase is an option sucrase is much faster than babel(and swc), however is able to produce only "modern" code. However, this is what we need. If your code is not using babel plugins, and non-yet-supported by the browsers code - feel free to use it.

2. Fire devolution to produce de-modernized bundles

the first run would create .devolutionrc.js, which could be used to tune some details

yarn devolution from to
// like
yarn devolution dist dist

It will convert all files in dist into esm and es5 targets in the same dist

By default it will handle only files in the directory, not including subdirs. You might return array of files via .devolutionrc to handle all your files

3 (Only webpack) setup public-path, somewhere close to the script start

__webpack_public_path__ = devolutionBundle + '/'; // devolutionBundle is a predefined variable

Parcel will configure public path automatically.

Symlink

Then devolution will symlink resources to "sub-bundles"

4. 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 work well for the really "old" browsers - IE11 will download both bundles, but execute only the right one. This syntax would made things even worse for the legacy browsers.

Use feature detection to pick the right bundle:

  var script = document.createElement('script');
  var prefix = (!('noModule' in script)) ? "/ie11" : "/esm"; 
  script.src = prefix + "/index.js"; // or main? you better know
  document.head.appendChild(script);

This "prefix" is all you need.

4, again. SSR this time

However, it's much better to use Server Side logic to pick the right bundle - you might control which bundle should be shipped in which case.

But default - use the same browsers as they are listed in .devolutionrc.js targets for esm, however - you might "raise the bar", shipping modern code only to Chrome 80+, or introduce more than two bundles - the "language" in the top ones could be the same, but polyfills set would be different.

import UA from 'browserslist-useragent'

export const isModernBrowser = (userAgent) => {
  return UA.matchesUA(userAgent, {
    _allowHigherVersions: true,
    browsers: [
      "Chrome >= 61",
      "Safari >= 10.1",
      "iOS >= 11.3",
      "Firefox >= 60",
      "Edge >= 16"
    ]
  })
}

function renderApp(req, res) {
  const userAgent = req.headers['user-agent'];

  const bundleMode = isModernBrowser(userAgent) ? 'esm' : 'es5';
  // send the right scripts
}

See Optimising JS Delivery for details

5. Done!

A few minutes to setup, a few seconds to build

Tuning

See .devolutionrc.js, it contains all information you might look for

FAQ

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.

Drawbacks
(default) Targets for "esm"
(default) Targets for "ie5"

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

SWC

SWC is much faster than babel, however not as stable and might produce broken code. Controlled by useSWC, disabled by default

Terser

There are two options, which control minification - useTerser and useTerserForBaseline. You have to enable useTerser if you enable useSWC as long as it produces non minified code.

API

You may file devolution manually

import {devolute} from 'devolution';

devolute(sourceDist, destDist, options)

// for example

devolute(
  'dist', // the default webpack output
  'dist', // the same directory could be used as well
  require('.devolutionrc')     
)

License

MIT