preface

Prior to ES6, there was no standard module solution for JavaScript, and the popular solution in the community was the CommonJS solution used by AMD and Node.

In order to be able to use the large number of CommonJS specification packages on NPM on the browser side, we need to do module compatibility and processing, this is the front-end packaging needs to solve a problem.

Webpack

Webpack is now the mainstream packaging tool, whether it is Umi of ant or VUE-CLI of Vue, the bottom layer is based on Webpack to carry out secondary packaging.

Webpack officially identifies it as a Module Bundler. It collects dependencies from a defined entry file, builds a dependency graph for a project, and generates and outputs one or more bundles.

The following figure is the official Webapck module processing flow

A modular system that runs in a browser

We want the browser to be able to run third-party packages and business code smoothly, whether it is CommonJS, Requirejs or ES6 modules, so we need to provide a new module system to deal with these third-party packages and business modules in the project in the same way, so that the browser can run correctly.

Webpack runs the final packaged code in the browser using a module scheme similar to CommonJS for Node.

(function(modules) { // webpackBootstrap
    // Already loaded templates
    var installedModules = {};
    
    // Module loading function
    function __webpack_require__(moduleId) {
      // ...
    }
    
    // Import file
    return __webpack_require__(__webpack_require__.s = "./index.js"); ({})"./index.js": (function(module, __webpack_exports__, __webpack_require__) {}),
    "./utils/test.js": (function(module, __webpack_exports__, __webpack_require__) {})});Copy the code

Js is the entry file of the project. All modules are loaded by __webpack_require__, and the completed modules are cached.

var installedModules = {};

// The require function
function __webpack_require__(moduleId) {
  // Check if module is in cache
  if(installedModules[moduleId]) {
    return installedModules[moduleId].exports;
  }
  // Create a new module (and put it into the cache)
  var module = installedModules[moduleId] = {
    i: moduleId, // Module ID
    l: false.// 
    exports: {}};// Execute the module function
  modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);

  // Flag the module as loaded
  module.l = true;

  // Return the exports of the module
  return module.exports;
}
Copy the code

As you can see from the __webpack_require_ code, Webpack implements a module scheme similar to CommonJS.

Plug-in system

For WebPack, it’s not enough to just aggregate and run different solution modules. For increasingly complex front-end projects, developers need to be provided with lower-level capabilities to implement different requirements for different business scenarios.

Webpack realizes a complex plug-in system based on Tapable. During its construction process, hooks of various key nodes are thrown out. Developers can process and process resources in Webpack by subscribing to these hooks, so as to fulfill the requirements of customization

With plugins and loaders, the functionality of Webpack can be continuously extended.

Existing problems

Of course, Webpack is not perfect, in the process of learning and trial, there will be large and small problems

  • There are too many concepts and configuration items, and the configuration files need to be constantly optimized according to actual project scenarios. Scaffolding such as UMI and VUE-CLI is to solve the problem that the configuration files cannot be used out of the box
  • As projects grow larger, devServer takes longer to start up and HMR speeds suffer
  • Functionality is missing. Prior to webpack5, there was no system-level file caching system

The emergence of an opportunity

Opportunity 1: HTTP /2

Due to the popularity of HTTP /2, many HTTP / 1-based optimizations have become anti-patterns, most notably merging code to reduce network requests.

In the era of HTTP /1, browsers could only initiate 5 requests in parallel, which caused the problem of blocking requests if there were too many files. Therefore, in many projects, we would package the code to generate as few vendors as possible. In HTTP /2, however, all requests are made within a TCP connection and resources are loaded in parallel, so a single large file takes longer to load than multiple small files. Therefore, in the HTTP /2 network, we need to split project resources reasonably and make full use of the parallel requests of resources to reduce the load time of resources.

Opportunity 2: ES Module

In ES6, JavaScript modules have been added.

Simply add the type=module attribute to the script tag and browsers will treat inline code or externally referenced scripts as ECMAScript modules.

The ES Module has a number of differences compared to the CommonJS Module for Node:

  • ES Module throws a reference, exports throws a value
  • Require can be referenced dynamically, whereas ES Module needs to declare all dependencies at the top of the scope. This causes Node to load modules at runtime, whereas ES Module can do all dependency analysis at compile time. And prepare for later optimizations (e.g. Tree shaking)
  • .

The following code can be run directly in a browser

// test.js
export default function hello() {
  console.log('hello world');
}

// index.html
<script type="module">
  import hello from './test.js';

  hello(); // hello world
</scirpt>
Copy the code

In browsers that support ES Module, when the ES Module is parsed, the browser will automatically send a request to load the corresponding module resources, and we do not need to deal with the module import and load.

Major modern browsers already have good compatibility with ES Module support, and this coverage will increase as users upgrade.

Vite

From the above, it seems possible to take advantage of HTTP /2 and browser support for ES Module to load code directly without the need for code packaging.

Vite and Snowpack are front-end building tools based on this idea.

What is the Vite

Vite, a development server based on browser native ES Imports. Parsing the imports in the browser, compiling and returning them on the server side on demand, bypassing the concept of packaging entirely, and making the server available on demand. Not only does it have Vue file support, it also handles hot updates, and the speed of hot updates does not slow down with the increase of modules. For production environments, the same code can be rollup. Although it is still rough, I think this direction has potential. If done well, it can completely solve the problem of changing a line of code and waiting for hot updates for half a day.

— from Yu Xi’s Weibo

Bundleless

