What is the vite
Vite – a Web development tool developed by VUE author Yu Yuxi, which has the following features:
- Quick cold start
- Instant module hot update
- 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:
- from
http://localhost:3000/@modules/vue
To derivecreateApp
This method - from
http://localhost:3000/App.vue
To get the application entry - use
createApp
Create 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:
- Remove packing step
- 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
\
- Get the request body in the KOA middleware
- Use es-module-lexer to parse the ast resource to get the contents of the import
- Check whether the imported resource is an absolute path and consider it as an NPM module
- 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 get
import
The 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:
- First you need the Web framework to support rerender/ Reload for the module
- Listen for file changes through Watcher
- Compile resources through the server side and push new module content to the client.
- 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 execute
HMRRuntime.reload
- vue-rerender— Vue template update: Import a new template through import and execute
HMRRuntime.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: use
window.reload
Refresh 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.