ViteIt came out last year, but I really got to know it more recentlyVue ConfOn Li Kui aboutVite: Next generation Web toolTo share. Some of the things he said caught my eye. At the beginning of the sharing, he outlined the key points of the sharing:

The ESM and ESBuild are described in more detail below

And then he mentionedBundle-Based Dev Server. That’s what we’ve been usingwebpackProcessing methods:

Here’s a quote from the official website:

As we start building larger and larger applications, the amount of JavaScript code we need to process grows exponentially. It is not uncommon for large projects to contain thousands of modules. We started hitting performance bottlenecks — tools developed in JavaScript often took a long time (even minutes!). To start the development server, even with HMR, it takes a few seconds for the effect of the file changes to be reflected in the browser. Over and over again, slow feedback can have a huge impact on a developer’s productivity and happiness.

To summarize, if the application is more complex, the development process using Webpack is less slippery:

  • Webpack Dev ServerCold startup takes a long time
  • Webpack HMRThe reaction speed of hot update is slow

That’s where Vite comes in. You can think of it simply as the no-Bundler build scheme. It takes advantage of the browser’s native ESM capabilities.

But the first tool to take advantage of the browser’s native ESM capabilities was not Vite, but a tool called Snowpack. Of course, this article will not expand to compare Vite and its difference, want to know the difference between the poke Vite and X is?

At this point, I can’t help but wonder: Why did Vite exist, and what were the preconditions on which it was based?

With that in mind, and combining the sharing and Vite source code with some community articles, I found the following modules that are inseparable from Vite:

  • ES Modules
  • HTTP2
  • ESBuild

In fact, I have heard of these pieces, but I have not thoroughly understood the details. Today is a good day to dig into it.

ES Modules

In modern front-end engineering systems, we actually use ES Modules all the time:

import a from 'xxx'
import b from 'xxx'
import c from 'xxx'
Copy the code

Maybe it’s too commonplace. We’ve taken it for granted. But without a deep understanding of ES Modules, it might be a bit of a hindrance to understanding existing wheels like the Vite in this article.

ES Modules is a system of Modules that browsers support natively. Previously, CommonJS and other AMD-based module systems such as RequireJS were commonly used.

Take a look at the current browser support:

Mainstream browsers (except IE11) have all supported it. Its biggest feature is to import and export modules by using export and import on the browser side, set type=”module” in the script tag, and then use the module content.

After all, it’s just an introduction to ES Modules. All along, he’s been like a black box, and we don’t really know the internal execution mechanism. Here’s a look.

Let’s take a look at the role of the module system: the loading of traditional script tags leads to global scope contamination, and it becomes increasingly difficult to maintain a sequence of scripts in a large project. Module systems make dependencies between modules obvious by declaratively exposing and referencing modules.

When you develop with modules, you are building a dependency diagram. The wires between the different modules represent import statements in the code.

It is these import statements that tell the browser or Node what code to load.

All we need to do is specify an entry file for the dependency diagram. From this entry file, the browser or Node will follow the import statement to find other code files that depend on it.

For ES modules, there are three main steps:

  • structure. Locate, download, and parse all files into the module record.
  • instantiation. Find an area of memory to store all exported variables (but not yet filled with values). Then have export and import point to these memory blocks. This process is called linking.
  • evaluation. Run the code to fill the memory block with the actual value of the variable.

Construction stage

During the construction phase, each module goes through three things:

  • Find: Find out where to download the file containing the module (also known as module resolution)
  • Download: Get file (download from URL or load from file system)
  • Parse: Parses files into module records

Find Find

Usually we have a main file main.js to start everything off with:

<script src="main.js" type="module"></script>
Copy the code

The import statement is then used to import the content exported by other modules:

importPart of the statement is calledModule Specifier. It tells theLoaderWhere to find the imported modules.One thing to note about module identifiers: they are sometimes required in the browser andNodeAre processed differently between. Each host has its own way of interpreting the module identifier string.

Currently, you can only use the URL as the Module Specifier in the browser, that is, use the URL to load the Module.

Download Download

The problem is that the browser does not know which modules the file depends on until it has been parsed, and of course it cannot parse the file until it has been retrieved.

