Follow me on my blog shymean.com

A vite tool was mentioned in the Vue 3.0 beta broadcast two days ago. Its description is: for the Vue single page component of the unpackaged development server, you can directly run the Vue file request in the browser. I am interested in its principle, so I experience and write this article, mainly including vite implementation principle analysis and some thoughts.

Preliminary knowledge

Vite relies heavily on module SciPRt features, so do your homework: JavaScript modules -mdn.

Module SciPRt allows native support modules to run directly in the browser

<script type="module">
    // index.js can be exported as a module, or it can continue to use import to load other dependencies
    import App from './index.js'
</script>
Copy the code

When an import dependency is encountered, HTTP requests are made directly to the corresponding module file.

The development environment

The version used for this article is [email protected], with the github project address ~ currently the project seems to be updated daily

First clone the warehouse

git clone https://github.com/vuejs/vite
cd vite && yarn
Copy the code

After the environment is installed, create examples under the project, add index.html and comp. vue files, and use the examples in readme. md directly

The first is inidex. HTML

<div id="app"></div>
<script type="module">
import { createApp } from 'vue'
import Comp from './Comp.vue'

createApp(Comp).mount('#app')
</script>
Copy the code

Then a ` Comp. Vue ` `


<template>
  <button @click="count++">{{ count }} times</button>
</template>

<script>
export default {
  data: () => ({ count: 0 })
}
</script>

<style scoped>
button { color: red }
</style>
Copy the code

Then run it under the exmples directory

../bin/vite.js 
Copy the code

You can open the preview in the browser http://localhost:3000 and support hot update

If you need to debug the source code, start NPM run dev. This will enable Tsc-w –p to listen for changes in the SRC directory and output them to the dist directory in real time

Entrance to the file

At present this project iteration is very frequent (and historyFallbackMiddleware yesterday this middleware today apparently did not have), but about the implementation of the ideas should be determined the basic, so make sure the source reading goals: Learn how to run vue files directly without using packaging tools such as Webpack. Based on this purpose, the main idea is to understand the implementation, clarify the overall structure, do not stick to the specific details.

Start with the entry bin/ viet.js

const server = require('.. /dist/server').createServer(argv)
Copy the code

You can see the createServer method, which locates directly to SRC /server/client.tx. Vite uses Koa to build the server and registers the functionality primarily through middleware in createServer

// SRC /index.ts // Plugin[] = [modulesPlugin, // Handle the entry HTML file script tag and each vue file module depends on vuePlugin, // vue single page component parsing, Template, script, and style can be interpreted as a simple version of vue-loader hmrPlugin, // Using websocket to implement file hot update servePlugin // KOA configuration plug-in, Export function createServer({root = process.cwd(), middlewares: userMiddlewares = []}: ServerConfig = {}): Server {const app = new Koa() const Server = http.createserver (app.callback()) // userMiddlewares; [...userMiddlewares, ...middlewares].forEach((m) => m({ root, app, server }) ) return server }Copy the code

Vite registers koA middleware through the following middleware form,

exportConst modulesPlugin: Plugin = ({root, app}) => {// Each Plugin is actually registered with koA middleware app. Use (async (CTX, next) => {})}Copy the code

It looks similar to Vue2’s source code structure, adding functionality step by step via decorator. For now, we just need to clarify the function of these four plug-ins.

// vue2 source code structure
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
Copy the code

moduleResolverMiddleware

The purpose of this middleware is to compile file contents such as index.html and SFC, and handle related dependencies.

For example, the HTML file script tag content above is compiled using methods like rewriteImports

import { createApp } from '/__modules/vue'Import {createApp} from 'vue'
import Comp from './Comp.vue'

createApp(Comp).mount('#app')
Copy the code

This way, when the browser parses and runs the Module type script tag, it requests the corresponding module file, where

  • /__modules/vueIs a static resource directory file for the KOA server,
  • ./Comp.vueIs a single page component file that we wrote
  • It also looks like it will be providedsourcemapThe function such as

For import files, dependencies under the script tag are required. For single-page components, tmplate, script, and style tags are also handled in vue-loader; In Vite, these dependencies are loaded as CSS and JS file requests.

The single-page component mainly contains the template, script, and style tags, where the export of the code within the script tag is compiled into

// Load the hot update module client, which will be discussed later
import "/__hmrClient"

let __script; export default (__script = {
  data: (a)= > ({ count: 0})})// 根据type进行区分,样式文件type=style
import "/Comp.vue? type=style&index=0"
// Preserve CSS scopeID
__script.__scopeId = "data-v-92a6df80"
// render function file type=template
import { render as __render } from "/Comp.vue? type=template"
__script.render = __render
__script.__hmrId = "/Comp.vue"
Copy the code

The style and template tags will be rewritten as/comp.vue? In the form of type= XXX, the HTTP request is re-sent. This way of differentiating and loading the contents of each module of SFC file in the form of query parameter is the same as that of vue-loader through the resourceQuery configuration of Webpack. If you understand the vuE-loader operating principle of the students to see this is estimated to have a double take, I wrote a previous vue-loader source code analysis of CSS-Scoped implementation, which also introduced the general principle of vue-Loader.

Going back to Vite, we now know what moduleResolverMiddleware does. It basically overrides module paths, differentiating SFC file dependencies with the Query parameter, and making it easier for browsers to load actual modules from urls. Open the browser console to view the specific file request

VuePlugin

As mentioned earlier, the template and style of single-page components are treated as separate import paths, distinguished by query.type. How does the server return the correct resource content when it receives the corresponding URL request? The answer lies in the second plug-in, VuePlugin.

Single-page file requests have a feature that ends with *. Vue as the request path. When the server receives an HTTP request with this feature, it mainly processes it

  • According to thectx.pathDetermine the specific VUE file for the request
  • useparseSFCParse the file and getdescriptor, adescriptorContains basic information about this component, includingtemplate,scriptandstylesAnd so onComp.vueThe documents were obtained after processingdescriptor
{
 filename: '/Users/Txm/source_code/vite/examples/Comp.vue'.template: {
   type: 'template'.content: '\n \n'.loc: {
     source: '\n \n'.start: [Object].end: [Object]},attrs: {},
   map: {
     version: 3.sources: [Array].names: [].mappings: '; AACA'.file: '/Users/Txm/source_code/vite/examples/Comp.vue'.sourceRoot: ' '.sourcesContent: [Array]}},script: {
   type: 'script'.content: '\nexport default {\n data: () => ({ count: 0 })\n}\n'.loc: {
     source: '\nexport default {\n data: () => ({ count: 0 })\n}\n'.start: [Object].end: [Object]},attrs: {},
   map: {
     version: 3.sources: [Array].names: [].mappings: '; AAKA; AACA; AACA'.file: '/Users/Txm/source_code/vite/examples/Comp.vue'.sourceRoot: ' '.sourcesContent: [Array]}},styles: [{type: 'style'.content: '\nbutton { color: red }\n'.loc: [Object].attrs: [Object].scoped: true.map: [Object]}],customBlocks: []}Copy the code
  • Then according to thedescriptorandctx.query.typeSelect a method of the corresponding type and return after processingctx.body
    • If type is empty, processing is performedscriptTag, usingcompileSFCMainMethod returnsjscontent
    • The type oftemplateTime representation processingtemplateTag, usingcompileSFCTemplateMethod returnsrendermethods
    • The type ofstyleS indicates processingstyleTag, usingcompileSFCStyleMethod returnscssThe file content

Let’s go back to the process

  • Import file dependencyComp.vueThe script code
  • Com.vueRely ontempplateCompile the render method, dependentstyleLabel compiled CSS code in these two filesscriptTo declare dependencies in compiled code
// comp.vue returns a file that looks similar to the script tag in the entry file
import { updateStyle } from "/__hmrClient"

const __script = {
  data: (a)= > ({ count: 0})}// Style tag content parsed CSS code
updateStyle("92a6df80-0"."/Comp.vue? type=style&index=0")
__script.__scopeId = "data-v-92a6df80"
// Render with temlpate tag
import { render as __render } from "/Comp.vue? type=template"
__script.render = __render
__script.__hmrId = "/Comp.vue"
export default __script
Copy the code

After the content of each label is parsed, it is cached by LRUCache for reuse

export const vueCache = new LRUCache<string, CacheEntry>({
  max: 65535
})
Copy the code

This gives you a general idea of how vite runs vue files directly from KOA. The idea is similar to vue-Loader, which handles file dependencies with Module Script, and then handles resource files parsed by a single page file by concatenating different Query.types. The response is rendered to the browser.

hmrPlugin

As mentioned earlier, Vite also supports hot file updates, so how do you do that without Using WebPack? The answer is to implement one of your own

Hot update is mainly realized through webSocket, including WS server and WS client. HmrPlugin is mainly responsible for WS server. Ws client is implemented in SRC /client.ts. And associate the server with the client by importing “/__hmrClient” when handling module dependencies in the first step.

Currently, the following message types are defined

  • reload
  • rerender
  • style-update
  • style-remove
  • full-reload

When the file changes, the server pushes different messages according to the changed type in the handleVueSFCReload method. When the client receives the corresponding message, it processes it with vue.HMRRuntime or reloads new resources.

Hot update here is still a lot of TODO, feel is a good case to learn the principle of hot update, first code later come back to read.

The community has a lot of analysis on the principle of hot update, so you can read it

  • Webpack hot update
  • Easy to understand the webpack hot update principle

servePlugin

This plug-in is mainly used to implement some configurations of KOA requests and responses.

As a result of the above analysis, each dependency is analyzed in turn, starting with the entry file, on each request

  • For normal files, find server static resources directly throughservePluginIn the configurationkoa-staticimplementation
  • For vUE files, HTTP requests are reconcatenated, for each request, includingpathandqueryWhere PATH is used to determine component files,query.typeUsed to determine what method to use to return the response content

In this step, it is clear that it is very frequent for each VUE file to send multiple HTTP requests, and then perform lookup and parsing operations. If caching is not configured, the performance burden of the server is very high. Koa-conditional-get and KOA-eTAG should solve this problem. But that doesn’t seem to be happening yet.

summary

At this point, the basic reading of vite source code is completed, because the main purpose of local reading source code is to understand the implementation principle and general function of the whole tool, so there is no in-depth understanding of the implementation details of each function, Some of the more important methods, including rewriteImports, compileSFCMain, compileSFCTemplate, compileSFCStyle, updateStyle, etc., don’t show the code implementation, but the main lesson is that you know

  • Module Script and Query. type are combined to implement a mechanism similar to vue-loader to run vue files directly on the server
  • Use websocket to implement hot update manually. There is no peruse here due to time constraints

When I first saw the introduction of Vite, I thought it would be a very interesting tool, although it hasn’t been officially released yet, so I couldn’t resist checking it out. The main functions of sensation are

  • Use Vite to develop demos quickly without having to install a bunch of dependencies
  • Similar to thejsfiddleOnline preview of vUE files to facilitate the development, testing and distribution of single-file components

Vite currently lacks important features such as packaging and should not be a replacement for tools such as WebPack. I don’t think Vite is meant to replace existing development tools, so I don’t think packaging will be added in the future