In the development, modularization can prevent variables and methods from being polluted, only need to pay attention to a part of the logical implementation, effectively reduce the coupling with the global, but also facilitate the later maintenance and expansion

Of course, anyone who knows the development history of front-end modularity should have heard of IIFE, AMD, CommonJS, etc. They are all specifications that can achieve modularity. It was not until ES2015 that modularity was officially included in its standard. Before talking about today’s theme, we will briefly explain the realization and difference of the above several modular ways, which will be helpful for the webpack modular optimization.

IIFE

Before the various modularity specifications came out, ES5 did not support modularity development, but at that time, some of the best people thought of using IIFE to implement modularity in order to better avoid the side effects and encapsulation of functions.

As you can see, on the basis of the original ES5, you can encapsulate part of the logic module, which is a simple closure behavior, so that internal variables are not affected by the external environment.

However, the defects are also obviously exposed, it can not realize the dependency between modules, and the code is allocated to the main process, which brings difficulties to the later maintenance and modification.

CommonJS

The CommonJS specification implements synchronous loading and is often used on the server side. Its ultimate goal is to provide a modular standard library like Python, Ruby and Java.

For this reason, NodeJS officially marks the birth of Javascript modular programming. On the server side, modules need to interact with the operating system or applications, and NodeJS’s module system is written according to the CommonJS specification.

The specification states that you need to export external variables or interfaces through exports or module.exports, and import the output of other modules into the current module scope through the require method. Serve chestnuts straight:

Can CommonJS be used for clients?

The answer is yes. The CommonJS specification cannot be used by the client due to the absence of four node. js environment variables: module, exports, require, global. To do this, third-party tools or libraries (such as Require1k, Tiny-Browser-require, etc.) are required to enable clients to implement the CommonJS specification.

AMD

Given ES5 inside through IIFE modular doesn’t really mean to (similar to Java, Python, etc.) on the modular, so some recognize proposed is applicable to the client’s AMD specification, it can be asynchronous module are introduced, at the same time the module is a good way to certain logic functions encapsulated in a file so that the main process introduced when necessary. The RequireJS library is a good implementation of the AMD specification.

The RequireJS API exposes two global methods, require and DEFINE, which can be passed into the first parameter of the require and DEFINE global methods if the main process or module needs to rely on other modules. At the same time, we can see that the AMD specification implements the asynchronous module loading mode (multiple modules will be loaded in parallel when introduced, effectively speeding up the execution efficiency and non-blocking page loading). For each module only need to do what the module should do, the coupling degree between the module and the main process is effectively reduced.

CMD

The CMD specification, also known as Common Module Definition, implements asynchronous loading of modules. SeaJS is a good implementation of this specification. It has the following features:

  • In use: andAMD specificationSimilarly, both are usedrequireanddefineTwo global methods, but userequireMethod is synchronized to the execution of module code, which is the same asCommonJS specificationVery similar;
  • Implementation:CMD specificationThe core is pre-loading, delayed execution, whileAMD specificationThe core is pre-load, pre-execute, of courseCommonJS specificationThe core is lazy loading, lazy execution;

It should be noted that the reason why the CMD specification uses require is that the loaded module is stored in memory during the pre-loading process, so that the client executes the saved module in memory synchronously when the main process imports the module on demand. (If you’re interested, check out this article on how SeaJS works.)

CMD standard, in my opinion, lazy enforcement mechanism can effectively improve the page interactive performance, because before the page interactive process does not need to perform other temporarily useless to module (of course this is only my personal point of view, if there are children’s shoes have different opinions, can write up to discuss on the comments section to learn, for performance comparison, Kids can also check out SeaJS github for an interesting comparison between AMD and CMD). This mechanism is also closely related to the webPack modularity optimization I will mention below.

Now let’s go straight to the SeaJS CMD specification (if you’re interested, take a look at the SeaJS API) :

UMD