Vite directly uses ES Module, using the browser to resolve the import dependencies in the file, for the dependencies used by the request to obtain the corresponding resource file, so as not to need to start from the import file, collect all the dependencies and package as a whole like Webpack. Finally, the packaged bundle is handed over to the browser for parsing

The figure above shows how long it takes for each build tool to package and generate the final code. Snowpack and Vite are both Bundleless solutions, so the final code generation does not require any build, just throw the ES Module code to the browser.

Speed of HMR

Vite does not slow down hot updates as the number of modules expands, because unlike Webpack, Vite does not need to build a full dependency graph, and this dependency graph can cause inaccurate hot updates when module dependencies are particularly complex

DevServer uses 304 to negotiate Cache. For modules that have been loaded, cache-control: max-age=31536000 is added to Cache to reduce network requests

Above is a comparison of snowpack and Webpack hot update times

True on-demand compilation

Vite loads dependent resources through browser parsing, so all dependent files don’t have to be processed until the files are used, truly compiling on demand

Webpack’s SPA mode is difficult to compile on demand because all the dependency analysis and processing is done from the entry file, and even modules loaded on demand are still compiled. Another possible solution is MPA, but it moves away from the single-page application development model and increases the complexity of the project during development and release.

The above two images can intuitively see the advantages of Vite in compilation. Projects only need to load on demand at the routing level, so they can be compiled on demand.

It is important to note that if the project does not load the route on demand, it will eventually compile and load all the dependencies at once

Extremely fast compilation speed

Vite uses Esbuild to compile the code. JSX and TSX generate AST through Esbuild parsing to generate the final ES Module code. Commonjs to ES Module also uses it for conversion

Esbuild is developed in Go and releases compiled lower-level machine code, which is expected to perform 10 to 100 times faster than a JavaScript wrapper

In Vite 1.0, commonJS was converted to ES6 using rollup’s @rollup/ plugin-CommonJS, and in Vite 2.0, esBuild was used to handle module conversion, greatly improving efficiency

The chart above shows how long it takes each build tool to package 10 copies of There.js, showing esBuild’s advantages

Depend on prebuild

When Vite first runs, third-party dependencies are analyzed and packaged from the entry file, and these chunks are cached to speed up page loading

Many third-party dependencies are commonJS and UMD modules that are pre-built and converted to ESM using esBuild so that these third-party packages can run directly in the browser

Converting multi-entry modules, such as LoDash, into single-file modules reduces network requests and improves page loading performance

Out of the box

In addition to first priority support for Vue, Vite also has a lot of default processing built in, such as support for JSX and TSX, and style preprocessor libraries for less, SASS, and CSS Modules

The complete scaffolding tool chain can quickly generate templates for Vue, React and other projects, providing a large number of configuration items, the author said that it is possible to replace vuE-CLI in the future

Good and friendly documentation as vue and VUe-CLI, with corresponding Chinese documentation

Simple processing flow

Core dependencies used

  • Connect & Connect middleware
  • Rollup & Rollup plugin
  • esbuild & esbuild plugin
  • Acorn, es – the module – the lexer…

Vite 1.0 used KOA as devServer, and through koA’s plug-in mechanism, different plug-ins were used to handle various file formats and extended functionality

Vite 2.0 uses Connect to replace KOA and middleware for process control. Since Vite uses Rollup for the final code build, it directly embraces the Rollup community and internally inherits the Rollup plug-in extension scheme. Extend the Vite functionality with the Rollup plugin

Deficiency in

  • Esbuild is not stable enough to be used for packaging in production environment, and Rollup is needed when the project is packaged. As a result, the codes in development environment and production environment are inconsistent, and some bugs may not be located
  • Browser support has yet to improve
  • SSR is still in the experimental stage

Some problems caused by habitual thinking

In the trial vite run demo, encountered some problems.

Because the technology stack used is React + ANTD, we built a simple demo to see the actual development experience. In previous development experience, the first thing you might do for a project with ANTD is introduce the babel-plugin-import module to implement on-demand loading, which is the same solution that was tried out in this demo.

However, after the local operation of the project, it was found that the page loading speed was sometimes very slow in the first operation without cache. After analyzing the waterfall diagram of network, the antD module tried would be loaded every time the page came in. Later analysis revealed that the babel-plugin-import plug-in was responsible.

The function of babel-plugin-import is to convert the antD introduced syntax. The effect of the conversion is as follows

import { Button } from 'antd'; ↓ ↓ ↓ ↓ ↓var _button = require('antd/lib/button');
require('antd/lib/button/style/css');
Copy the code

The plugin converts antD’s Bare import syntax to absolute path import, which invalidates Vite’s antD precompilation because it only works with Bare Import modules, and if the current route relies on a large number of ANTD modules, the page loads slowly

Another recent problem with babel-plugin-import is component library packaging. Since the component relies on ANTD, antD, React and react-DOM need to be excluded from the umD package by externals. However, react and react-DOM were not included in the analysis of the final packaging products. But the modules used by ANTD are packed in anyway. After checking, babel-plugin-import is used in the packaged Babel configuration. Since loader is compiled before build, all bare import of ANTD is compiled into absolute path import mode. The externals replacement rule becomes invalid during webpack build because externals can only replace bare import modules.

So habitual thinking can be confusing to us to a certain extent and requires a more thorough understanding of the principles and construction process in order to avoid many pitfalls.

conclusion

  • The front-end construction tool based on ES Module makes full use of the capabilities of the browser itself and essentially solves the problem of long construction time of local development projects
  • Lighter weight and higher levels of packaging than Webpack
  • Complete ecology, active community, stable core development