This causes the entire process of resolving dependencies to be blocked.Blocking the main thread like this makes modular applications too slow to use. This is aESOne of the reasons the module specification divides the algorithm into multiple stages. Separating the construction process allows browsers to download files and build their own understanding of the module diagram before performing synchronous initialization.

forESModules, you need to build the entire module diagram before doing any evaluation. This means that you can’t have variables in your module identifier because they don’t have values yet.

But sometimes it’s really useful to use variables in the module path. For example, you might need to toggle loading a module depending on how the code is running or the environment in which it is running.

In order for the ES module to support this, there is a proposal called dynamic import. With it, you can use import statements like import(${path} /foo.js).

The principle is that anything that goes throughimport()Loaded files are used as an entry point to a separate dependency graph. Dynamically imported modules open a new dependency graph and process it separately.

The Parse analytical

Parsing the file actually helps the browser understand the components of the module, and we call the table of components of the module it parsesModule RecordModule records.Module records contain the values of the current moduleAST, which module variables are referenced, and some previous specific properties and methods.

Once a Module record is created, it is recorded in the Module mapping Module Ma. If there is another request for the same URL, Loader will directly use the Module Record corresponding to the URL in Module Map.

There is one detail in the parsing that may seem trivial, but actually has a big impact. All modules are parsed as if they use “use strict” at the top. There are other nuances. For example, the keyword await remains in the top-level code of the module, and the value of this is undefined.

This different way of parsing is called the parsing target. If you parse the same file with different targets, you will get different results. So before you start parsing, you need to know what type of file you’re parsing: is it a module?

This is easy in a browser. You just set type=”module” in the script tag. This tells the browser that the file should be resolved as a module.

In Node, however, there are no HTML tags, so there are other ways to identify them. The prevailing solution in the community is to change the file suffix to.mjs to tell Node that it will be a module. But it’s not standardized, and there are a lot of compatibility issues.

At this point, at the end of the loading process, you go from a normal main entry file to a bunch of module recordsModule Record.The next step is to instantiate the module and link all instances together.

Instantiation phase

To instantiateModule RecordEngines will be usedDepth First Post-order Traversal(depth-first order) to traverse,JSThe engine will be for every oneModule RecordTo create aModule Environment RecordModule environment record, which it will manageModule RecordCorresponding variables, and for allexportAllocate memory space.

This connection mode of ES Modules is called Live Bindings (Dynamic Bindings).

The reason ES Modules use Live Bindings is that it is useful to do static analysis and avoid some problems such as circular dependencies.

CommonJS exports the export object after the copy, which means that if the export module changes the value later, the import module won’t see the change.

This is the usual conclusion: CommonJS module exports are copies of values, and ES Modules are references to values.

Evaluate stage

The last step is to fill in the values in memory. Remember we wired all the exports and imports through memory, but the memory didn’t have a value yet.

The JS engine adds values to these areas of memory by executing top-level code (code outside of functions).

So far I have roughly analyzed the black box of ES Modules.

Of course, IN this part, I refer to es-modules-a-Cartoon-deep-dive, and then I get the analysis based on my own understanding. If you want to know more about the implementation behind it, please click the link above.

ES ModulesinViteWe can open a running oneViteProject:

As can be seen from the above picture:

import { createApp } from "/node_modules/.vite/vue.js? v=2122042e";
Copy the code

With pastimport { createApp } from "vue"Instead, the introduced module path is rewritten:

Vite takes advantage of modern browsers’ native ESM support, omitting the packaging of modules. (This is also an important reason for rapid project startup and hot update in development environments)

HTTP2

Before looking at HTTP2, let’s take a look at the history of HTTP.

We know that HTTP is the most important and most used protocol in browsers and the communication language between browsers and servers. As browsers evolve, HTTP continues to evolve in order to adapt to new forms.

The first oneHTTP / 0.9The implementation is relatively simple: a request-response-based model is adopted, in which the client sends the request and the server returns the data.You can see from the figure that there is only one request line and the server does not return header information.

The rapid development of the World Wide Web created many new requirements, and HTTP/0.9 was no longer suitable for the development of emerging networks, so a new protocol was needed to support the emerging networks, which is why HTTP/1.0 was born.

