It's a common way in most web developers that write JavaScript with ES6+ and then bundle it to ES5, so that it can run in all browsers. However, the modern browsers support ES6 natively so it's unnecessary to shipping a lot of polyfills.

I'm really excited to share you a technique that you can compile and serve two separate JavaScript bundles:

  • One bundle you are definitely already generating, which serve for legacy browsers with polyfills.
  • Another bundle has less code with no polyfills, which serve for modern browsers.

That is <script type="module"> as a way to load ES modules. You can also load code with <script nomodule> for legacy browsers.

The rest of this article explains how to implement this technique and the bundle solution for webpack.

Concepts

How does it works? let's look at a example:

<!-- For modern browsers -->
<script type="module" src="main.mjs"></script>

<!-- For legacy browsers -->
<script nomodule src="main.js"></script>

In modern browsers, script with type="module" will be loaded and executed, and script with nomodule will be ignored.

And in legacy browsers, script with type="module" will be ignored because they can't recognize this attrbute, script with nomodule has no effect, it will be treated as usual.

Note: There's something you should know about <script type="module">.

  • It isn't executed until the document has been parsed, just like <script defer>.
  • Code is running in strict mode and top-level isn't window.

Warning: Safari 10 doesn’t support the nomodule attribute, but you can solve this by inlining a JavaScript snippet in your HTML (This has been fixed in Safari 11).

Implementation

I really appreciate it that @babel/preset-env provides a convenient config for esmodules.

babel for legacy:

{
  "presets": [
    [
      "@babel/preset-env", {
        "modules": false,
        "useBuiltIns": "entry",
        "targets": {
          "browsers": [
            "> 1%",
            "last 2 versions",
            "Firefox ESR"
          ]
        }
      }
    ]
  ]
}

babel for modern:

{
  "presets": [
    [
      "@babel/preset-env", {
        "modules": false,
        "useBuiltIns": false,
        "targets": {
          "esmodules": true
        }
      }
    ]
  ]
}

It's a bit complex for webpack to bundle two different JavaScript, there are some details you should know. So I wrote a esmodules-webpack-plugin to simplify configuration and bundle thest JavaScript just run webpack once.

Is it worth a try?

I think it's definitely worth a try, the size of polyfills that bundles by babel is more than what we think, and ES Modules code has an average reduction of 50% or even more, it all depends on your source code.

Besides, larger files not only take longer to download, but also take longer to parse and execute. So reduce file size is a efficient way to improve the performance of website.

Conclusion

<script type="module"> is really a fascinating technique that helps us shipping less code to users who use modern browsers and improve our website's performance.

However, there are also some limitations. for example, most module authors don’t publish ES6+ versions of their source code, a few browsers support <script type="module">, but will still download <script nomodule>(but won't execute).

Resources

A convenient webpack plugin for esmodules that I wrote: esmodules-webpack-plugin


ansenhuang
692 声望24 粉丝

frontend boy