📜 ⬆️ ⬇️

History of ES6 Modules

Vladislav Vlasov, a software engineer at Developer Soft and a teacher at the Netology course, wrote a series of articles on EcmaScript6 especially for the blog. In the first part , examples of the considered dynamic analysis of code in EcmaScript using Iroh.js, in the second focused on the implementation of canceled Promises. This article will talk about the history of ES6 modules.



The history of the EcmaScript language extends from a simple scripting language in the browser down to the modern general-purpose language that works in various host environments. Along with the complication of the language, the need to organize a modular structure and reuse the code with its placement in libraries also appeared. The first libraries were imported by loading the corresponding JS file from the supplier’s host or CDN, and the interaction was carried out, as a rule, by exporting functions and classes with known names to the global space - the window object.

This scheme was used for a long time, and in simple cases it works quite successfully.
Difficulties begin when libraries and the relationships between them become too much.
First, the global host object is clogged, and all link libraries must import non-conflicting unique names. Secondly, there is no explicit way to ensure inter-library communication and reuse.
')
The issue of nested dependencies can be solved using dynamic <script> injection in the DOM model, and reuse can be achieved by exporting with a known name in the global host object, but this is not a universal solution and it is based solely on an implicit agreement between the authors of the libraries and using their client scripts. Partial name negotiation is solved by passing parameters to the query string in the query string specifying the namespaces for the JS library being loaded, but this is also not universal.

There remain some fundamental problems associated with asynchronous loading and interaction with the DOM model. Some libraries must be loaded earlier than others if the latter are dependent on the former. In the case of dynamic imports, this requires the correct installation of an async flag or manipulation of the readystatechanged event, depending on the vendor and browser version.

Of course, for this general case there is a solution described in the article . However, firstly, it requires careful monitoring of dependencies in all loaded libraries, and secondly, if some libraries are polyfills that need to monitor DOM state and events. In case of defer fallfack this will not work.

For a universal solution of the problems described above, several standards for the organization of library modules for JS were developed, the most famous of which are AMD (Asyncronous module definition), UMD (Universal module definition) and CommonJS . Due to the fact that the authors followed the modules of the general declaration format and the presence of a common file uploader, most of the problems were solved.

In the meantime, the Node.JS platform was actively developing, where the dependencies of the modules were solved in a completely different way - by means of a synchronous require-call, and the modules had the corresponding specific format. Then the technical committee TC-39 began developing a universal means of importing modules, which was supposed to solve all the above mentioned tasks and at the same time work equally on the server and the client and provide the synchronous and asynchronous semantics of module loading. ES6-modules have become such a tool.

Support for ES6 modules through transpile and bundle builder


With the advent of the Ecmascript 262 version 6 specification and subsequent revisions, many new syntactic constructs and native functions were added to the language. As a rule, most of them could easily be run on older versions of JS engines due to preliminary transpile inga - for syntactic constructions, and adding polyfills - for missing functions.

ES6 modules also provided synchronous non-blocking load semantics, binding bindings for exported / imported entities, modular visibility, and other aspects that are not easy to provide with the usual transpile ing.

The developers wanted to create web applications in the current Ecmascript dialect of 6, 7, 8th and later versions , and this required the convenience of executing transpile-ing and adding appropriate polyfills for applications in an automatic way so that the developed application could work in relatively old browsers no problem.

The cumulative solution to these problems was the bundle builder , customizable along with the connected transpilers and polyfills . The idea is that the application code is converted to a reference dialect, which is considered supported by all current browsers, for example, ES3 or ES5 - depending on the task. After that, all library module files and application code are combined into one large file - the so-called bundle. This file is sent to the client and no longer requires any synchronous or asynchronous imports, since all the necessary code is already in the bundle and is available by code numbers.

Known solutions implementing the appropriate approach: Browserify and Webpack , the latter being currently the de facto standard. The de facto transpiler is Babel . The proposed scheme has a large number of advantages.

First, thanks to the transpiler scheme, the original project can actually be written in any language. As a rule, this is the latest version of EcmaScript or TypeScript, but the possibilities for extending the syntax are almost endless. One of the known extensions for ES - JSX , used in the React library and its derivatives.