The browser displays not only HTML files, but also JavaScript, CSS, images, audio, video and other different types of files. Supporting multiple types of file downloads was therefore a core requirement of HTTP/1.0.

In order for the client and server to communicate more deeply,HTTP / 1.0Request headers and response headers are introduced, both of which are based onKey-ValueForm preserved inHTTPWhen a request is sent, it carries a request header, and when the server returns data, it returns a response header first.HTTP / 1.0The specific request process can be referred to the following figure:

HTTP/1.0 Each HTTP communication needs to go through three phases: establishing TCP connections, transmitting HTTP data, and disconnecting TCP connections. At the time, because the files being communicated were small and there were few references per page, this form of transfer was not a big problem. But as the browser popular, more and more image files in a single page, sometimes a page may contain hundreds of external reference resource files, if at the time of download each file, all need experience to establish a TCP connection disconnected, data transmission, and such a step, will undoubtedly increase a lot of unnecessary spending.

To address this problem, HTTP/1.1 added the persistent connection method, which features the ability to transfer multiple HTTP requests over a SINGLE TCP connection and keep the TCP connection as long as the browser or server does not explicitly disconnect. By default, six TCP persistent connections can be established for the same domain name in the browser.

In this way, the page download speed is greatly increased to some extent.

Before we usedWebpackPackage the application code into onebundle.jsThere is an important reason why scattered module files can produce a large number of filesHTTPThe request. And a lot ofHTTPThe browser side of the request can cause concurrent requests for resources:As shown in the figure above, the requests circled in red are concurrent requests, but the subsequent requests have been suspended for some time because the number of domain connections has exceeded the limit.

In HTTP1.1 standard, each request needs to establish a separate TCP connection, through the complete communication process, very time-consuming. This problem is mainly caused by the following three reasons:

  • TCPThe slow start
  • TCPConnections compete with each other for bandwidth
  • Team head block

The first two problems are due to the mechanics of TCP itself, while queue header blocking is due to the mechanics of HTTP/1.1.

To address these known problems, the idea behind HTTP/2 is to use only one TCP long connection per domain name to transfer data, so that the entire page resource download process requires only a slow start, while avoiding the problems caused by multiple TCP connections competing for bandwidth.

Also known as multiplexing, it can realize the parallel transfer of resources.

As mentioned above, Vite uses ESM to use modules in the browser, using HTTP requests to retrieve modules. This will produce a large number of HTTP requests, but due to the emergence of HTTP/2 multiplexing mechanism, it is a good solution to the problem of transmission time.

ESBuild

esbuildOfficial introduction: It is oneJavaScript BundlerPackage and compress tools that it canJavaScriptandTypeScriptThe code is packaged and distributed to run on a web page. esbuildunderusedgolangTo prepare, in contrast to the traditionwebBuild tools have obvious advantages in packaging speed. compileTypescriptFar faster than the official onetsc.

For JSX or TS files that need to be compiled, Vite compiles the files using esBuild. Unlike Webpack, Vite compiles the files at the request of the browser and provides them to the browser. Because esBuild compiles fast enough, this compilation every time the page loads doesn’t really affect acceleration.

Vite implementation principle

Static Server + Compile + HMR Static Server + Compile + HMR

  • Use the current project directory as the root of the static file server
  • Intercept partial file requests
    • Processing codeimport node_modulesIn the module
    • To deal withvueSingle file component (SFC) compilation
  • throughWebSocketimplementationHMR

Of course, there are many articles in the community that are similar to the implementation of handwritten Vite, so I won’t repeat them here. The general principle is the same.

conclusion

The writing of this article brings me more thoughts. From a share to discover the vast ecosystem behind it and the black boxes of technology we use all the time but don’t understand deeply.

What’s more, the idea of the big guys is to stand at the commanding heights of technology, have a higher depth and breadth, and develop some wheels that are extremely useful for improving productivity.

Therefore, the article finished, the pace of learning in advance ~

reference

  • Hacks.mozilla.org/2018/03/es-…
  • Github.com/evanw/esbui…
  • Geek Time/Luo Jianfeng/Perspective HTTP protocol