ES6 modules in the browser: are they ready or not?

Have you heard about the use of ES6 modules in a browser? Actually, these are ordinary ES6 modules. They only apply in code intended for browsers.



If anyone does not know - this is how it looks.

There is a page index.html:

<!DOCTYPE html>
<html>
  <body>
    <div id="app"></div>
  
    <script type="module" src="index.mjs"></script>
    <!--     "module" -->
  </body>
</html>

There is a file index.mjsrepresenting a module connected to the page:

import { doSomething } from './utils.mjs';

doSomething('Make me a sandwich');

This module, in turn, imports the function doSomethingfrom the module utils.mjs:

export const doSomething = message => {
  console.log('No.');
};

ES6 modules have been around for many years. And maybe you are thinking about trying them. I used them for about a month in my own project. At the same time, I came to the conclusion that ...

Suspence!

About browser support for modules


Before I talk about what I understand, I suggest taking a look at browser support for modules. I am writing this material at the beginning of 2020, so the level of support for the modules is quite impressive 90%.


Support for modules with browsers according to caniuse.com,

I am quite happy with these 90% (I do not take into account the needs of 10% of users), although you may want to be more responsible. But, even considering this, if your project is not designed for IE, or UC, or Opera Mini, this level of support means that almost 100% of your target audience will be able to work with your project without problems.

True, browser support for modules is only the beginning. Here I want to find the answer to three questions that arose at the very beginning of my journey towards modules:

  • Does the use of modules in browsers have any advantages?
  • What about the cons?
  • I am sure I had a third question, but now I canā€™t remember it.

Let's figure it out ...

What are the advantages of using modules in browsers?


This is pure JavaScript! No assembly project pipeline, no 400-line Webpack configuration file, no Babel, no plugins and presets, and no additional 45 npm modules. Only you and your code.

There is something refreshing about writing code that will be executed in the browser exactly in the form in which it was created. This may require a little more effort, but the result is very pleasant. This is how to drive a car with a manual transmission.

So. The advantages of ES6 modules are pure JavaScript, this is the lack of assembly, project configuration, and the rejection of dependencies that have become unnecessary. What else? Is there anything else?

No, nothing more.

What are the disadvantages of using modules in browsers?


ā–Comparison of modules and bundlers


When thinking about whether to use ES6 modules in a browser, you actually have to choose between using modules and bundlers like Webpack, Parcel, or Rollup.

You can (probably) use both of them, but in reality, if you plan to run the code through the bundler, there is no reason to use the design <script type="module">to load the bundle files.

So, based on the assumption that someone is considering the possibility of using ES6 modules instead of a bundler, here is what he will have to give up:

  • Minification of the finished code.
  • Cutting-edge JavaScript features.
  • Syntax that is different from JavaScript syntax (React, TypeScript, etc.).
  • Npm modules.
  • Caching

Let's consider the first three points of this list in more detail.

ā– File Size


The size of my project, which uses modules, is 34 Kb. Since I do not use the project build step, the code transmitted over the network contains very long variable names; there are a great many comments in it. It, in addition, represents a whole bunch of small files, which is not very good in terms of data compression.

If I put all this into a bundle using Parcel , then the size of what would turn out would be 18 Kb. My Casual Casio calculator reports that this is ā€œabout halfā€ the project code in which the bundler is not used. This is partly due to the fact that Parcel minifies files, but also because the materials with this approach are better compressed using gzip.

Data compression draws our attention to another problem: the way files are organized (in the sense of development convenience) is directly transferred to the way files are transferred over the network. And the way the files are organized during development does not necessarily correspond to how you want to see them in the site. For example, 150 modules (files) may make sense while working on a project. And the same materials transferred to the browser can be justified to organize in 12 bundles.

I will explain this idea. I'm not talking about the fact that using modules means that files cannot be bundled and minified (the fact that this cannot be done is an illusion). I just mean that it makes no sense to do both.

Yes, here's a funny observation. In my application, until I wrote this section, modules were used (I emphasize!). I installed Parcel to calculate the correct assembly size. And now I see no reason to return to normal modules. Well, is it not interesting!

ā–Life without transpilation


Over the years, Iā€™m very used to using the latest JavaScript syntax constructs, and to the wonderful Babel tool, which turns them into code that all browsers understand. I got used to it so much that I rarely at least thought about browser support (of course, with the exception of DOM and CSS).

When I first tried type=Ā«moduleĀ», I was going to do everything myself. My goal was fresh browsers, so I thought I could use modern JavaScript, which I'm used to.

But the reality was not so rosy. Try to answer the following questions quickly and without looking anywhere. Does Edge SupportflatMap()? Does Safari support destructive assignment (objects and arrays)? Does Chrome support commas after last function arguments? Does Firefox support exponentiation?

