Liang Xiaoying, a piggy girl who likes swimming & reading.
Have you failed to learn Vue3 recently? Especially yuxi especially big without a break and gave you a special development tool for Vue3 – Vite. Do you feel less silky using Webpack during development, or can you take a few sips of hot water while waiting for build to start? This article will give you a brief understanding of the basic knowledge and functions of Vite, so that we can better start the journey of Vue3 development. First, before learning Vite, you must have at least two parts of the knowledge reserve: 1) Understand ES Modules 2) understand Http2 standard, limited space, here will not be described
First, the source of the problem
1.1 Webpack slot
If the application is more complex, the development process using Webpack is less comfortable.
- Webpack Dev Server takes a long time to start in cold mode. - Webpack HMR responds slowly to hot updatesCopy the code
1.2 Review the original intention of Webpack
[Previous Technical environment] We used Webpack to package the application code and ended up generating bundle.js for two main reasons:
- Browser environments do not support modularity well - scattered module files can generate a large number of HTTP requestsCopy the code
1.3 Thinking about the Present
Bundle is too big, so I should use all kinds of Code Splitting, compress Code, remove plug-ins and extract third-party libraries. So tired~ can we solve the difficulties of Webpack at that time? thinking~~
Second, the solution
2.1 ES Module
- The first problem has gradually disappeared as browsers have improved their support for the ES standard. ES Modules is supported by most browsers today.
Its biggest feature is to import and export modules in the browser by using export import, write type=”module” in the script tag, and then use ES module.
// When the ES Module script tag is embedded in HTML, the browser will make an HTTP request to the HTTP server hosted main.js;
// index.html
<script type="module" src="/src/main.js"></script>
// Use export to export modules, import to import modules:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')
Copy the code
Error: Access index.html directlyUsing ES Module in the browser is an HTTP request for the module, so file requests are not allowed.
2.2 Module Analysis
Let’s create a static service locally and open index.html again Error: module vue not found; Cause: “/”, “./”, or “.. /” import relative/absolute path is legal.
import vue from 'vue'
Copy the code
This means that the ESM in the browser does not get the content of the imported module. When we write code, if we do not reference the relative path module, but the module reference node_modules, we directly import XXX from ‘XXX’, by Webpack and other tools to help us deal with the interdependence between JS, find the specific path of this module to package. But the browser doesn’t know you have node_modules in your project, so it can only find modules in relative or absolute paths.
So what?? So one of Vite’s tasks is to start a Web server to proxy these modules, and Vite here borrows koA to start a service
export function createServer(config: ServerConfig) :Server {
// ...
const app = new Koa<State, Context>()
const server = resolveServer(config, app.callback())
// ...
const listen = server.listen.bind(server)
server.listen = (async(port: number, ... args: any[]) => {if(optimizeDeps.auto ! = =false) {
await require('.. /optimizer').optimizeDeps(config)
}
returnlisten(port, ... args) })as any
server.once('listening'.() = > {
context.port = (server.address() as AddressInfo).port
})
return server
}
Copy the code
So that brings us to one of the core implementations of Vite – intercepting browser requests for modules and returning the result of the processing. How does Vite do that?
2.3 /@module/
The prefix
By comparing main.js in the project with main.js in the development environment, it is found that the main.js content has changed
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')
Copy the code
Turned out to be
import { createApp } from '/@modules/vue.js' import App from '/src/App.vue' import '/src/index.css? import' createApp(App).mount('#app')Copy the code
In order to solve the problem of import XXX from ‘XXX’, Vite does a uniform handling of the resource path, adding a /@module/ prefix. We are in the SRC/node/server/serverPluginModuleRewrite ts source this koa middleware can see Vite did a layer of processing for the import, the process is as follows:
- Get the request CTx.body in 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
- RewriteImports returns the processed resource path: “vue” => “/@modules/vue”
How to support /@module/? In/SRC/node/server/serverPluginModuleResolve ts can be seen in the processing logic is probably
- Get the request CTx.body in koA middleware
- Check whether the path starts with /@module/, if the package name is retrieved
- Go to node_module and find the library and return the corresponding contents based on package.json
2.4 File Compilation
What about other files such as Vue, CSS, TS, etc.? Let’s take a look at the vue file as an example. In Webpack we use vue-loader to compile single-file components. In fact, Vite also intercepts the module request and performs a real-time compilation. By comparing the App. Vue under the project with the actual loaded App. Vue under the development environment, it is found that the original App
<template> <img Alt ="Vue logo" SRC ="./assets/logo.png" /> <HelloWorld MSG ="Hello Vue 3.0 + Vite" /> import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', components: { HelloWorld } } </script> <style> body { background: #fff; } </style>Copy the code
Turned out to be
import HelloWorld from '/src/components/HelloWorld.vue'
const __script = {
name: 'App'.components: {
HelloWorld
}
}
import "/src/App.vue? type=style&index=0"
import {render as __render} from "/src/App.vue? type=template"
__script.render = __render
__script.__hmrId = "/src/App.vue"
typeof__VUE_HMR_RUNTIME__ ! = ='undefined' && __VUE_HMR_RUNTIME__.createRecord(__script.__hmrId, __script)
__script.__file = "/Users/liangxiaoying/myfile/wy-project/vite-demo/src/App.vue"
export default __script
Copy the code
This splits a.vue file into three requests (script, style, and template). The browser receives the response from app. vue that contains the script logic, parses it to the template and style paths. An HTTP request is made again to request the corresponding resource, which Vite intercepts and processes again before returning the corresponding content.
In fact, after seeing this idea, the processing of other types of files is almost similar logic, depending on the file type requested, different compilation processing. In fact, Vite does on-demand compilation in real time by intercepting requests on top of load on demand
HTTP 2.5 2
- Scattered module files in HTTP 1.x will indeed generate a large number of HTTP requests, and a large number of HTTP requests in the browser side will concurrently request resources; But with HTTP 2, these problems are gone.
- why?
In HTTP 1.x, multiple TCP connections must be used if multiple requests are to be made concurrently. In addition, the browser limits the number of TCP connection requests for a single domain name to 6-8 in order to control resources. HTTP 2 can be usedmultiplexingTo replace the original sequence and blocking mechanism. All requests are made concurrently over a TCP connection.
Three, three functions
Vite: Static Server + HMR + Compile
3.1 Fast cold start
Community: For example, you can use various CLI: vue-CLI, create-react-app, etc
When we compare the use of vue-CLI-service serve, there will be an obvious feeling.This is because Webpack Dev Server needs to be built first, and the build process takes a lot of time. Vite is completely different. When we execute Vite Serve (npm run dev), starts the Web Server internally without compiling all the code files first.That is just to start the Web Server, the speed is naturally slow upβ. What about just-in-time compilation? Support for JSX, TSX, Typescript compilation to native JS — Vite introducedEsBuild, is written using Go, directly compiled into Native code, performance is 20 or 30 times better than TSC, so do not worry about ~ of course will also use cache, specific here will not be extended.
3.2 Instant hot module updates
Community: Webpack HMR, etc
When it comes to hot updates, Vite only needs to compile the file being modified immediately, so it responds very quickly.When Webpack modifies a file, it will automatically rewrite the build again using the file as the entry point, and all dependencies involved will be loaded once, so the response time will be much slower.
3.3 True on-demand compilation
Community: it is up to developers to introduce other plug-ins impor(‘xx.js’) into their code to implement dynamic-import; Such as @ Babel/plugin – syntax – dynamic – import
But tools like Webpack do this by pre-compiling and packaging all modules into bundles. In other words, modules are compiled and packaged into bundles regardless of whether they will be executed or not. As the project gets bigger and the bundle gets bigger and bigger, the packaging process gets slower and slower.
Vite takes advantage of modern browsers’ native ESM support, omitting the packaging of modules.
For files that need to be compiled, Vite uses a different mode: just-in-time compilation. That is, a file is compiled only when it is specifically requested. Therefore, the main benefits of this “just-in-time compilation” are as follows: on-demand compilation.
Iv. Core ideas
4.1 Starting the Static Service initially
Initial execute commands NPM run dev – > actual is launched/SRC/node/server/index. The ts such as mentioned above has launched a koa server, the file also USES the chokidar library to create a watcher, changes to monitor files:
export function createServer(config: ServerConfig) :Server {
// Start static server:
const app = new Koa<State, Context>()
const server = resolveServer(config, app.callback())
......
const listen = server.listen.bind(server)
server.listen = (async(port: number, ... args: any[]) => { ... })as any
// Key 1: use Chokidar to recursively listen on files: when the file changes are monitored, different modules can process them accordingly
const watcher = chokidar.watch(root, {
ignored: ['**/node_modules/**'.'**/.git/**'],... })as HMRWatcher
// Key 2: execute various plug-ins
const resolvedPlugins = [
// rewrite and source map plugins take highest priority and should be run
// after all other middlewares have finished
sourceMapPlugin,
moduleRewritePlugin,
htmlRewritePlugin, // Process HTML files
// user plugins. toArray(configureServer), envPlugin, moduleResolvePlugin, proxyPlugin, clientPlugin,// Outputs the client execution code
hmrPlugin, // Process hot module updates. (transforms.length ||Object.keys(vueCustomBlockTransforms).length
? [
createServerTransformPlugin(
transforms,
vueCustomBlockTransforms,
resolver
)
]
: []),
vuePlugin, // Handle single-file components
cssPlugin, // Process the style file
enableEsbuild ? esbuildPlugin : null,
jsonPlugin,
assetPathPlugin,
webWorkerPlugin,
wasmPlugin,
serveStaticPlugin
]
resolvedPlugins.forEach((m) = > m && m(context))
}
Copy the code
4.2 Listen for messages and intercept some requests
We can see the initial first request as follows:So where did this file come from? This is after clientPlugin [/ SRC/node/server/serverPluginClient ts γ processing output:
export const clientPublicPath = `/vite/client` // The current file name
const legacyPublicPath = '/vite/hmr' // Name of the historical version.export const clientPlugin: ServerPlugin = ({ app, config }) = > {
// clientCode replaces the configured information for the final body output:
const clientCode = fs
.readFileSync(clientFilePath, 'utf-8')
.replace(`__MODE__`.JSON.stringify(config.mode || 'development'))... app.use(async (ctx, next) => {
// The request path is /vite/client and the response: 200 is returned with the processed clientCode text
if (ctx.path === clientPublicPath) {
// Set the socket configuration
let socketPort: number | string = ctx.port
...
if (config.hmr && typeof config.hmr === 'object') {
// HMR Option has the highest priority. } ctx.type ='js'
ctx.status = 200
// Return the integrated body
ctx.body = clientCode.replace(`__HMR_PORT__`.JSON.stringify(socketPort))
} else {
if (ctx.path === legacyPublicPath) { // Historical version /vite/ HMR
console.error('xxxx')}return next()
}
})
}
Copy the code
SRC /client/client.ts = body = clientCode = client.ts; So what did it do?? Use Websoket to process messages, quickly compile, and achieve real-time hot updates:
const socketProtocol =
__HMR_PROTOCOL__ || (location.protocol === 'https:' ? 'wss' : 'ws')
const socketHost = `${__HMR_HOSTNAME__ || location.hostname}:${__HMR_PORT__}`
// Start webSocket communication, can process messages in real time, to achieve HMR
const socket = new WebSocket(`${socketProtocol}: / /${socketHost}`.'vite-hmr')...Copy the code
Listening for messages:
socket.addEventListener('message'.async ({ data }) => {
const payload = JSON.parse(data) as HMRPayload | MultiUpdatePayload
handleMessage(payload)
})
Copy the code
Processing messages:
async function handleMessage(payload) {
const { path, changeSrcPath, timestamp } = payload;
switch (payload.type) {
case 'connected': // The socket connection succeeded
console.log(`[vite] connected.`);
break;
case 'vue-reload': // The component reloads
queueUpdate(import(`${path}? t=${timestamp}`)
.catch((err) = > warnFailedFetch(err, path))
.then((m) = > () = > {
__VUE_HMR_RUNTIME__.reload(path, m.default);
console.log(`[vite] ${path} reloaded.`);
}));
break;
case 'vue-rerender': // Re-render the component
const templatePath = `${path}? type=template`;
import(`${templatePath}&t=${timestamp}`).then((m) = > {
__VUE_HMR_RUNTIME__.rerender(path, m.render);
console.log(`[vite] ${path} template updated.`);
});
break;
case 'style-update': // Style update
// check if this is referenced in html via <link>
const el = document.querySelector(`link[href*='${path}'] `);
if (el) {
el.setAttribute('href'.`${path}${path.includes('? ')?'&' : '? '}t=${timestamp}`);
break;
}
// imported CSS
const importQuery = path.includes('? ')?'&import' : '? import';
await import(`${path}${importQuery}&t=${timestamp}`);
console.log(`[vite] ${path} updated.`);
break;
case 'style-remove': // Style removed
removeStyle(payload.id);
break;
case 'js-update': / / js update
queueUpdate(updateModule(path, changeSrcPath, timestamp));
break;
case 'custom': // Custom update
const cbs = customUpdateMap.get(payload.id);
if (cbs) {
cbs.forEach((cb) = > cb(payload.customData));
}
break;
case 'full-reload': // The page is refreshed
if (path.endsWith('.html')) {... }else{ location.reload(); }}}Copy the code
Yi? So if you set up a message listener, who sends the message?
4.3 Different plug-ins, listen for file changes and return messages
For example: cssPlugin [/ SRC/node/server/serverPluginCss ts.
// Process CSS files, listen for CSS file changes
export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) = > {
// Outputs the CSS request response template
export function codegenCss(id: string, css: string, modules? : Record
,>) :string {
let code =
`import { updateStyle } from "${clientPublicPath}"\n` +
`const css = The ${JSON.stringify(css)}\n` +
`updateStyle(The ${JSON.stringify(id)}, css)\n`
if (modules) {
code += dataToEsm(modules, { namedExports: true})}else {
code += `export default css`
}
return code
}
app.use(async (ctx, next) => {
await next()
// Process imports of.css.const id = JSON.stringify(hash_sum(ctx.path))
if (isImportRequest(ctx)) {
const { css, modules } = await processCss(root, ctx)
ctx.type = 'js'
/ / use `? Import 'to rewrite the CSS file as a JS module, insert style tags, and link to the actual raw URL
ctx.body = codegenCss(id, css, modules)
}
})
watcher.on('change'.(filePath) = > {
// Filter out CSS files and update CSS request files
if(File update) {watcher. Send ({// Send a message
type: 'style-update'.path: publicPath,
changeSrcPath: publicPath,
timestamp: Date.now()
})
}
})
}
Copy the code
4.4 Logical Summary
- Use the current project directory as the root of the static file server
- Intercept partial file requests
- Process module B in import node_modules in the code
- Handles compilation of Vue single file components (SFC)
- Implement HMR through WebSocket
Snowpack VS Vite
With:
- Basic principle: Snowpack V2 and Vite both provide development server based on native ES module import of browser;
- Cold start fast: Both projects have similar performance characteristics in terms of speed of development feedback;
- Out of the box: avoid configuration of various Loaders and plugins;
Vite supports more opt-in features by default – such as TypeScript transpilation, CSS import, CSS Modules, and postCSS support (which requires a separate compiler installation) are readily available and do not require configuration; Snowpack also supports JSX, TypeScript, React, Preact, CSS Modules and other builds.
- Plugins: Support for many custom plugins; Vite’s official documentation for this part is not yet available.
Vision:
- Evolution: Snowpack did not initially provide HMR support, but it was added in V2, bringing the scope of the two projects closer together. Vite was originally a reference to Snowpack V1; The two sides have cooperated on ESM-based HMR, trying to establish a unified API ESM-HMR API specification, but it will still be slightly different because of the underlying differences.
- Use: Vite is currently only available for Vue 3.x. Use + React and other templates, Vue support is better π; Snowpack has no limits;
- Production package: Vite uses rollup, smaller package volume (rollupInputOptions: define the plug-in function of rollup); Snowpack parcel/webpack; – Determines how developers will produce personalized configurations differently
- Bias: Vue support is a first level feature in Vite. For example, Vite provides a more fine-grained integration of HMR and Vue, and fine-tunes the build configuration to generate the most efficient bundles;
- Documentation perfection:
- The advantage of Vitejs is that it is produced by Yuxi, which may better integrate with Vue3 ecology. The downside is incomplete documentation. Current STAR 13.7k;
- Snowpack is more mature. It has a mature V1 and an official version of V2, supports react, Vue, Preact, Svelte and other applications, and has more complete documentation. Current STAR 14.4K.
So… How to choose? : = > choose Vite:
- Prefer Vue, Vite provides better support;
- Vue uses rollupπ for small bundles.
= > choose Snowpack:
- Don’t like using Vue, don’t use VUe-CLI, like react etc.
- Big teams want to use plugins, clear documentation π, etc.
- Used to Webpack, want to develop mode do not bundle, faster π.
The purpose of this article is to guide Vue developers to learn more about the ideas and new ideas in the aspects of compilation on demand, so as to help children develop efficiently and reduce the learning path. It is expected that Vite will not only become a supporting tool for VUE, but also form a more mature community program in the future to promote technological progress!!