What is the vite

Vite – a Web development tool developed by VUE author Yu Yuxi, which has the following features:

  1. Quick cold start
  2. Instant module hot update
  3. True on-demand compilation

From the author’s comments on Weibo:

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.

The main features of Vite are browser native ES Module (developer.mozilla.org/en-US/docs/…). To develop, skip the packaging step, because what resources are needed can be imported directly into the browser.

Developing web applications based on the browser’s ES Module is nothing new, and Snowpack is based on it, but it hasn’t caught on in the community so far, and Vite may just keep it going for a while.

Interestingly, Vite has taken webpack’s life out of it, so the webPack developers are calling it out…

(Author’s note: this article was written in the early days of Vite, some functions and principles of Vite have been updated.)

\

How to use vite

Like common development tools, Vite provides the method of generating project structure using NPM or YARN first, and using YARN at the terminal:

$ yarn create vite-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev
Copy the code

To initialize a Vite project (the default application template is vue3.x), the resulting project structure is very concise:

| ____node_modules | ____App. Vue / / application gateways | ____index. / / HTML page entry | ____vite. Config. Js / / configuration file | ____package. JsonCopy the code

To start the application, run yarn dev.

\

Vite Starts the link

The command parsing

NPM scripts are parsed by minimist, a lightweight command resolution tool, resolveOptions, SRC /node/cli.ts.

Function resolveOptions() {dev/build/optimize if (argv._[0]) {argv.command = argv._[0]; } return argv; }Copy the code

After receiving options, the options.com command determines whether to run runServe in the development environment or runBuild in the production environment based on the options.com command value.

if (! options.command || options.command === 'serve') { runServe(options) } else if (options.command === 'build') { runBuild(options) } else if (options.command === 'optimize') { runOptimize(options) }Copy the code

In the runServe method, the create development server method of the Server module is performed, and the build method of the build module is performed in runBuild.

The latest release also adds support for the Optimize command, but what Optimize does will be discussed below.

\

server

SRC /node/server/index.ts exposes a createServer method.

Vite uses KOA as a Web server, uses CLmLoader to create a watcher that listens for file changes, and implements a plug-in mechanism. Inject a context object into each plugin by combining koA-app with Watcher and other necessary tools.

Context consists of the following:

The plugin takes these components from the context in turn. Some plugins add middleware to koA instances, and some use Watcher to listen for changes to files. At the same time, each plug-in handles different things and has more distinct responsibilities,

\

plugin

We talked about plugins, but what are the plugins? They are:

  • User-injected plugins — custom plugins
  • HmrPlugin — Handles HMR
  • HtmlRewritePlugin — Rewrite script content within HTML
  • ModuleRewritePlugin – Overriding import imports in modules
  • ModuleResolvePlugin — Gets the content of the module
  • VuePlugin — Handles vUE single file components
  • EsbuildPlugin — Use esBuild to process resources
  • AssetPathPlugin – Handles static resources
  • ServeStaticPlugin – Manages static resources
  • CssPlugin — Handles references to CSS /less/sass etc
  • .

The plugin can be used to intercept JSON files.

interface ServerPluginContext {
  root: string
  app: Koa
  server: Server
  watcher: HMRWatcher
  resolver: InternalResolver
  config: ServerConfig
}

type ServerPlugin = (ctx:ServerPluginContext)=> void;

const JsonInterceptPlugin:ServerPlugin = ({app})=>{
    app.use(async (ctx, next) => {
      await next()
      if (ctx.path.endsWith('.json') && ctx.body) {
        ctx.type = 'js'
        ctx.body = `export default json`
      }
  })
}
Copy the code

The principles behind Vite are all in plugin. The purpose of each plugin is not explained here, but discussed in the principles behind vite below.

build

Node /build/index.ts /index.ts/node.build /index.ts/node.build /index.ts/node.build Since the build uses rollup, these plugins are also packaged for rollup and will not be covered in this article.

\

How vite works

ES module

To understand how Vite works, it’s important to know what the ES Module is.

Mainstream browsers (except IE11) all support it, its biggest characteristic is to use exportimPort in browser to import and export modules, set type=”module” in script tag, then use module content.

<script type="module"> import {bar} from './bar.js' </script>Copy the code

When the above script tag is embedded in HTML, the browser will make an HTTP request to HTTTP Server hosted bar.js. In bar.js, we use named export to export the bar variable. You can get the definition of bar in the script above.