For example, I had to look for answers to these questions, conduct cross-browser tests. But I used all this for ages. In any large enough application, this would very likely lead to production errors.

This also means that I will not be able to use the most remarkable JavaScript innovation since the advent of the array method slice()- the so-called optional sequence operator . I used magical designs likeprop?.valuejust about a month (from the moment the Create React App tool started supporting them without any additional settings). But itā€™s already inconvenient for me to work without them.

ā– Caching burdens


Caching was for me the biggest stumbling block. In fact, the fact that the solution to the caching problem turned out to be quite interesting turned out to be the main reason why I decided to write this material.

As you are sure, you know, when materials are processed using a bundler, each of the resulting files gets a unique name - sort of index.38fd9e.js. The contents of a file with this name never (never ever) changes. Therefore, it can be cached by the browser for an unlimited period. If such a file is downloaded once, you will not have to download it again.

This is an amazing system - unless you try to find the answer to the question of how to clear the cache.

Modules are loaded using a design like<script type="module" src="index.mjs"></script>. The hash in the file name is not used. How, in this situation, is it supposed to inform the browser about where to download it index.mjs- from the cache or from the network?

It should be noted that it is almost possible to make the cache work acceptable, but it will take some effort. Here's what you need for this:

  • You must set the title of all answers cache-controlto a value no-cache. Incredibly, no-cache does not mean "do not cache." This means that the file must be cached, but the file should not be "used to satisfy a subsequent request without successful verification on the source server."
  • ETag. ā€” () , , , . , 38fd9e .
  • CDN- , . ETag . . CDN- . ( ETag) . (, , Firebase).
  • -, , Ā« Ā» . , , , , .

As a result, when a visitor re-visits the site, the browser will say: ā€œI need to download the file index.mjs. I see this file already exists in my cache, its ETag is 38fd9e. Iā€™ll request this file from the server, but Iā€™ll tell him to send it to me only if his ETag isnā€™t 38fd9e. ā€ The service worker will intercept this request, ignore ETag and return it index.mjsfrom its cache (this file got into the cache when the page was loaded last time). Then the service worker will redirect the request to the server. The server will either return a message stating that the file has not changed, or a file that will be stored in the cache.

In my opinion, all this is very troublesome.

Now, if you are interested, the service worker code:

self.addEventListener('fetch', e => {
  e.respondWith(
    (async () => {
      //       
      const cacheResponse = await caches.match(e.request);
      
      //   (),   
      // (ETag-    )
      fetch(e.request).then(fetchResponse => {
        caches
          .open(CACHE_NAME)
          .then(cache => cache.put(e.request, fetchResponse));
      });
      
      if (cacheResponse) return cacheResponse;
      
      return fetch(e.request);
    })()
  );
});

I was lazy, so I did not study and did not use the latest FetchEvent.navigationPreload property . The fact is that by that time I spent more time caching than writing an application (for your information, I spent 10 and 11 hours on these matters, respectively).

Yes, I want to note that the proposal of the ā€œimport cardā€ is aimed at solving some of the problems described above. It allows you to organize something like mapping index.json index.38fd9e.mjs. But to generate a hash, anyway, you need some kind of assembly pipeline, import cards will have to be embedded in the HTML file. This means that a bundler is needed here ... Actually, in this situation, the modules are no longer needed in the browser.

As a result, although it was interesting to understand all of this, it can be compared with the way I drove everywhere on a monocycle for a whole year. I wonā€™t do this anymore.

But probably not everyone uses bundlers?


I wrote this material on the assumption that everyone writes code in modules, using constructs import/exportor require, and then collect the code into production bundles using Webpack, Grunt, Gulp or something like that.

Are there any developers who don't use bundlers? Is there anyone who puts their JavaScript code in multiple files and sends them to production without bundling? Maybe one of these people is you? If so, I would like to know all about your life.

Summary


I strive to make all the serious decisions, such as choosing between modules and bundlers, based on the main principles of effective development. This is productivity and quality.

Unfortunately, the use of modules in browsers does not contribute to either one or the other.

If tomorrow I start work on a new project, then in my head I wonā€™t have a question about whether to run the good old create-react-app. All initial settings will take about thirty seconds, and although the initial project size of 40 Kb is a bit big, for most sites this will not play any role.

And here is a different situation. Suppose I would need to put together a little HTML / CSS and JavaScript for some kind of experiment, and such an experiment would be a project that included a little more files than a few. If, while working on this project, I did not plan to spend time setting up the system to build it, then I would probably use the modules in the browser. And then - if I did not care about the performance of this project.

I would be interested to know how Webpack and its related tools work with ES6 modules in a browser. I believe that in the future, when the proposal for ā€œimport cardsā€ receives sufficient support, bundlers will probably use them as a mechanism for abstracting unsightly hashes that are used today.

Dear readers! Have you used ES6 modules in your browser?


All Articles