The UMD specification can be seen as a solution for cross-platform module loading on the front and back ends, supporting both the AMD specification and the CommonJS specification. In plain English, work on an implementation that can load modules compatible with the front and back ends.

Without further ado, those interested can check out UMD’s official introduction and chestnut. Here is also a simple chestnut to appreciate the UMD specification written:

It can be seen that the UMD specification is implemented by determining whether AMD specification is supported first, and then determining whether CommonJS specification is supported. When neither of the two specifications is supported, the module is directly defined on the global object

ES6 Module

Due to the lack of modular loading concept in ES5, modular loading was officially included as a standard in ES6

In ES6, modules are output interfaces at compile time, while the various Module loading specifications mentioned above are output interfaces at run time. As you can see, ES6 Module has been optimized for performance to some extent. Interested children’s shoes can see Ruan Yifeng’s introduction to ES6 Module. Here’s an example:

At present, the client basically uses ES6 Module Module loading mode to load modules, while the Node server is gradually approaching this Module loading mode, but it still uses CommonJS specification in most cases.

I believe that you should have a certain understanding of modularity after seeing the various implementations of loading modules above. Ok, so let’s move on to today’s topic. How can you optimize modularity in your Application in Webpack?

Problems encountered

In everyday modular development, a page is made up of many components, and these components need to be introduced as needed. There is no doubt that we are now using Vue as a reference, and the project structure (only the main directories and files are shown below, but other directories and files are not shown) is as follows:

|- src
|--- components
|------ message.vue      // Details box component
|------ main.vue         // Home page components required
|------ goods.vue        // Product page component
|--- router
|------ index.js         // Routing file
|--- App.vue             // Enter the vue file
Copy the code

Normally, we would write a page like this:

App.vue

Index.js routing file

Main. Vue home page component

Goods. vue product page component

Message. vue product information component

When you type localhost:8080/#/goods, you will display the goods.vue product component. When you click the goods button, you will display the message.

At this point, I would like to ask, is there a more optimized solution to the simple SPA chestnut above? What if I want to speed up the loading of the home page to make it a better user experience?

For the above questions, we will often encounter, you can also express their views in the comments section. Here, without further ado, if it were me, the first thing that came to my mind was Webpack, and this is the core of what I want to talk about today: how to better optimize the modularity of complex programs with Webpack.

Dynamic Import

The import() syntax of the Stage 3 ECMAScript proposal is familiar to many people. What does it actually mean? The official line is:

ECMAScript modules are completely static, import() enables dynamic loading of ECMAScript modules.

To put it simply, currently modules are statically loaded, but import() allows us to load modules on demand. The goods page loads only the Goods and Message components. The goods page loads only the Goods and Message components. Then take a closer look at the above chestnut is really done on demand loading?

The answer is no, as I mentioned above ES6 modules export interfaces at compile time, so when we pack them with Webpack, all the required modules are bundled into a SINGLE JS file. Therefore, in the loading process of the home page, it is necessary to load a complete JS file to allow users to experience the effect. Of course, as we all know, this JS file also has some module logic that we do not need to use temporarily, and this is exactly what we will deal with next.

The advent of the import() syntax, combined with Webpack 4 (or, optionally, WebPack 3), solves this problem elegantly. And Babel’s transformation. Here is the processing implementation:

Webpack.config.js partial configuration

Then modify the routing file as follows:

As you can see, when we visit localhost:8080/#/ again, the loading js file is smaller than before, and most importantly there is no code for the Goods component in this file. When localhost:8080/#/goods is accessed, the 0.js file containing the goods component code is loaded asynchronously from the server. Thus, it can not only reduce the size of the main process JS file, speed up the page loading experience, but also asynchronously download the necessary module files on demand.

Ok, so if I want to optimize the Goods Message detail component because it only loads when the button is clicked, can we also make it lazy and make the page under localhost:8080/#/ Goods load faster?

Component syntax (Vue) component syntax (Vue) Component syntax (Vue)

Goods. vue product page component