// bar.js 
export const bar = 'bar';
Copy the code

The role in Vite

Open the running Vite project and visit the view-source to find this code in the HTML:

<script type="module"> import { createApp } from '/@modules/vue' import App from '/App.vue' createApp(App).mount('#app')  </script>Copy the code

\

From this code, we can get the following information:

  • fromhttp://localhost:3000/@modules/vueTo derivecreateAppThis method
  • fromhttp://localhost:3000/App.vueTo get the application entry
  • usecreateAppCreate an application and mount a node

CreateApp is the API for vue3.x, which is used to create a vue application. Vite uses ES Module to create a Vue application directly in the browser. This is done to:

  1. Remove packing step
  2. Implement load \ on demand

Remove packing step

The concept of packaging is that developers use a packaging tool to bundle application modules together and read the module’s code with certain rules — for use in browsers that do not support modularity.

To load modules in the browser, the packaging tool uses glue code to assemble modules, such as Webpack using map to store module ids and paths, and the __webpack_require__ method to retrieve module exports.

By taking advantage of the browser’s native support for modular imports, Vite eliminates the need to assemble modules, so you don’t need to generate bundles, so you can skip the packaging step.

\

Implement on-demand packaging

As mentioned earlier, packaging tools such as WebPack pack modules into bundles in advance, but the packaging process is static — regardless of whether a module’s code is executed, the module is packed into the bundle. The disadvantage of this is that as the project gets bigger, the bundle gets bigger.

In order to reduce the bundle size, developers load modules asynchronously by dynamically importing import(), or remove unreferenced modules as much as they can using tree shaking. However, these methods are not as elegant as Vite. Vite can introduce a module dynamically (with import()) only when it is needed, without having to package it in advance, which is only useful in a development environment, but that’s enough.

\

How does Vite handle ESM

Since Vite uses ESM to use modules in the browser, how exactly does this step work?

As mentioned above, to use ES Modules in a browser is to use HTTP requests to get modules, so Vite must provide a Web server to proxy these modules. Koa is responsible for this. Vite returns the contents of the resource to the browser by hijacking the requested path, but vite does something special for module imports.

\

What is @modules?

By comparing the index.html file in the project with the HTML source file in the development environment, it is found that the content in the script tag has changed

<script type="module">
    import { createApp } from 'vue'
    import App from '/App.vue'
    createApp(App).mount('#app')
</script>
Copy the code

Turned out to be

<script type="module"> import { createApp } from '/@modules/vue' import App from '/App.vue' createApp(App).mount('#app')  </script> importCopy the code

\

  1. Get the request body in the KOA middleware
  2. Use es-module-lexer to parse the ast resource to get the contents of the import
  3. Check whether the imported resource is an absolute path and consider it as an NPM module
  4. Returns the processed resource path:"vue" => "/@modules/vue"

\

This part of the code in the serverPluginModuleRewrite plugin,

\

Why @modules?

The esM in the browser cannot retrieve the contents of the imported module if we write the following code in the module:

import vue from 'vue'
Copy the code

Since the vue module is installed in node_modules, the previous use of Webpack, webpack encounters the above code, will help us do the following things:

  • Get the contents of this code
  • Parsed into AST
  • Walk through the AST to getimportThe name of the package in the statement
  • Use enhanced-resolve to fetch the actual address of the package for packaging,

But the ESM in the browser doesn’t have direct access to node_modules under the project, so Vite handles all imports, overwriting them with a prefix with @modules.

On the other hand, this is a very clever way to rewrite all the file paths in the same plugin, so that if you add more logic, it won’t affect other plugins. All other plugins get resource paths in @modules. For example, a later configuration might add aliases: just like webpack aliases: local files in a project can be configured as references to absolute paths.

\

How do I return module contents

In the next piece of KOa Middleware, match a resource with @modules on its path with are, and take the package export back to the browser via require(‘ XXX ‘).

In the past, packaging tools like WebPack were used to assemble modules together into bundles and allow packages that use different module specifications to refer to each other, such as:

  • ES Module (ESM) imports CJS
  • CommonJS (CJS) imports esM
  • Dynamic Import Imports the ESM
  • Dynamic Import imports CJS

\

Pit on es module can be reading this (zhuanlan.zhihu.com/p/40733281).