Secondly, by controlling the conversion of the code in the transpile-ing phase, it is possible to implement support even for such functionality as ES6 proxy or reflexive information in the code .

Among the interesting implications of using bundled code is the ability to write client code in F # or Ocaml and much more.

In addition to the obvious advantages, the bundled solution also has some obvious drawbacks.

First, the resulting bundle , even considering the possible compression, has quite a large amount and can be noticeable with mobile traffic. Secondly, the bundle includes absolutely all dependencies of the web application that will be downloaded and interpreted in the user's browser, even if it does not use the elements of the web application in which they are needed. Thirdly, the possibility of caching library dependencies disappears, since the bundle either completely relevant or requires a complete update.

Negative effects occur in the development and debugging of the application. Since bundling is almost always associated with transpile , the process of getting a new bundle , especially for a large project, can take a long time. This means that during the development and debugging process, after making the next change, the bundle needs to be rebuilt and the new version loaded onto the client. In addition, due to machine conversion of the source code, it becomes almost unreadable, which leads to difficulties in using the debugger in the browser.

Of course, most of the problems identified above have their own solutions. In order not to load the entire application code into the browser in the production mode, the codepack uses code chunk splitting technology. You can also use a dynamic version of the import that returns Promise and provides asynchronous loading of the target module.

For debugging purposes, there are also solutions. Viewing the original source code, and even navigating through it in a browser debugger, is achieved through the specification of source maps embedded in the target bundle in development mode. A partial update without a full reload bundle is solved using the Hot Module Reload, although a truly incremental update only works correctly in simple cases.

Native support for ES6 modules


The scheme with bundle dependencies was relevant for its time, but currently all modern browsers have native support for ES6 modules.


This requires a revision of the view on the assembly of modern web applications, since the bundle was a necessity due to imperfections and the lack of required functionality in browsers. After its appearance, the use of native constructs provides a much better result.

First, excessive transpile syntactic constructions and replacing it with an emulating code leads to slowing down and complication of optimizations. This applies to async and generator functions that are replaced by the regenerator runtime , and let / const lexical variables that are converted to suboptimal var declarations.

Of course, this is not directly related to the ES6 modules as such, but is usually determined by the scheme of assembly and delivery to the client application. In this sense, these are interrelated things.

Secondly, the modules are efficient in terms of performance. ES6 modules are loaded and executed deferred by default. This means that it is impossible to mistakenly add blocking modules to a web application, and accordingly there is no SPOF problem out of the box.

To maintain performance in old browsers that do not have support for ES6 modules, you can have the bundle assembled and give it to old agents. At the same time, due to the design features of importing ES6 modules, no conditional webpack configuration is required with segregation of the supplied code depending on the User-Agent of the browser string, or feature discovery.

To distinguish, the following code is sufficient:

 <html> <head> <script src="app/index.js" type="module"></script> <script src="dist/bundle.js" defer nomodule></script> </head> <!-- … --> </html> 

A browser without support for ES6 modules will simply download dist / bundle.js and will work according to the old scheme. A modern browser will take app / index.js as an entry point and will load dependent resources automatically.

You can read more about webpack settings for the above scheme, asynchronous and deferred module loading, dependency caching, inline modules and CORS policies in more details: And ECMAScript modules in browsers .

Results


EcmaScript has gone through a long history and continues to evolve to this day. Many solutions were relevant for their time and allowed to solve problems, including proactive support for functionality not yet built into client agents. Now browsers and Node.js-server releases update versions quite often, adding to them the modern functionality of EcmaScript.

As a result, solutions that allow in the past to provide emulation of support for new features in popular versions of browsers today apply to outdated agents, which, depending on the task, it makes sense to support separately or eliminate them altogether.

Preliminary resolution and linking of modules and their subsequent bundle, which was recently the main way to support ES6 modules in most browsers, now has a negative impact on them and interferes with optimizations and caching tools.

Thus, while customizing the assembly of a web application, it is advisable to provide modern agents with code in modern EcmaScript, including syntax elements and import / export of modules.

From the Editor


Courses "Netology" on the topic:

Source: https://habr.com/ru/post/351704/


All Articles