When we go to localhost:8080/#/goods, we will find that the js file is also less than the previous Message detail component code, which speeds up the loading experience. In addition, after clicking the button, the 1.js file will be dynamically introduced, which is also asynchronously imported from the server to load the Message detail component.

When you close the Message details component popover and click the button again, you need to render again. And the answer is that there will be some loss, so if you want to optimize it, what can you do? Simply wrap our < Component > with the

component exposed by the Vue API. This will effectively store the component in memory, which can be read directly from memory the next time it is accessed.

At this point, perhaps some children will ask questions, the above 0.js file and 1.js file is what the heck? Be patient and look down, maybe there is a surprise you want haha 🙊

Magic Comments

The reason for the zeros and 1s above is that when the module name chunkName is not specified for dynamically imported import(), Webpack defaults to naming subsequent dynamically imported files in order from 0 to n.

However, for these numbers 0 or N, we have no way of knowing which module the js file dynamically imported belongs to, so Webpack also provides Magic Comments for us to customize the module name.

The following uses the above routing file as a dynamic import chestnut. It is very simple to use:

When you access goods from localhost:8080/#/, you’ll find that instead of loading 0.js, you’ll find goods.js, so it’s easy to know which modules are being introduced dynamically. Of course, for those interested, you can also think about specifying the chunkName when the Message details component is dynamically imported.

So far, the scheme mentioned above can effectively optimize our current stage of modular development, of course, children can also try.

Extension

In fact, in my opinion, we could go a little further. Take the above Goods commodity components, when click the button, if the load module is larger, the user will have to wait for loading the whole message. The js file to display the pop-up (of course the situation there is not much more special, because the packaging of js files are generally deposit to CDN, and request to nearby principle of loading, This also avoids the problem, but we can discuss how it can be avoided). Therefore, do we have a plan to remove the delay effectively? I won’t keep you guessing, in Webpack 4.6 there is explicit support for preloading and fetching, which solve the problem we had to solve.

Preload And PreFetch

Preload is a Preload, PreFetch is a prediction of the module that will be loaded, both of which are attributes under the Link tag. Those interested in children’s shoes can take a look at the priorities of both implementations in the client

In simple terms, Preload has a Height priority and PreFetch has a Low priority. There is a difference between the two:

  • preload: is mainly used for preloading the current page, and the main filebundle.jsParallel downloads, with priority access, can be used to preload certain necessary modules
  • prefetch: is mainly used for next operations or pages. It is downloaded during the idle time of the browser and has the lowest priority

Also, it should be noted that Preload and Prefetch are currently not that friendly to existing browsers. Look at two compatibility points: Preload compatibility and Prefetch compatibility

While compatibility is not that friendly, both Preload and Prefetch are declarative, so if not supported, it doesn’t affect any functionality of existing pages

Prefetch can be used to solve the delay problem we encountered above. How to write it? Here, we also need to use the Webpack preload-webpack-plugin, the configuration is written as follows:

Webpack.config.js partial configuration

At this point, on Webpack 4.6+, modify the product page component as follows:

Goods. vue product page component

As you can see, you just need to add webpackPrefetch: true and preload-webpack-plugin to Prefetch dynamically imported modules.

When we visit localhost:8080/#/goods again, we will find that after loading the js file of the main process, then during idle time of the browser, the message.js file will be automatically loaded and we can see its priority is Low. Also, we can see that in the tag, the message.js file is loaded using rel=”prefetch” via the tag. Clicking the button at this point will load the Message component without waiting for delay.

As for the above operation renderings, I will not screen shots, interested children can personally try to verify it, so as to have a more profound impression. If there is inconvenience, here I say sorry ha 🙈

For Webpack modular load optimization, maybe there will be some better and better solutions, also welcome 👏 gods to share in the comments section to learn together. And the above optimization scheme, in fact, can be used in the daily modular development, but also please combine their own needs to optimize the application scenario analysis, once used improperly, optimization may be a consumption of performance experience.