前端新工具—vite从入门到实践
liuian 2024-12-31 12:58 86 浏览
作者:蜗牛老师
转发链接:https://zhuanlan.zhihu.com/p/149033579
前言
前段时间尤大大在B站直播,介绍了一款新的前端开发工具,利用了浏览器自带的import机制,无论多大的项目,都是秒开,听起来很诱人,火速看了源码,并且最近做了《前端会客厅》后,经过尤大亲自讲解了设计思路,又有了新感悟,写个文章总结一下
能和尤大大当面交流vue3的设计思路 收获真的很大,最近也成为了vue3的contributor,希望下半年能给vue生态贡献更多的代码
#TOC
- 入门使用
- 自己实现支持html
- 支持js
- 支持第三方模块
- 支持.vue组件
- 支持import css
- 预告
补充
- vite开发环境利用浏览器的import机制,打包右内置的rollup,所以已经可以直接用了
如果对vite还不太了解,请看这一篇《一个由 Vue 作者尤雨溪开发的 web 开发工具—vite》
实战
这个没啥,github走起吧,贼简单 https://github.com/vitejs/vite
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev原理
然后我们看下大概的代码 一如既往的精简
? vite-app tree
.
├── index.html
├── package.json
├── public
│ └── favicon.ico
└── src
├── App.vue
├── assets
│ └── logo.png
├── components
│ └── HelloWorld.vue
├── index.css
└── main.js看下index和main, 就是利用了浏览器自带的import机制,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')
当浏览器识别type="module"引入js文件的时候,内部的import 就会发起一个网络请求,尝试去获取这个文件,我们先整个简单的,吧main.js清空以下
import {log} from './util.js'
log('xx')
目录新建util.js
export function log(msg){
console.log(msg)
}
但是现在会有一个小报错
Access to script at 'file:///src/main.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
main.js:1 Failed to load resource: net::ERR_FAILED
/favicon.ico:1 Failed to load resource: net::ERR_FILE_NOT_FOUNDvite的任务,就是用koa起一个http 服务,来拦截这些请求,返回合适的结果,就欧克了,下面我们一步步来,为了方便演示,代码简单粗暴
支持html和js
先不废话了,我们先用朴实无华的if else试下这个demo的功能
npm install koa --save拦截路由/ 和xx.js结尾的请求,代码呼之欲出
const fs = require('fs')
const path = require('path')
const Koa = require('koa')
const app = new Koa()
app.use(async ctx=>{
const {request:{url} } = ctx
// 首页
if(url=='/'){n
ctx.type="text/html"
ctx.body = fs.readFileSync('./index.html','utf-8')
}else if(url.endsWith('.js')){
// js文件
const p = path.resolve(__dirname,url.slice(1))
ctx.type = 'application/javascript'
const content = fs.readFileSync(p,'utf-8')
ctx.body = content
}
})
app.listen(3001, ()=>{
console.log('听我口令,3001端口,起~~')
})
访问locaohost:3001 看下console和network 搞定第一步 支持了import 本底的js文件
看到这里,你应该大概对vite为什么快,有一个初步的认识,这就是天生的按需加载呀,告别冗长的webpack打包
第三方库
我们不能满足于此,毕竟不可能所有模块都自己写,比如我们用到的vue 就是从npm 引入的,准确的来说,是从node_module引入的 改一下main.js
import { createApp } from 'vue'
console.log(createApp)
不出意外 报错了 我们要解决两个问题
1. 不是合法的相对路径,浏览器报错
Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".大概意思就是"/", "./", or "../"开头的路径,才是合法的,这个其实也好说,我们对main.js里返回的内容做个重写就可以,我们做个规定,把import from 后面,不是上面仨符号开头的,加一个/@module/前缀
// 替换前
import { createApp } from 'vue'
// 替换后
import { createApp } from '/@module/vue'
我们新建一个函数,其实vite是用的es-module-lexer来解析成ast拿到import的地址,我们既然是乞丐版,整个土鳖的正则把
// 单引号双引号都支持 我真是个小机灵
/from ['"]([^'"]+)['"]/g
大概就是from 后面 引号中间的内容抠出来 验证以下看看是不是加前缀即可,思路明确,代码就呼之欲出了
function rewriteImport(content){
return content.replace(/from ['"]([^'"]+)['"]/g, function(s0,s1){
// . ../ /开头的,都是相对路径
if(s1[0]!=='.'&& s1[1]!=='/'){
return `from '/@modules/${s1}'`
}else{
return s0
}
})
}
if(url.endsWith('.js')){
// js文件
const p = path.resolve(__dirname,url.slice(1))
ctx.type = 'application/javascript'
const content = fs.readFileSync(p,'utf-8')
ctx.body = rewriteImport(content)
}
在刷新,报了另外一个错 说明模块重写完毕,下面我们需要支持@module的前缀
GET http://localhost:3001/@modules/vue net::ERR_ABORTED 404 (Not Found)支持/@module/
解析的url的时候,加一个判断即可,主要就是要去node_module里找 大概逻辑
- url开头是/@module/ 就把剩下的路径扣下来
- 去node_module里找到这个库,把package.json读出来
- 我们用的import语法,所以把package.json里的Module字段读出来,就是项目的入口 替换回来即可
思路清楚了,代码就呼之欲出了
---- 孟德鸠斯注意node_module里的文件,也是有import 别的npm 包的,所以记得返回也要用rewriteImport包以下
if(url.startsWith('/@modules/')){
// 这是一个node_module里的东西
const prefix = path.resolve(__dirname,'node_modules',url.replace('/@modules/',''))
const module = require(prefix+'/package.json').module
const p = path.resolve(prefix,module)
const ret = fs.readFileSync(p,'utf-8')
ctx.type = 'application/javascript'
ctx.body = rewriteImport(ret)
}
然后报了一个小错 就是vue源码里有用process.ENV判断环境的,我们浏览器client里设置以下即可
Uncaught ReferenceError: process is not defined
at shared:442我们注入一个全局变量 ,vite的做法是解析html之后,通过plugin的方式注入,逼格很高,我这乞丐版,凑和replace一下吧
if(url=='/'){
ctx.type="text/html"
let content = fs.readFileSync('./index.html','utf-8')
content = content.replace('<script ',`
<script>
window.process = {env:{ NODE_ENV:'dev'}}
</script>
<script
`)
ctx.body = content
}
打开console yeah 折腾了半天,终于支持了第一行
.vue组件
然后我们把代码补全 main.js
import { createApp } from 'vue' // node_module
import App from './App.vue'
// import './index.css'
createApp(App).mount('#app')
App.vue
<template>
<h1>大家好 kkb欢迎你</h1>
<h2>
<span>count is {{count}}</span>
<button @click="count++">戳我</button>
</h2>
</template>
<script>
import {ref,computed} from 'vue'
export default {
setup(){
const count = ref(0)
function add(){
count.value++
}
const double = computed(()=>count.value*2)
return {count,add,double}
}
}
</script>
ok不出所料的报错了 毕竟我们node环境还没支持单文件组件,大家其实看下vite项目的network就大概知道原理了
- 发起.vue的请求后,先把script解析出来,然后里面加上请求template和css的import语句
- 把template解析成render函数,返回拼成一个组件
- 还是那句话,思路通了,代码就呼之欲出了,当时看到这里,觉得尤大真是优秀啊
看到app.vue的返回结果没,这就是我们的目标,核心就是
const __script = {
setup() {
...
}
}
import {render as __render} from "/src/App.vue?type=template&t=1592389791757"
__script.render = __render
export default __script
好了 写代码 拼呗
单文件组件解析
我们就不考虑缓存了,直接解析,我们直接用vue官方的@vue/compiler-sfc来整单文件,用@vue/compiler-dom来把template解析成 ,这块核心逻辑都是这里vue核心包的,我们反而没做啥,思路通了写代码
if(url.indexOf('.vue')>-1){
// vue单文件组件
const p = path.resolve(__dirname, url.split('?')[0].slice(1))
const {descriptor} = compilerSfc.parse(fs.readFileSync(p,'utf-8'))
if(!query.type){
ctx.type = 'application/javascript'
// 借用vue自导的compile框架 解析单文件组件,其实相当于vue-loader做的事情
ctx.body = `
// option组件
${rewriteImport(descriptor.script.content.replace('export default ','const __script = '))}
import { render as __render } from "${url}?type=template"
__script.render = __render
export default __script
`
}
}
看下结果 完美下一步搞定type=template的解析就可以,
模板解析
直接@vue/compiler-dom把html解析成render就可以, 可以在线体验一波
if(request.query.type==='template'){
// 模板内容
const template = descriptor.template
// 要在server端吧compiler做了
const render = compilerDom.compile(template.content, {mode:"module"}).code
ctx.type = 'application/javascript'
ctx.body = rewriteImport(render)
}
体验一下
支持css
其他的就思路类似了 比如支持css
import { createApp } from 'vue' // node_module
import App from './App.vue' // 解析成额外的 ?type=template请求
import './index.css'
createApp(App).mount('#app')
代码直接呼
if(url.endsWith('.css')){
const p = path.resolve(__dirname,url.slice(1))
const file = fs.readFileSync(p,'utf-8')
const content = `const css = "${file.replace(/\n/g,'')}"
let link = document.createElement('style')
link.setAttribute('type', 'text/css')
document.head.appendChild(link)
link.innerHTML = css
export default css
`
ctx.type = 'application/javascript'
ctx.body = content
}
其实内部设置css的逻辑,应该在client端注入,最好每个link加一个id,方便后续做热更新
支持typescript
其实支持less啥的逻辑都是类似的,vite用了esbuild来解析typescript, 比官方的tsc快了几十倍,快去体验一波 vite的实现 ifelse太多了,不不献丑了,下次再写 其实支持less sass都是类似的逻辑
总结
以上逻辑其实大家直接去看vite的import解析源码更合适 ,我只是希望能讲明白思路 代码略丑 请轻喷 就是通过拦截import的http请求,来实现无需打包,自带按需加载的工具
下一次来讲一下热更新怎么做的,其实核心逻辑就是注入http://socket.io ,后端数据变了,通知前端即可,大概类型如下 在线代码
// 不同的更新方式
interface HMRPayload {
type:
| 'js-update'
| 'vue-reload'
| 'vue-rerender'
| 'style-update'
| 'style-remove'
| 'full-reload'
| 'sw-bust-cache'
| 'custom'
timestamp: number
path?: string
changeSrcPath?: string
id?: string
index?: number
customData?: any
}
client代码
switch (type) {
case 'vue-reload': Vue组件更新
case 'vue-rerender': Vue-template更新
case 'style-update': css更新
case 'style-remove': css删除
case 'js-update': js更新
case 'full-reload': 全量重载更新
到此为止基本上vite我们就入门了,下篇文章写一下如何做的热更新 欢迎关注 ,敬请期待
推荐Vue学习资料文章:
《细聊Single-Spa + Vue Cli 微前端落地指南「实践」》
《一文带你搞懂vue/react应用中实现ssr(服务端渲染)》
《教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(上)》
《教你Vue3 Compiler 优化细节,如何手写高性能渲染函数(下)》
《Deno将停止使用TypeScript,并公布五项具体理由》
《为什么Vue3.0不再使用defineProperty实现数据监听?》
《如何写出优秀后台管理系统?11个经典模版拿去不谢「干货」》
《一个由 Vue 作者尤雨溪开发的 web 开发工具—vite》
《提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器》
《大厂Code Review总结Vue开发规范经验「值得学习」》
《带你了解 vue-next(Vue 3.0)之 炉火纯青「实践」》
《「干货」Vue+高德地图实现页面点击绘制多边形及多边形切割拆分》
《细品pdf.js实践解决含水印、电子签章问题「Vue篇」》
《Vue仿蘑菇街商城项目(vue+koa+mongodb)》
《基于 electron-vue 开发的音乐播放器「实践」》
《「实践」Vue项目中标配编辑器插件Vue-Quill-Editor》
《「干货」Deno TCP Echo Server 是怎么运行的?》
《「实践」基于Apify+node+react/vue搭建一个有点意思的爬虫平台》
《「实践」深入对比 Vue 3.0 Composition API 和 React Hooks》
《前端网红框架的插件机制全梳理(axios、koa、redux、vuex)》
《深入学习Vue的data、computed、watch来实现最精简响应式系统》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(一)》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(二)》
《教你部署搭建一个Vue-cli4+Webpack移动端框架「实践」》
《尤大大细品VuePress搭建技术网站与个人博客「实践」》
《是什么导致尤大大选择放弃Webpack?【vite 原理解析】》
《带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】》
《带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】》
《一篇文章教你并列比较React.js和Vue.js的语法【实践】》
《深入浅出通过vue-cli3构建一个SSR应用程序【实践】》
《聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总》
《【新消息】Vue 3.0 Beta 版本发布,你还学的动么?》
《Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5》
《深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】》
《手把手教你深入浅出vue-cli3升级vue-cli4的方法》
《Vue 3.0 Beta 和React 开发者分别杠上了》
《手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件》
《Vue3 尝鲜》
《2020 年,Vue 受欢迎程度是否会超过 React?》
《手把手教你Vue解析pdf(base64)转图片【实践】》
《手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】》
《深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现》
《手把手教你D3.js 实现数据可视化极速上手到Vue应用》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】》
作者:蜗牛老师
转发链接:https://zhuanlan.zhihu.com/p/149033579
相关推荐
- ps下载电脑版官方下载(ps电脑版下载地址)
-
目前在电脑上免费下载PS是不太可能的。主要有以下几个原因。1.AdobePhotoshop(简称PS)是一款商业软件,它需要用户购买和激活许可证才能合法使用。从正规渠道下载并且获得合法授权需要付费...
- 迅猛兔加速器(迅猛兔加速器官网)
-
要下载迅猛兔加速器,首先需要在官网或其他可信的下载平台上搜索并找到该软件。一般情况下,官网提供的下载链接是最稳定和安全的选择。在下载之前,确保您的电脑或手机系统能够支持使用此软件,并检查下载链接的文件...
- 台式电脑怎么重做系统(台式电脑怎么重装系统)
-
你好,电脑系统重装的步骤如下:1.备份数据:在重装系统之前,需要备份电脑中的重要数据,以免数据丢失。2.准备安装介质:需要准备一个安装介质,可以是光盘、U盘或者硬盘分区镜像等。3.设置启动顺序:将电脑...
-
- 电脑无法从u盘启动怎么办(电脑无法从u盘启动解决方法)
-
电脑的进入不了u盘启动的解决方法:一、我们第一步需要确定的是你的u盘在别的电脑上检查一下U盘是否可读,如果可读的话是否成功制作了u盘启动盘了,因为想要启动进入pe的话需要u盘具备启动的功能。 二、如果你检查好自己的u盘已经成功制作了启动盘...
-
2026-01-13 10:05 liuian
- cpu频率越高越好吗(cpu频率越高速度越快吗)
-
高好。CPU的频率是影响CPU的一个重要因素,直观上来说,频率的高低影响了CPU的性能。频率越高,CPU性能越好;不过需要注意的是,CPU的主频表示在CPU内数字脉冲信号震荡的速度,与CPU实际的运算...
- 注册表清理软件(注册表清理软件残留软件)
-
你好!关于注册表清理工具的推荐,以下是几个值得推荐的工具:1.CCleaner:这是一款功能强大的免费清理工具,可以有效地清理注册表、垃圾文件等,使用简单方便。2.WiseRegistryCl...
- 显卡驱动升级有好处吗(显卡驱动升级有什么坏处)
-
显卡的新版本驱动能修改一些游戏,图形显示的BUG,所以新版本的显卡驱动能有效的利用显卡的资源,提高游戏性能。不仅可以修正旧版本中的BUG,而且可以进一步挖掘显卡硬件的功能,使得部分硬件功能得以充分发挥...
- w7旗舰版系统安装无线网卡(win7系统安装无线网卡)
-
要在Windows7中安装无线网卡,请按照以下步骤进行操作:1.检查您的计算机是否已安装无线网卡。您可以通过右键单击“我的电脑”并选择“属性”来查看计算机的硬件设置。如果计算机没有内置无线网卡,则...
- 腾达路由器管理员密码是什么
-
1、旧版本的腾达路由器,默认的用户名和密码都是:admin。?旧版腾达路由器的初始密码是:admin2、目前腾达新推出的无线路由器,在出厂状态下,是没有初始管理员密码的。?新版腾达路由器没有初始密码新...
- 电脑开机只有一个鼠标箭头黑屏
-
解决方法如下:1、同时按“ctrl+shlft+exc”键,调出任务管理器。2、点击任务管理器左下角的“详细信息”。3、然后点击左上角“文件”里的“运行新任务”。4、弹出新窗口,输入“explorer...
- 把vx好友删了想找回聊天记录
-
没有啦,联系人列表里没有了,聊天记录就没有了,无法进行恢复,收不到好友消息微信删除好友时会同时删除与该联系人的聊天记录,不过对方还是有双方的微信聊天记录的,删除好友后将无法发送消息给对方,所以伙伴们在...
- 163邮箱密码正确就是登不上(163邮箱密码一直错误)
-
邮箱不能登录或登录异常的原因有很多种哦,如您浏览器“隐私”或“安全”级别设置过高,或用户名、密码输入不正确、较长时间未登录被冻结等都会导致不能登录或登录异常。请您先检查一下哦。解决无法登录的方法有:...
- 移动硬盘维修费用大概是多少钱
-
芯片不需要多少钱,但数据恢复就另当别论了。。。如果认识人就帮你换个芯片板,要不了多少钱,如果是硬盘盒的芯片板坏了你就乾脆换个盒子,80左右。如果是硬盘芯片坏了,那就不好办了,没人愿意给你换阿。。。但如...
- windows资源管理器停止工作是什么原因
-
1.在进行重装系统之前,可以先检测一下windows资源管理器停止工作的原因是什么。如果是因为电脑的文件太多了,垃圾堆积导致的停止工作,我们就不需要进行重装系统。我们只需要下载一个360卫士或者其他可...
- 一周热门
-
-
飞牛OS入门安装遇到问题,如何解决?
-
如何在 iPhone 和 Android 上恢复已删除的抖音消息
-
Boost高性能并发无锁队列指南:boost::lockfree::queue
-
大模型手册: 保姆级用CherryStudio知识库
-
用什么工具在Win中查看8G大的log文件?
-
如何在 Windows 10 或 11 上通过命令行安装 Node.js 和 NPM
-
威联通NAS安装阿里云盘WebDAV服务并添加到Infuse
-
Trae IDE 如何与 GitHub 无缝对接?
-
idea插件之maven search(工欲善其事,必先利其器)
-
如何修改图片拍摄日期?快速修改图片拍摄日期的6种方法
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
