前言
之前作者学了一下Vite,如果您了解其速度之快,我相信你会爱上Vite,甚至怀疑大家会摒弃需要打包的构建工具。Vue3+Vite+Ts 绝对会成为新的技术时代潮流,引起新的一轮技术更新热潮。现在跟着我一起从浅入深,一起点亮自己的前端技能树,为技术赋能,在未来的蓝海里有属于自己的一片栖息地。
🎈 一. Vite是什么?
Vite, 一个基于浏览器原生ES MODULE的下一代前端开发和构建工具。利用浏览器解析import发出Http请求,服务端拦截返回给定资源,完全摒弃了打包操作,服务器即启即用。他不仅支持Vue文件,还搞定了热更新,并且热更新的速度并不会随着模块的增多而直线下降(其它构建工具的弊端),按需加载,仅使用需要的模块。在生产环境上使用Rollup进行构建。
🐬 二. Vite的特点
🎃 1. 真正的按需编译
你需要啥,就返回啥。
💡 2. 极速的服务启动
使用原生ESM模块,无需打包
⚡️ 3. 轻量快速的热重载HMR
无论应用程序大小如何,始终于一的模块热重载。
🛠️ 4. 丰富的功能
对 TypeScript、JSX、CSS 等支持开箱即用。
📦 5. 优化的构建
可选 “多页应用” 或 “库” 模式的预配置 Rollup 构建
🔩 6. 通用的插件
vite和Rollup的插件是可以共用的
🔑 7. 完全类型化的API
API完整和完全的TS类型声明
🦞 三. ESModule
ESModule是浏览器支持的模块化方案,允许在代码中实现模块化。这儿我们以vite项目的app.vue为例子
<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>
当我们请求App.vue时,浏览器会发起请求,如图所示
🦝 四. 浏览器兼容性
-
开发环境:Vite 需要在支持 原生 ES 模块动态导入 的浏览器中使用。
-
线上环境:默认支持的浏览器需要支持 通过脚本标签来引入原生 ES 模块 。 目前基于Web标准规范的ESModule以及覆盖市面上90%的浏览器。
在Vite中我们在入口index.html中可以看到这样一段代码:
判断兼容性的方法如下: 我们根据script的type="module"可以判断浏览器支不支持es modules,如果不支持,该script里面的内容就不会运行。 对于不支持的浏览器解决方案咱们可以使用systemjs来支持,它就是使浏览器支持的polyfill。也可以使用vite官方插件 @vitejs/plugin-legacy 支持旧浏览器。这儿不是重点咱们就不提及了。
🐧 五. 为什么使用Vite
1. 基于打包构建工具的痛点
在浏览器支持ES模块之前,开发者没有以模块式的方法开发的原生机制。因此出现了打包这个概念,使用工具将源代码以链接,转换,处理,抓取等方式到文件中,使其可以运行在浏览器上。 直到今日,我们见证了许多打包工具的诞生并掀起了一波新的技术热潮(webpack,Parcel,rollup等),这些工具极大程度上提升了前端开发者的开发体验。在大型企业应用中,随着模块的数量提升(上千个模块),这些基于“打包”的构建工具带来一个现实痛点,开发效率呈直线下降,开发服务器启动长达几分钟。即使使用HMR,有可能出现改动一行代码,热重载几秒钟才能呈现出来,如此循环往复,浪费了极大的时间。
(1)开发服务器启动缓慢
当冷启动开发服务器时,基于打包器的方式是在提供服务前经过一系列处理来构建你的应用。
由图可知,在服务器就绪前,不管你否使用到某些模块,都打包到了bundle中。
(2)热重载缓慢
当基于打包器启动开发服务器时,我们更新一处代码,将会重构整个文件,显然我们不应该重新构建整个包,否则更新速度将随文件体积直线下降。 一些打包器将文件载入内存,当文件更改时,只要使模块的一部分失去活跃,即使如此,也是需要重新构建文件在重新载入,代价依然很高。打包器支持了动态模块热重载(HMR):允许一个模块 “热替换” 它自己,而对页面其余部分没有影响。这大大改进了开发体验,然而,在实践中发现,即使是 HMR 更新速度也会随着应用规模的增长而显著下降。
vite中这样完成,引用官方回答和截图:
在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失效(大多数时候只需要模块本身),使 HMR 更新始终快速,无论应用的大小。
Vite 以 原生 ESM 方式服务源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入的代码,即只在当前屏幕上实际使用时才会被处理。
Vite同时使用了http头加快了速度,vite中把模块视为两种,一种是依赖模块(我们使用到的依赖库,通常不会变化,一般是纯js),一种是源码模块(我们写的代码如.vue,.tsx,.less等)。在请求源码时,vite使用协商缓存(304 Not Modified);在请求依赖模块时,使用强缓存(Cache-Control: max-age=31536000,immutable) 来处理,这样一旦缓存即使刷新也不会进行在请求了。 知识点传送门🚀
有的同学会问,那如果要更新怎么办呢: 强烈看看大佬张云龙的回答
如此,vite来了,Vite的就是为了解决以上所述痛点而设计的,旨在利用生态系统中的新进展解决上述问题,利用浏览器开始原生支持ES模块化,真正意义上面的按需加载,热更新速度并不受文件体积大小影响。
🦂 六. 为什么生产环境还需要打包
即使ES MODULE得到广泛的支持,但是在嵌套导入时,会照成额外的网络请求,并且生产环境中发布为打包的esModule的不到最好的效率(即使使用HTTP2)。为了得到最好的性能,还是将code进行tree-shaking、懒加载和chunk分割来获得更好的效率。
🧰 七. 核心原理
1. 模块声明
首先在把 type= "module"
放到<script>
标签中来声明这是一个模块
<script type="module" src="main.js"></script>
这样通过src
或import
导入的文件将会发起http请求;vite会拦截这些请求,并将请求文件进行特别处理。
2. 裸模块替换
当试图请求node_modules文件夹的文件时,会进行裸模块替换
(路劲转换为相对路劲),浏览器中只识别相对路劲
和绝对路劲
。
import Vue from 'vue' 会转换成 import Vue from '/@modules/vue'
3. 解析/@modules
接下里将/@modules
解析为真正的文件地址,并返回给浏览器。其它打包工具,如webpack
打包时帮我们做了这件事情。
通过 import
导入的文件 webpack
会去 node_modules/包名/package.json
文件内找 moduel
属性,如下例子。
{
"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"
}
只要将这个dist/vue.runtime.esm-bundler.js
这个地址文件返回就好了。
4. 解析单文件(SFC)组件
我们知道.vue文件中包含了template
,script
,style
,三个部分,vite分别对这三个部分做了处理
(1)解析template
vite中使用/@vue/compiler-dom
来编译template, 编译后返回的结果类似如下图所示
(2)处理script
vite中使用/@vue/compiler-sfc
来处理单文件组件,它的作用跟vue-loader一样。它的解析结果就好像如下所示。
(3)解析style
vite对 style
的处理比较特殊,可以看到vite项目请求type=style
返回的内容中调用了 updateStyle
方法,在 Vite
中是把它放在了 热更新 的模块中,在mini-vite中,我们模拟实现的方式, 在client
实现该功能的简版。
🔥 八. mini-vite实现
1. 项目准备
创建文件夹mini-vite,然后进入文件夹执行npm init -y 初始化
编辑器打开,并修改package.json文件如下图:
{
"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"
}
}
npm i 安装依赖,完成后将index.js,重命名为mini-vite.js,作为项目的入口文件,修改后的项目结构如下所示:
向mini-vite.js中添加如下代码:
const Koa = require('koa')
const app = new Koa();
app.use(ctx => {
ctx.body = "hello Vite"
})
app.listen(3000, function() {
console.log('started vited')
})
在终端运行,npm run dev启动
浏览器打开项目,如下所示,到此项目准备完成
2. 返回html文件
在根目录下新建index.html文件,并写入如下代码
<!DOCTYPE html>
<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>
项目根目录新建main.js,并写入以下代码
alert('hello mini-Vite!!!')
修改mini-vite代码,如下所示:
// 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. 返回html
if (url === "/") {
ctx.type = "text/html"
ctx.body = home
}
});
app.listen(3000, function () {
console.log("started vited");
});
项目结构如下所示:
重新打开浏览器浏览:
从图中咱们可以清楚的看到localhost时返回了咱们的index.html
。有的小伙伴们就会问,为啥main.js请求报红呢???
这时我们可以思考一下,我们只处理了"/"的请求,并没有处理其它请求,我们查看main.js的请求路劲
是不是恍然大悟,咱们是不是只要请求url匹配js
就可以了,我们往下看
3. 返回js请求
我们思考一下,我们该如何对所有的js文件进行文件进行正确返回呢?
有的童鞋已经发现了,所有js请求url都是以.js
结尾的,我们只需取到对应url下的对应文件,在直接返回就ok了,我们看代码。
修改mini-vite代码, 添加如下代码
...
app.use((ctx) => {
const { url } = ctx.request;
const home = fs.readFileSync('./index.html', 'utf-8')
// 1. 返回html
if (url === "/") {
ctx.type = "text/html"
ctx.body = home
} else if(url.endsWith('.js')) {
// 2. 返回js
const filePath = path.join(__dirname,url) // 获取绝对路劲
const file = fs.readFileSync(filePath, 'utf-8')
ctx.type = "application/javascript"
ctx.body = file
}
});
...
我们在浏览器刷新,结果如图所示
我们可以清晰的看到已经完成我们想要的结果了。
main.js是不是too young too simple!!!
我们对main.js代码作出以下修改:
// main.js
import { createApp, h } from "vue";
createApp({
render() {
return h("div", "", "hello mini-vite!!!");
}
}).mount('#app');
我们刷新浏览器:
报错,浏览无法识别模块vue,只能识别已
/
,./
,../
这种路劲开头(即相对路劲),那我们怎么解决呢,我们往下看
4. 裸模块替换
根据vite原理,首先我们应该将裸模块
替换成/@modules/vite
,我们修改mini-vite.js代码, 如下所示:
// main.js
app.use((ctx) => {
// ...
else if (url.endsWith(".js")) {
// 2. 返回js
const filePath = path.join(__dirname, url); // 获取绝对路劲
const file = fs.readFileSync(filePath, "utf-8");
ctx.type = "application/javascript";
// ctx.body = file
// 裸模快替换成/@modules/包名,浏览器就会发起请求
ctx.body = rewirteImport(file);
}
});
/**
* @description: 裸模块替换, import xxx from "xxx" -----> import xxx from "/@modules/xxx"
* @param {*} content
* @return {*}
*/
function rewirteImport(content) {
return content.replace(/ from ['"](.*)['"]/g, (s1, s2) => {
// s1, 匹配部分, s2: 匹配分组内容
if (s2.startsWith("./") || s2.startsWith("/") || s2.startsWith("../")) {
// 相对路劲直接返回
return s1;
} else {
return ` from "/@modules/${s2}"`
}
});
}
// ...
我们刷新浏览器,结果如下图所示:
从结果上,我们完成了,初步的替换;也发起了Vue的请求
,请求路劲也是咱们想要的,现在只需将请求路劲以/@modules/
开头的请求返回
node_modules/包名/package.json
里面的module
属性值文件即可,我们在mini-vite代码下进行添加,如下所示:
app.use((ctx) => {
// ...
else if (url.startsWith("/@modules/")) {
// 3. 返回裸模快引用的node_modules/包名/package.json.module引用的真实文件
ctx.type = "application/javascript";
/** 文件前缀 */
const filePrefix = path.resolve(
__dirname,
"node_modules",
url.replace("/@modules/", "")
);
/** 得到node_modules/包名/package.json 里面的moudule路劲 */
console.log(filePrefix, "ttt");
const module = require(filePrefix + "/package.json").module;
const file = fs.readFileSync(filePrefix+'/'+module, "utf-8");
// 如果里面还要import XXX 再继续替换
ctx.body = rewirteImport(file);
}
});
// ...
我们刷新浏览器,结果如图所示:
我们可以清楚的看到
Vue
已经成功返回了。
我们发现页面报了一个错误:
因为有的库会访问process
,如这儿的Vue3会判断是否为生产环境,而我们这儿并没有process这个变量,那么问题来了,我们应该怎么做呢???
诶Σ(⊙▽⊙"a,是不是有的童鞋已经想到了,咱们模拟一下不就Ok了,咱们在window
上面挂载一个process
,是不是瞬间恍然大悟!!!我们往下看
5. 模拟process
针对这个问题我一开始使用的是方案一,引入cross-env,修改package.json的dev脚本为以下:
然后在mini-vite.js返回html时打印,代码如下:
大家觉得这个问题能够解决不?为什么?
答案是:NO!!!,原因就是,咱们的代码是在浏览器执行的,浏览器的根对象是window,并不存在属性process,我们应该在window上面添加这个属性
我们来看正确解决方法,在index.html
中添加如下代码:
<body>
<div id="app"></div>
<script>
// hash: 规避 通过process.env.NODE_ENV环境判断
window.process = {
env: {
NODE_ENV: 'dev',
}
}
</script>
<script src="/main.js" type="module"></script>
</body>
刷新一下浏览器,咱们发现结果就不报错了。
6. 单文件(SFC)组件处理
在vite
中,单文件处理是使用的是@vue/compiler-sfc
模块进行编译处理的,因此咱们也借助该模块完成Vue文件的处理。
它解析的结果大概如下:
(1)script处理
首先我们在src
文件夹下新建App.vue
<!--
* @Author: 水冰淼
* @Date: 2021-11-01 15:06:48
* @LastEditTime: 2021-11-01 15:43:51
* @LastEditors: Please set LastEditors
* @Description: App.vue代码
* @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>
然后修改main.js代码为以下:
/*
* @Author: 水冰淼
* @Date: 2021-10-22 20:58:14
* @LastEditTime: 2021-11-01 15:18:34
* @LastEditors: Please set LastEditors
* @Description: main.js,模拟vue项目入口文件
* @FilePath: \mini-vite\main.js
*/
import { createApp } from "vue";
import App from "./src/App.vue"
createApp(App).mount('#app')
然后在mini-vite里添加如下代码:
// ...
else if (url.includes(".vue")) {
// 获得绝对路劲, url.slice(1)去掉第一个'/'
const filePath = path.resolve(__dirname, url.slice(1));
// console.log(filePath, url,'path')
const { descriptor } = compilerSfc.parse(
fs.readFileSync(filePath, "utf-8")
);
// 处理script
if (!query.type) {
// 获取script
const scriptContent = descriptor.script.content
// export default {...} --------> const __script = {...}
const script = scriptContent.replace('export default ', 'const __script = ')
// 返回App.vue解析结果
ctx.type = 'text/javascript'
ctx.body = `
${rewirteImport(script)}
// 如果有 style 就发送请求获取 style 的部分
${descriptor.styles.length ? `import "${url}?type=style"` : ''}
// 发送请求获取template部分,这儿返回一个渲染函数
import { render as __render } from '${url}?type=template'
__script.render = __render
export default __script
`
}
}
// ...
我们刷新浏览器,f12切到network查看结果:
我们可以看到请求style和template了,下面我们继续往下处理template和style
(2) 处理template
在vue中我们使用@vue/compiler-dom
来编译 template
,由于我们返回的vue
是runtime
版本的,是没有编译器的,我们应该将编译好的template返回回去,下面我们返回一个渲染(render)函数即可。
继续在mini-vite.js添加代码:
// ... 处理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
我们刷新浏览器,发现结果如下:
下面我们来处理style
(3) 解析style
Vite对style的处理比较特殊,处于热更新
模块中,由于我们没有实现热更新,咱们这儿就模拟实现一下,将style content返回,在客户端实现该方法。
在mini-vite.js新增代码
// ...
// 处理style
else if (query.type === "style") {
const styleBlock = descriptor.styles[0];
ctx.type = "application/javascript";
ctx.body = `
const css = ${JSON.stringify(styleBlock.content)};
updateStyle(css);
export default css;
`;
}
// ...
在index.html
中添加updateStyle
代码:
//...
<body>
<div id="app"></div>
<script>
// hash: 规避 通过process.env.NODE_ENV环境判断
window.process = {
env: {
NODE_ENV: 'dev',
}
};
function updateStyle(content) {
const isExist = typeof CSSStyleSheet !== undefined
if(isExist) {
// 方法1,使用可构造样式表
let cssStyleSheet = new CSSStyleSheet()
cssStyleSheet.replaceSync(content)
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
cssStyleSheet
]
} else {
// 方法2
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>
//...
方法1:使用了可构造样式表,方法2咱们就不巴拉巴拉了
刷新浏览器,咱们查看结果:
从图咱们可以发现,SFC组件已成功解析完成。
手撕mini-vite的核心原理
到此结束。
7. 优化点
有的同学已经发现了咱们刷新的速度是非常慢的,咱们其实在上文说原理时已经说到过,vite将依赖使用强缓存
,将源码使用协商缓存
进行响应速度的加快,我们这边来模拟一下这个操作。核心思想:
如果是依赖我们使用
强缓存
,即(Cache-control:max-age=31536000,immutable),如果是源码咱们使用协商缓存(即Cache-control: no-cache)。这儿使用etag
,和Last-Modified
,首先判断是否存在etag,如果存在,会发送ifNoneMatch(值为etag)到服务器,咱们和源码的唯一hash值进行对比,如果相同返回304,如果不存在ifNoneMatch或者不相同,将会返回200和新的资源并在响应头设置新的etag
;如果请求头不存在ifNoneMatch而存在ifModifiedSince(值为:Last-modified),将使用ifModifiedSince和源码的最后修改时间对比,如果相同,返回304;如果不相同或者不存在ifModifiedSince,将返回200和新的资源并在响应头设置新的Last-modified
。返回304从缓存读取的条件就是前提有缓存,我们对源码都使用http头Expires
缓存一段时间。
缓存流程可以参考下图所示:
跟图有点不同的是,源码文件第二次请求时,是直接走协商缓存了,是不会判断本地缓存的而是直接发起请求让服务器去决策了。
代码如下所示:
// mini-vite.js
// ...
/** 获取文件的最后修改时间 */
const getFileUpdatedDate = (path) => {
const stats = fs.statSync(path);
return stats.mtime;
};
/** 协商缓存判断返回304还是200 */
const ifUseCache = (ctx, url, ifNoneMatch, ifModifiedSince) => {
let flag = false
// 使用协商缓存
ctx.set('Cache-Control', 'no-cache')
// 设置过期时间在30000毫秒,也就是30秒后
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");
}
// 获取文件的最后修改时间
let fileLastModifiedTime = getFileUpdatedDate(filePath);
console.log(fileLastModifiedTime, "lastTime");
const buffer = fs.readFileSync(filePath, "utf-8");
// 计算请求文件的md5值
const hash = crypto.createHash("md5");
hash.update(buffer, "utf-8");
// 得到etag
const etag = `${hash.digest("hex")}`;
if (ifNoneMatch === etag) {
ctx.status = 304;
ctx.body = "";
flag = true
} else {
// etag不一致 更新tag值,返回新的资源
ctx.set("etag", etag);
flag = false
}
if (!ifNoneMatch && ifModifiedSince === fileLastModifiedTime) {
ctx.status = 304;
ctx.body = "";
flag = true
} else {
// 最后修改时间不一致,更新最后修改时间,返回新的资源
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. 返回html
if (url === "/") {
ctx.type = "text/html";
ctx.body = home;
} else if (url.endsWith(".js")) {
ctx.set("cache-control", "no-cache");
// 判断是否读取缓存
const used = ifUseCache(ctx, url, ifNoneMatch, ifModifiedSince);
if (used) {
ctx.status = 304
ctx.body = null
return;
}
//...
} else if (url.startsWith("/@modules/")) {
// ...
// 依赖使用强缓存
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;
}
// ...
}
});
第一次请求结果如下,我们可以看到源码
已经符合需求了,依赖
也符合需求了:
第二次请求咱们可以发现,依赖
已经使用强缓存从内存
或磁盘
读取了, 源码走的协商是返回304
还是200
。
🐳 九. 第一个vite项目
1.安装
兼容性注意Vite需要 Node.js 版本 >= 12.0.0。
使用 NPM:
npm init @vitejs/app
使用YARN
yarn create @vitejs/app
也可以使用如下命令:
# npm 6.x
npm init @vitejs/app my-vue-app --template vue
# npm 7+, 需要额外的双横线:
npm init @vitejs/app my-vue-app -- --template vue
# yarn
yarn create @vitejs/app my-vue-app --template vue
支持的模板预设包括:
vanilla
vue
vue-ts
react
react-ts
preact
preact-ts
lit-element
lit-element-ts
查看 @vitejs/create-app 获取每个模板的更多细节。
我们观察页面请求:
页面入口返回了index.html,其中中通过script type="module"引入了入口文件main.js,当遇到src或者import导入时将会发起请求
:
浏览器在请求main.js文件,vite返回如下
这儿发生了裸模快替换
,将import {createApp} from 'vue' 替换成了相对路径。因为浏览器只有相对路径或绝对路径。后面又发起了请求App.vue,vue.js,HelloWorld.vue
通过type=style中来处理css并返回, 对Css的处理比较特殊,可以看到返回之中调用了updateStyle(),vite是把它放在热模块更新之中,这个不是咱们的重点,本文写mini-vite时候会用模拟方法实现。
2.命令行
在安装了 Vite 的项目中,可以在 npm scripts 中使用 vite
可执行文件,或者直接使用 npx vite
运行它。下面是通过脚手架创建的 Vite 项目中默认的 npm scripts:
{
"scripts": {
"dev": "vite", // 启动开发服务器
"build": "vite build", // 为生产环境构建产物
"serve": "vite preview" // 本地预览生产构建产物
}
}
你可以通过 --port 指定启动的端口,通过 --https使用https, 执行npx vite --help获取更多, 后续出一篇文章讲解这个带你从0撸一个脚手架工具
🐋十. 定制Vite
1. 配置文件
(1)配置文件解析
当以命令行运行vite时,默认读取的配置文件是根目录下的vite.config.js
的文件, 脚手架生成的默认配置是这样的:
你也可以显示的通过 vite --config filePath
,来指定配置文件,默认基于当前项目根目录。
(2)配置智能提示
由于Vite本身支持TS,因此你可以通过IDE和JSDOC配合来进行提示
/**
** @type {import('vite').UserConfig}
*/
const config = {
// ...
}
export default config
或者使用助手函数defineConfig
, 这样不适用jsdoc也能获得智能提示:
import { defineConfig } from 'vite'
export default defineConfig({
// ...
})
根据环境来定义配置
我们可以基于环境(development, production)或者命令(server,build)来输出不同的配置, 如下所示:
export default ({ command, mode }) => {
if (command === 'serve') {
return {
// serve 独有配置
}
} else {
return {
// build 独有配置
}
}
}
🦄 十一. 结语
代码比较粗糙,没有做太多的封装,仅做思想传递,其实还是有很多可以优化的地方。静态资源
托管,解析less,sass,stylus等,可以集成esbuilder来做更多事情,这就不是mini-vite了,咱们只实现核心原理和思想,有兴趣的童鞋可以继续研究实现自己的ideal。