preface
The author took a look at Vite earlier, and if you understand how fast it is, I’m sure you’ll love Vite and doubt anyone will ever abandon the build tools that need to be packaged. Vue3+Vite+Ts will definitely become the trend of the new technology era, causing a new round of technology update upsurge. Now follow me from shallow to deep, light up your front-end skill tree together, empower technology, and have your own habitat in the blue ocean of the future.
🎈 i. What is a Vite?
Vite, a next-generation front-end development and build tool based on the browser’s native ES MODULE. Using the browser to parse import to send Http requests, the server interception returns a given resource, completely abandon the packaging operation, the server is ready to use. Not only does it support Vue files, but it also handles hot updates, which don’t go down as fast as modules go up (the downside of other build tools), loads on demand, and only uses modules as needed. Build on production environment using Rollup.
🐬 ii. Vite features
🎃 1. True on-demand compilation
Return whatever you need.
💡 2. Start the extreme service
Use native ESM modules without packaging
⚡️ 3. Lightweight and fast thermal overload HMR
Always-on-one module hot overload regardless of application size.
🛠️ 4. Rich functions
Support for TypeScript, JSX, CSS, and more out of the box.
📦 5. Optimized build
Optional pre-configured Rollup builds in multi-page application or Library mode
🔩 6. Common plug-ins
Plug-ins for Vite and Rollup can be shared
🔑 7. Fully typed apis
API complete and complete TS type declarations
Three. 🦞 ESModule
ESModule is a browser-supported modularity scheme that allows modularity in code. Here we take the vite project’s app.vue as an example
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + Vite" />
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Copy the code
When we request app.vue, the browser makes the request, as shown in the figure below
🦝 4. Browser compatibility
-
Development environment: Vite needs to be used in browsers that support dynamic import of native ES modules.
-
Online environment: Default supported browsers need to support the introduction of native ES modules via script tags.
The ESModule is currently based on the Web standards specification and covers 90% of the browsers on the market.
In Vite we can see this code in the entry index.html:
We can tell if the browser supports es modules by checking the script type=”module”. If it doesn’t, the script won’t run. For unsupported browser solutions we can use SystemJS to support it, which is polyfill for browser support. You can also use the official vite plugin @vitejs/ plugin-Legacy to support older browsers. This is not the point and we will not mention it.
🐧 5. Why use Vite
1. Pain points of packaging-based build tools
Before browsers supported ES modules, there was no native mechanism for developers to develop in a modular way. Hence the concept of packaging, the use of tools to link, transform, manipulate, crawl, etc., source code into files that can be run in a browser. To this day, we’ve seen the creation of packaging tools and a wave of new technologies (Webpack,Parcel,rollup, etc.) that have greatly improved the development experience for front-end developers. In large enterprise applications, as the number of modules increases (thousands of modules), these “packaged” build tools introduce a real pain point, where development productivity plummets and development servers start up in minutes. Even with HMR, it is possible to change a line of code and heat reload it for a few seconds, wasting a lot of time over and over again.
(1) The development server starts slowly
When cold starting the development server, the packager-based approach is to build your application through a series of processes before providing the service.
As you can see from the figure, before the server is ready, modules are packaged into bundles, whether you use them or not.
(2) Slow thermal overload
When we start the development server based on the packer, updating one piece of code will refactor the entire file, and obviously we should not rebuild the entire package, otherwise the update speed will drop with the size of the file. Some packers load files into memory, and when the file changes, it only takes a part of the module to become inactive, and even then the file needs to be rebuilt and reloaded, which is still expensive. The packer supports dynamic module hot overloading (HMR) : allowing a module to “hot replace” itself without affecting the rest of the page. This greatly improves the development experience, however, it has been found in practice that even HMR update rates decrease significantly as the application size increases.
This is done in Vite, citing official answers and screenshots:
In Vite, HMR is executed on native ESM. When editing a file, Vite only needs to precisely invalidate the chain between the edited module and its nearest HMR boundary (most of the time just the module itself), making HMR updates always fast, regardless of the size of the application.
Vite serves source code in a native ESM way. This essentially lets the browser take over part of the packaging: Vite simply transforms the source code when the browser requests it and makes it available on demand. Code that is dynamically imported according to the situation is only processed if it is actually used on the current screen.
Vite also uses HTTP headers to speed things up. Modules are treated as two types in Vite, one is a dependency module (the dependency library we use, usually unchanged, generally pure JS) and one is a source module (we write code such as.vue,.tsx,.less, etc.). Vite uses a negotiated cache (304 Not Modified) when requesting source code; Cache-control (max-age=31536000,immutable) is used when requesting dependent modules, so that no further requests can be made if the Cache is refreshed. Knowledge portal 🚀
Some students will ask, that if you want to update how to do: strong look at the big guy Zhang Yunlong’s answer
So here comes Vite, designed to address the pain points mentioned above, designed to take advantage of new advances in the ecosystem, using browsers to start natively supporting ES modularity, true load on demand, hot update speed is not affected by file size.
🦂 vi. Why do production environments need to be packaged
Even though ES Modules are widely supported, nesting imports can cause additional network requests, and publishing as packaged ESModules in production environments is not the most efficient (even with HTTP2). For best performance, it’s better to tree-shaking code, lazy loading, and chunk splitting for efficiency.
🧰 7. Core principles
1. Module declaration
First declare that this is a module by putting type= “module” in the
<script type="module" src="main.js"></script>
Copy the code
Files imported via SRC or import will make HTTP requests; Vite intercepts these requests and specialises in the request file.
2. Replace the raw module
When you try to request a file from the node_modules folder, a bare module replacement is performed (the force is converted to a relative force), and only relative and absolute forces are recognized in the browser.
Import Vue from ‘Vue’ will be converted to import Vue from ‘/@modules/ Vue ‘
3. Analytical / @ modules
Next, parse /@modules into the actual file address and return it to the browser. Other packaging tools, such as WebPack packaging, do this for us.
The webpack file imported by import looks for moduel properties in the node_modules/ package name /package.json file, as shown in the following example.
{
"license": "MIT"."main": "index.js"."module": "dist/vue.runtime.esm-bundler.js"."name": "vue"."repository": {
"type": "git"."url": "git+https://github.com/vuejs/vue-next.git"
},
"types": "dist/vue.d.ts"."unpkg": "dist/vue.global.js"."version": "3.2.20"
}
Copy the code
Just return dist/vue.runtime.esm-bundler.js.
4. Parse single file (SFC) components
We know that the.vue file contains three parts: Template,script, and style. Vite handles these three parts separately
(1) Parse the template
/@vue/compiler-dom is used to compile the template in vite, and the result is similar to the following figure
(2) Process script
Vite uses /@vue/ Compiler-sFC to handle single-file components, just like vue-loader. It should parse as follows.
(3)
Vite tostyle
In particular, you can see the vite project requesttype=style
Is called in the content of the returnupdateStyle
Methods,Vite
Is put it inHot updateIn the mini-Vite module, we simulate the implementation of the way inclient
Implement a simplified version of this feature.
🔥 eight. Mini-vite implementation
1. Project preparation
Create folder mini-vite, then go to the folder and perform NPM init -y initialization
Open the editor and modify the package.json file as shown below:
{
"name": "mini-vite"."version": "1.0.0"."description": ""."main": "mini-vite.js"."scripts": {
"dev": "nodemon mini-vite"."test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": []."author": ""."license": "ISC"."devDependencies": {
"koa": "^ 2.13.4." "."vue": "^ 3.2.20"}}Copy the code
NPM I install dependency, after completion of the index.js, rename to mini-viet. js, as the entry file of the project, the modified project structure is as follows:
Add the following code to mini-viet.js:
const Koa = require('koa')
const app = new Koa();
app.use(ctx= > {
ctx.body = "hello Vite"
})
app.listen(3000.function() {
console.log('started vited')})Copy the code
Running in terminal, NPM run dev starts
The browser opens the project, as shown below, and the project is ready to complete
2. Return to the HTML file
Create a new index. HTML file in the root directory and write the following code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Hello, mini - Vite!!!!!!</title>
</head>
<body>
<div id="app"></div>
<script src="/main.js" type="module"></script>
</body>
</html>
Copy the code
Create main.js in the project root directory and write the following code
alert('hello mini-Vite!!! ')
Copy the code
Modify the Mini-Vite code as follows:
// mini-vite.js
const Koa = require("koa");
const path = require("path");
const fs = require("fs");
const app = new Koa();
app.use((ctx) = > {
const { url } = ctx.request;
const home = fs.readFileSync('./index.html'.'utf-8')
// 1. Return HTML
if (url === "/") {
ctx.type = "text/html"
ctx.body = home
}
});
app.listen(3000.function () {
console.log("started vited");
});
Copy the code
The project structure is as follows:
Re-open the browser to browse:
We can clearly see that localhost returns our index.html. Some partners will ask, why main.js request reported red?? At this point we can consider that we only processed the “/” request, not the other request, and look at the main
Is it suddenly realized, we are not as long as the REQUEST URL matching JS can be, we look down
3. Return the JS request
Let’s think, how do we file all the JS files and return them correctly?
Some children have already found that all JS request urls end with.js, we just need to fetch the corresponding file under the corresponding URL, and return it directly, let’s look at the code.
Modify the Mini-vite code, add the following code
. app.use((ctx) = > {
const { url } = ctx.request;
const home = fs.readFileSync('./index.html'.'utf-8')
// 1. Return HTML
if (url === "/") {
ctx.type = "text/html"
ctx.body = home
} else if(url.endsWith('.js')) {
// return js
const filePath = path.join(__dirname,url) // Get absolute strength
const file = fs.readFileSync(filePath, 'utf-8')
ctx.type = "application/javascript"ctx.body = file } }); .Copy the code
We refresh in the browser, and the result is shown
We can clearly see that we have achieved the desired result.
Main.js is not too young too simple!! We made the following changes to the main.js code:
// main.js
import { createApp, h } from "vue";
createApp({
render() {
return h("div".""."hello mini-vite!!!");
}
}).mount('#app');
Copy the code
We refresh the browser:
Vue module cannot be recognized by browsing/
.. /
.../
This road force is at the beginning, so how do we solve it, let’s go down
4. Replace the raw module
According to the vite principle, first we should replace the bare module with /@modules/vite. We modify the mini-vite.js code as follows:
// main.js
app.use((ctx) = > {
// ...
else if (url.endsWith(".js")) {
// return js
const filePath = path.join(__dirname, url); // Get absolute strength
const file = fs.readFileSync(filePath, "utf-8");
ctx.type = "application/javascript";
// ctx.body = file
// Replace bare modules with /@modules/, and the browser will initiate the requestctx.body = rewirteImport(file); }});/ * * *@description: raw module replacement, import XXX from "XXX" -----> import XXX from "/@modules/xxx"
* @param {*} content
* @return {*}* /
function rewirteImport(content) {
return content.replace(/ from ['"](.*)['"]/g.(s1, s2) = > {
// s1, match part, s2: match group content
if (s2.startsWith(". /") || s2.startsWith("/") || s2.startsWith(".. /")) {
// Return directly with relative strength
return s1;
} else {
return ` from "/@modules/${s2}"`}}); }// ...
Copy the code
We refresh the browser and the result looks like this:
As a result, we’ve made a preliminary substitution; Now just return the request starting with /@modules/ to the module attribute value file in node_modules/ package name /package.json. We add it under the mini-Vite code. As follows:
app.use((ctx) = > {
// ...
else if (url.startsWith("/@modules/")) {
// 3. Return the real file referenced by node_modules/ package name /package.json.module
ctx.type = "application/javascript";
/** The file prefix */
const filePrefix = path.resolve(
__dirname,
"node_modules",
url.replace("/@modules/".""));/** Get moudule in node_modules/ package.json */
console.log(filePrefix, "ttt");
const module = require(filePrefix + "/package.json").module;
const file = fs.readFileSync(filePrefix+'/'+module."utf-8");
// If you need to import XXX, continue to replace itctx.body = rewirteImport(file); }});// ...
Copy the code
We refresh the browser and the result looks like this:
We can see it clearlyVue
It has returned successfully.
We found that the page reported an error:
Because some libraries call process, such as Vue3 here, to determine if it is production, and we do not have the process variable, the question is, what should we do??
σ (⊙▽⊙” A, some children have already thought of it, let’s simulate it, let’s mount a process on the window, is not the moment suddenly realized!! Let’s go down
5. Simulation of the process
For this problem, I initially used the first scheme, introducing cross-env and modifying package.json dev script as follows:
Then print it when mini-viet.js returns HTML with the following code:
Do you think this problem can be solved? Why is that?
The answer is: NO!! The reason is that our code is executed in the browser, the root object of the browser is window, there is no property process, we should add this property on the window
To see the correct solution, add the following code to index.html:
<body> <div id="app"></div> <script> // hash: Circumvent window.process = {env: {NODE_ENV: 'dev', } } </script> <script src="/main.js" type="module"></script> </body>Copy the code
Refresh the browser, and we will not report the error if we find the result.
6. Single file (SFC) component processing
In Vite, the single file processing is compiled using @vue/ Compiler-SFC module, so we also use this module to complete the processing of VUE files. The result of its parsing is something like this:
(1) Script processing
First we create app.vue in the SRC folder
<! -- * @author: Water ice Miao * @Date: 2021-11-01 15:06:48
* @LastEditTime: 2021-11-01 15:43:51* @filepath: \mini-vite\ SRC \ app.vue --><template>
<div id="app">
{{title}}
</div>
</template>
<script>
export default {
data () {
return {
title: "hello Vite"}}}</script>
<style>
#app {
color: red;
font-weight: 400;
}
</style>
Copy the code
Then change the main.js code to the following:
/* * @author: @date: 2021-10-22 20:58:14 * @lasteditTime: 2021-11-01 15:18:34 * @lasteditors: Please set LastEditors * @description: main.js, simulation vue project entry file * @filepath: \mini-vite\main.js */
import { createApp } from "vue";
import App from "./src/App.vue"
createApp(App).mount('#app')
Copy the code
Then add the following code to mini-Vite:
// ...
else if (url.includes(".vue")) {
// Get absolute strength, url.slice(1) remove the first '/'
const filePath = path.resolve(__dirname, url.slice(1));
// console.log(filePath, url,'path')
const { descriptor } = compilerSfc.parse(
fs.readFileSync(filePath, "utf-8"));/ / processing script
if(! query.type) {/ / get the script
const scriptContent = descriptor.script.content
// export default {... } --------> const __script = {... }
const script = scriptContent.replace('export default '.'const __script = ')
// Returns the result of app.vue parsing
ctx.type = 'text/javascript'
ctx.body = `
${rewirteImport(script)}// If there is a style, send a request to get the style part${descriptor.styles.length ? `import "${url}? type=style"` : ' '}Import {render as __render} from 'import {render as __render} from'${url}? type=template' __script.render = __render export default __script `}}// ...
Copy the code
We refresh the browser and f12 cuts to Network to see the result:
Now that we can see the request style and template, let’s move on to template and style
(2) Process template
In vue, we use @vue/compiler-dom to compile the template. Since we return the Runtime version of vue, there is no compiler, we should return the compiled template. Let’s return the render function. Continue adding code to mini-viet.js:
/ /... To deal with the template
else if(query.type === 'template') {
const templateContent = descriptor.template.content
const render = compilerDom.compile(templateContent, {
mode: 'module'
}).code
ctx.type = "application/javascript"
ctx.body = rewirteImport(render);
}
// ... template
Copy the code
We refreshed the browser and found the following:
Now let’s deal with style
(3)
Vite’s style processing is special, in the hot update module, because we did not implement hot update, let’s simulate the implementation here, return the Style content, in the client side to implement the method.
New code in mini-viet.js
// ...
/ / processing style
else if (query.type === "style") {
const styleBlock = descriptor.styles[0];
ctx.type = "application/javascript";
ctx.body = `
const css = The ${JSON.stringify(styleBlock.content)};
updateStyle(css);
export default css;
`;
}
// ...
Copy the code
Add updateStyle code to index.html:
/ /...<body>
<div id="app"></div>
<script>
// hash: To avoid environment judgment by process.env.node_env
window.process = {
env: {
NODE_ENV: 'dev',}};function updateStyle(content) {
const isExist = typeofCSSStyleSheet ! = =undefined
if(isExist) {
// Method 1, use a constructible style sheet
let cssStyleSheet = new CSSStyleSheet()
cssStyleSheet.replaceSync(content)
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
cssStyleSheet
]
} else {
2 / / method
let style = document.createElement('style')
style.setAttribute('type'.'text/css')
style.innerHTML = content
document.head.appendChild(style)
}
}
</script>
<script src="/main.js" type="module"></script>
</body>/ /...Copy the code
Method 1: with constructible stylesheets, method 2 is a no-brait
Refresh the browser and see the result:
We can see from the figure that the SFC component has been successfully parsed.The core principle of hand-tearing Mini-Vite
That’s it.
7. Optimized point
Some students have found that the speed of our refresh is very slow, we actually said in the above principle has been said, Vite will rely on the use of strong cache, the source code using negotiated cache to speed up the response, we here to simulate the operation. Core ideas:
Cache-control: max-age=31536000,immutable; cache-control: no-cache; If etag is present, ifNoneMatch (eTAG) will be sent to the server. We compare the value with the unique hash value of the source code. If it is the same, 304 will be returned. If ifNoneMatch is absent or different, 200 and the new resource are returned and a new eTAG is set in the response header. If ifNoneMatch does not exist in the request header but ifModifiedSince(value: last-modified) is used to compare ifModifiedSince with the Last modified time of the source code, if the same, 304 is returned; If ifModifiedSince is not the same or does not exist, 200 and the new resource are returned and a new last-Modified is set in the response header. The condition for returning 304 to read from the cache is that there is a cache, and we all use HTTP header Expires caching for source code for a period of time.
The caching process can be referred to the following figure:
A bit different from the diagram, the source code file is requested directly from the negotiation cache, and does not judge the local cache. Instead, it makes a request directly and lets the server decide.
The code looks like this:
// mini-vite.js
// ...
/** Get the last modification time of the file */
const getFileUpdatedDate = (path) = > {
const stats = fs.statSync(path);
return stats.mtime;
};
/** Negotiates whether to return 304 or 200 */
const ifUseCache = (ctx, url, ifNoneMatch, ifModifiedSince) = > {
let flag = false
// Use negotiation cache
ctx.set('Cache-Control'.'no-cache')
// Set the expiration time to 30000 milliseconds, i.e. 30 seconds later
ctx.set("Expires".new Date(Date.now() + 30000));
let filePath = url.includes(".vue")? url : path.join(__dirname, url);if (url === "/") {
filePath = path.join(__dirname, "./index.html");
}
// Get the last modification time of the file
let fileLastModifiedTime = getFileUpdatedDate(filePath);
console.log(fileLastModifiedTime, "lastTime");
const buffer = fs.readFileSync(filePath, "utf-8");
// Calculates the MD5 value of the requested file
const hash = crypto.createHash("md5");
hash.update(buffer, "utf-8");
/ / get the etag
const etag = `${hash.digest("hex")}`;
if (ifNoneMatch === etag) {
ctx.status = 304;
ctx.body = "";
flag = true
} else {
// Etag inconsistent update the tag value, return a new resource
ctx.set("etag", etag);
flag = false
}
if(! ifNoneMatch && ifModifiedSince === fileLastModifiedTime) { ctx.status =304;
ctx.body = "";
flag = true
} else {
// The last modified time is inconsistent. Update the last modified time and return a new resource
ctx.set("Last-Modified", fileLastModifiedTime);
flag = false
}
return flag
};
app.use(async (ctx) => {
const { url, query } = ctx.request;
const { "if-none-match": ifNoneMatch, "if-modified-since": ifModifiedSince } =
ctx.request.headers;
const home = fs.readFileSync("./index.html"."utf-8");
// 1. Return HTML
if (url === "/") {
ctx.type = "text/html";
ctx.body = home;
} else if (url.endsWith(".js")) {
ctx.set("cache-control"."no-cache");
// Determine whether to read the cache
const used = ifUseCache(ctx, url, ifNoneMatch, ifModifiedSince);
if (used) {
ctx.status = 304
ctx.body = null
return;
}
/ /...
} else if (url.startsWith("/@modules/")) {
// ...
// Dependencies use strong caching
ctx.set("cache-control"."max-age=31536000,immutable");
// ...
} else if (url.includes(".vue")) {
// ...
const usedCache = ifUseCache(
ctx,
url.slice(1).split("?") [0],
ifNoneMatch,
ifModifiedSince
);
if (usedCache) {
ctx.status = 304
ctx.body = null
return;
}
// ...}});Copy the code
The first request results in the following, we can see that the source code has met the requirements, and the dependency has met the requirements:
On the second request we can find,Rely on
Strong caching has been used for slavememory
ordisk
Read the source code and go to the negotiation is returned304
or200
.
🐳 9. First Vite project
1. Install
Compatibility Note Vite requires node.js versions >= 12.0.0.
Use NPM:
npm init @vitejs/app
Copy the code
The use of YARN
yarn create @vitejs/app
Copy the code
You can also run the following command:
# npm 6.x
npm init @vitejs/app my-vue-app --template vue
# NPM 7+ requires additional double lines:
npm init @vitejs/app my-vue-app -- --template vue
# yarn
yarn create @vitejs/app my-vue-app --template vue
Copy the code
Supported template presets include:
vanilla
vue
vue-ts
react
react-ts
preact
preact-ts
lit-element
lit-element-ts
See @vitejs/create-app for more details on each template.
Let’s look at the page request: the page entry returns index.html, which uses script type=”module” to introduce the entry file main.js, which will be requested when SRC or import is encountered:
When the browser requests the main.js file, Vite returns the following
Here a bare-mod quick substitution occurs, replacing import {createApp} from ‘vue’ with a relative path. Because browsers only have relative or absolute paths. Behind and launched a request to the App. Vue, vue. Js, HelloWorld. Vue
Type =style is used to process CSS and return it. CSS is handled in a special way. As you can see, updateStyle() is called in the return and vite puts it in the hot module update.
2. The command line
In projects with Vite installed, you can use the Vite executable in NPM Scripts or run it directly with NPX Vite. Here are the default NPM scripts for Vite projects created through scaffolding:
{
"scripts": {
"dev": "vite".// Start the development server
"build": "vite build".// Build artifacts for production environments
"serve": "vite preview" // Preview production builds locally}}Copy the code
You can use –port to specify the port to boot from, — HTTPS to use HTTPS, NPX vite –help to get more, a follow-up article on this takes you from 0 to lift a scaffolding tool
🐋 x. Custom Vite
1. Configuration file
(1) Configuration file parsing
When running vite from the command line, the default configuration file read is the vite.config.js file in the root directory. The default configuration generated by scaffolding looks like this:
You can also use vite –config filePath to specify configuration files, which default is based on the current project root directory.
(2) Configure intelligent prompt
Since Vite itself supports TS, you can use IDE and JSDOC to do this
/ * * * *@type {import('vite').UserConfig}* /
const config = {
// ...
}
export default config
Copy the code
Or use the helper function defineConfig to get an intelligent hint that doesn’t apply to JSDoc:
import { defineConfig } from 'vite'
export default defineConfig({
// ...
})
Copy the code
Define the configuration based on the environment
We can output different configurations based on environment (development, production) or command (server,build), as follows:
export default ({ command, Mode}) => {if (command === 'serve') {return {// serve unique configuration}} else {return {// build unique configuration}}}Copy the code
🦄 11. Conclusion
The code is rough, not too much encapsulation, just do the idea of transfer, in fact, there are still a lot of places can be optimized. Static resource hosting, resolution of less, SASS, Stylus, etc., can be integrated with ESBuilder to do more things, this is not mini-Vite, we only implement the core principles and ideas, interested children can continue to research to achieve their ideal.
🦓 twelve. Complete code
Mini-vite complete code
🦝 13. Reference
- Vite – Chinese
- Node practices thoroughly understanding strong and negotiated caches