When Vite was originally designed for Vue 3.x, the Vue ESM package was treated specially, such as: @vue/runtime-dom = @vue/runtime-dom = @vue/runtime-dom = @vue/runtime-dom = @vue/runtime-dom = @vue/runtime-dom Do require(‘@vue/runtime-dom/dist/ Runtime-dom.esm-bundler.js’) so that vite can get the Vue package that complies with the ESM module standard.

At present, most modules in the community are not set to export ESM by default, but export CJS package. Since Vue3.0 requires extra processing to get ESM package content, do other daily use NPM package also need to support? The answer is yes, currently using LoDash directly in vite projects still generates errors.

The optimize command has been added in a recent update to Vite, which is specifically designed for module reference pothole. For example, to use Lodash in Vite, you just need to configure the optimizeDeps object in viet.config.js. Add LoDash to the include array.

// vite.config.js
module.exports = {
  optimizeDeps: {
    include: ["lodash"]
  }
}
Copy the code

\

When runOptimize is executed, vite recompiles the lodash package using Roolup and puts the new package compiled to the esM module specification into the.vite_opt_cache under node_modules. Depoptimizer.ts (); / / Depoptimizer.ts (); / / Depoptimizer.ts (); / / Depoptimizer.ts (); / / Depoptimizer.ts ();

In depoptimizer. ts, vite only handles packages declared in dependencies in package.json, so it cannot be used in a project

import pick from 'lodash/pick'
Copy the code

Use the pick method alone instead of using

import lodash from 'lodash'

lodash.pick()
Copy the code

This may have an impact on the volume of the bundle when some packages are used in production.

The content of the return module code in: serverPluginModuleResolve. Ts this plugin.

\

How does Vite compile modules

Vite was originally developed for vue3.x, so compilation here refers to compiling vue single-file components, while other ES modules can import content directly.

\

SFC

The vUE single-file component (SFC for short) is one of the bright points of VUE. Front-end SFC has received mixed reviews. Personally, SFC is more beneficial than harmful, although it brings additional development work, such as writing template parsers to parse templates. Also in the SFC to parse out the logic and style, in vscode to write vscode plug-in, in webpack to write vue-loader, but for the user can be in a file can write template, js, style, save the files jump to each other.

Similar to vue-loader, vite parses the vue file several times. If you open the browser network, you can see:

One request has nothing in the query, and the other two requests specify type style and template in the query, respectively.

Let’s take a look at how to convert an SFC into multiple requests, starting with the first request. The simplified code looks like this:

function vuePlugin({app}){ app.use(async (ctx, next) => { if (! ctx.path.endsWith('.vue') && ! Ctx.vue) {return next()} const query = ctx.query let filename = resolver.requesttofile (publicPath) // Resolver SFC const descriptor = await parseSFC(root, filename, ctx.body) if (! Descriptor) {ctx.status = 404 return} // First request.vue if (! query.type) { if (descriptor.script && descriptor.script.src) { filename = await resolveSrcImport(descriptor.script, CTX, resolver)} ctx.type = 'js' // body Returns the parsed code ctx.body = await compileSFCMain(descriptor, filename, publicPath) } // ... }Copy the code

In compileSFCMain there is a long code generate:

function compileSFCMain(descriptor, filePath: string, publicPath: string) { let code = '' if (descriptor.script) { let content = descriptor.script.content code += content.replace(`export  default`, 'const __script =') } else { code += `const __script = {}` } if (descriptor.styles) { code += `\nimport { updateStyle } from "${hmrClientId}"\n` descriptor.styles.forEach((s, i) => { const styleRequest = publicPath + `? type=style&index=${i}` code += `\nupdateStyle("${id}-${i}", ${JSON.stringify(styleRequest)})` }) if (hasScoped) { code += `\n__script.__scopeId = "data-v-${id}"` } } if (descriptor.template) { code += `\nimport { render as __render } from ${JSON.stringify( publicPath + `? type=template` )}` code += `\n__script.render = __render` } code += `\n__script.__hmrId = ${JSON.stringify(publicPath)}`  code += `\n__script.__file = ${JSON.stringify(filePath)}` code += `\nexport default __script` return code }Copy the code

Look directly at the code after Generate:

import { updateStyle } from "/vite/hmr" updateStyle("c44b8200-0", "/App.vue? type=style&index=0") __script.__scopeId = "data-v-c44b8200" import { render as __render } from "/App.vue? type=template" __script.render = __render __script.__hmrId = "/App.vue" __script.__file = "/Users/muou/work/playground/vite-app/App.vue" export default __scriptCopy the code

Vite/HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import, vite/ HMR import We do just one thing: by creating the Style element, we set its innerHtml to the CSS content.

Both of these methods cause the browser to make an HTTP request, which is picked up by the KOA middleware, resulting in the scenario we saw earlier: processing a.vue file three times.

This part of the code in the: serverPluginVue plugin.

css

What if you introduced a sass file in your Vite project?

Vite was originally developed for Vue projects, so there was no support for CSS precompilation, but with the next few major updates, using Sass/Less etc. in Vite projects can be just as elegant as webPack, simply by installing the corresponding CSS preprocessor.

In the cssPlugin, with the re: / (+). (less | sass | SCSS | styl | stylus) $/ whether path need CSS precompiled, if hit, regular use cssUtils method with the help of postcss to import in the CSS file to compile.

\

Vite hot update implementation

ServerPluginHmr Plugin: Path = vite/ HMR

app.use(async (ctx, next) => {
  if (ctx.path === '/vite/hmr') {
      ctx.type = 'js'
      ctx.status = 200
      ctx.body = hmrClient
  }
 }
Copy the code

HmrClient is a vite hot update client code, which needs to be executed in the browser. Here is the general hot update implementation, hot update generally needs four parts:

  1. First you need the Web framework to support rerender/ Reload for the module
  2. Listen for file changes through Watcher
  3. Compile resources through the server side and push new module content to the client.
  4. The client receives the new module contents, rerender/reload

Vite also has the same four parts, where the client code is in: client.ts, the server code is in serverPluginHmr, for vue component updates, through the HMRRuntime in vue3.

\

The client side

On the client side, WebSocket listens for some updated types and processes them separately:

  • vue-reload— VUE component Update: Import the new VUE component through import and executeHMRRuntime.reload
  • vue-rerender— Vue template update: Import a new template through import and executeHMRRuntime.rerender
  • Vue-style-update — Vue style update: Insert a new stylesheet directly
  • Style-update — CSS update: document inserts new stylesheet
  • Style-remove — CSS remove: document deletes stylesheet
  • Js-update — JS update: executes directly
  • full-reload— page roload: usewindow.reloadRefresh the page

The server side

On the server side, watcher listens for page changes and checks whether it is JS Reload or Vue Reload according to the file type:

watcher.on('change', async (file) => { const timestamp = Date.now() if (file.endsWith('.vue')) { handleVueReload(file, timestamp) } else if ( file.endsWith('.module.css') || ! (file.endsWith('.css') || cssTransforms.some((t) => t.test(file, {}))) ) { // everything except plain .css are considered HMR dependencies. // plain css has its own HMR logic in ./serverPluginCss.ts. handleJSReload(file, timestamp) } })Copy the code

In the handleVueReload method, the parser gets the template/script/style of the current file and compares it with the result of the last parse in the cache. If the template has changed, vue-rerender is executed. If the style changes, vue-style-update is executed. The simplified logic is as follows:

async function handleVueReload( file timestamp, Const cacheEntry = vueCache. Get (file) // Parse vue files const Descriptor = await parseSFC(root, file, content) if (! Descriptor) {// read failed return} // Get the last parse result const prevDescriptor = cacheEntry && cacheEntry. Descriptor // Set the refresh variable Let needReload = false // script change tag let needCssModuleReload = false // CSS change tag let needRerender = false // template // Check if the script is the same if (! IsEqual (descriptor. Script, prevDescriptor. Script)) {needReload = true} isEqual(descriptor.template, PrevDescriptor. Template)) {needRerender = true} // Send socket if (needRerender){send({type: 'vue-rerender', path: publicPath, timestamp }) } }Copy the code

The handleJSReload method determines which VUE component is dependent on according to the file path reference. If the vUE component dependency is not found, the page needs to be refreshed. Otherwise, the component update logic will be followed and no code will be posted here. \

The entire code is in client.ts and ServerPluginhMr.ts.

\

\

conclusion

This article looked at vite’s startup link and some of the rationale behind it, and while Vite isn’t going to replace WebPack anytime soon, it’s exciting to see an alternative in the community, which is why I wrote this article.

Vite has been updated so fast, thanks to the hard work and open source spirit of THE university, it has added CSS precompilation/React support/universal HMR support in just a month. Due to lack of space, this article will not introduce these new features.