CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛

vite教程(难以置信)vite从入门到精通,Vite学习:2 完善css加载 和 热更新,

1.vitE作用

上一篇(Vite 学习:1 从零实现一个 no-bundle 构建工具)已经实现了一个简易 my-vite,毫无疑问它还有很多功能、细节需要完善本篇将完善 css加载 和 热更新功能改用 ts为了可读性和易维护,我们将现在 commonJS 规范的代码 改写为 ts: 将所有 vite 文件改为 ts 后缀,下载 ts-node 和 typescript,改用 ts-node 启动项目。

2.vite介绍

这时会报一些错误,主要是类型补充 和 类型包加载问题,根据推断补充 并 下载类型包即可://package.json”scripts”:{“start”:”cd vite && ts-node ./index.ts”

3.vite好用吗

},”devDependencies”:{“@types/connect”:”^3.4.35″,”@types/debug”:”^4.1.7″,”@types/hash-sum”:”^1.0.0″,”@types/parseurl”

4.vite构建

:”^1.3.1″,”@types/ws”:”^8.2.2″,”ts-node”:”^10.4.0″,”typescript”:”^4.5.4″}完善“测试用例”增加子组件,增加css相关代码:// App.vue

5.vite技术

{{ msg }}
span import Child from ./Child.vue; export default { components: { Child }, data() { return { msg: my-vite haah !!! } }, } span { color: red; } div { color: red; }

6.vite配置

// Child.vue

{{ msg }}
span export default { data() { return { msg: my-vite child !!! } }, } @import ./style.css; div { color: green; }

// style.cssspan { color: green; }理论上页面应该长这样:

css加载发现问题重启 my-vite 工具,发现 style.css 文件加载异常 且 child组件颜色不符合预期 :

这是因为css中通过 import 加载了style.css 文件,发起文件请求但我们并没有进行处理首先想到的解决方案是 添加 middleware/css,但 style.css 需要结合 vue style 的 scpoe、module 等属性,如果单独抽离到单独的中间件需要处理很多和 vue 相关的细节。

换个思路想,因为 css 文件由 vue 组件引入,所以可以直接在 middleware/vue 处理 sfcStyle 的时候一起处理 @import 语句处理 @import css 思路为:判断包含了 @import 时,将 postcss 处理后的css 传给 @vue/compiler-sfc compileStyle

// middleware/vue import postcss from postcss; import atImport from postcss-import; const compileSFCStyle = async (res: any, style: SFCStyleBlock, filepath: string, pathname: string, index: any) => { let source: any = style.content const needInlineImport = source.includes(@import) if (needInlineImport) { const postRes = await postcss() .use(atImport()) .process(source, { from: filepath, }); source = postRes.css; } // …compileStyle }

热更新热更新功能在开发模式下给开发者带来了很大的便利:在我们更改本地文件时,可以自动重新渲染页面对应模块 而无需手动刷新,提升开发效率梳理成开发思路:监听文件变化 — 获取文件变化信息 — 通知浏览器 — 浏览器根据信息替换节点/css文件。

转换成问题:如何监听文件变化 — 如何对比文件获取变化信息 — 如何通知浏览器 — 浏览器如何获取信息 如何替换节点/css接下来,我们按照上面的路径 再把问题转换成解决方案及代码:监听文件变化。

在 server 启动时 使用工具包 chokidar 监听 开发文件夹:// vite/index.ts importchokidarfromchokidar;constfileWatcher=chokidar

.watch(cwd,{ignored:[/node_modules/]})fileWatcher.on(change,async(file)=>{// 判断文件更改类型 })判断文件更改类型(为了简化,这里只处理vue文件)

判断更改,就要知道文件之前是什么 现在是什么,所以要做一层缓存那么在什么时候缓存呢?当然是解析文件的时候,也就是vue中间件中在判断文件更改时,还要处理新的文件,所以把vue文件解析 抽成一个有缓存功能的解析函数。

// vite/middleware/vue.ts constcache=newMap();exportconstparseSFC=async(pathname:string)=>{const{filepath

,source}=awaitreadSource(pathname)const{descriptor,errors}=parse(source,{filename:filepath,sourceMap:

true})constprevDescriptor=cache.get(filepath)||{};cache.set(filepath,descriptor)return{filepath,descriptor

,prevDescriptor};}// vueMiddleware 函数内也有相应更改 有了缓存功能,我们就能通过文件路径获取新旧文件内容了,然后通过逐个模块的判断更改类型即可:// vite/index.ts

if(file.endsWith(.vue)){constresourcePath=/+path.relative(cwd,file)const{descriptor,prevDescriptor}=await

parseSFC(resourcePath)// 判断文件更改 }functionisEqual(a:SFCBlock|null,b:SFCBlock|null){if(!a&&!b)returntrue

if(!a||!b)returnfalseif(a.src&&b.src&&a.src===b.src)returntrueif(a.content!==b.content)returnfalseconst

keysA=Object.keys(a.attrs)constkeysB=Object.keys(b.attrs)if(keysA.length!==keysB.length){returnfalse}

returnkeysA.every((key)=>a.attrs[key]===b.attrs[key])}判断 vue 文件更改 可以分为3类:template script css,template script 都只有一块 比较好处理

// vite/index.ts if(!isEqual(descriptor.script,prevDescriptor.script)){send({type:reload,path:resourcePath

})return}if(!isEqual(descriptor.template,prevDescriptor.template)){send({type:rerender,path:resourcePath

})return}css是一个数组,且有scoped属性,处理稍显复杂:constprevStyles=prevDescriptor.styles||[]constnextStyles=descriptor

.styles||[]if(prevStyles.some((s:{scoped:any;})=>s.scoped)!==nextStyles.some((s)=>s.scoped)){send({type

:reload,path:resourcePath})}nextStyles.forEach((_,i)=>{if(!prevStyles[i]||!isEqual(prevStyles[i],nextStyles

[i])){send({type:style-update,path:resourcePath,index:i})}})prevStyles.slice(nextStyles.length).forEach

((_:any,i:number)=>{send({type:style-remove,path:resourcePath,id:`${hash_sum(resourcePath)}-${i+nextStyles

.length}`})})上面用到了 send 函数发送通知,下面小节会讲通知 与 接收通知发送消息:server端 监听到文件变动时,会通过websocket send消息接收消息:浏览器 通过websocket 接收 server 发过来的消息

// vite/index.ts import { WebSocketServer, WebSocket } from ws; const sockets = new Set() wss.on(connection, socket => { // console.log(chalk.green([wss connection])) sockets.add(socket) }) // watcher 为 chokidar.watch 相关代码 抽象而成,第一个函数为监听路径,第二个函数为通知函数 send会调用该函数 watcher(root, (payload: any) => { sockets.forEach(s => s.send(JSON.stringify(payload))) })

到这里 服务端已经能发送消息了,那浏览器如何接收消息呢?这就需要向浏览器中注入接收 ws 信息的相关代码了问题又来了,在什么时机 向哪个文件注入?这里选择在 index.html 中注入(默认 index.html 只有一个 且 比较好判断,个人觉得也可以向 main.js 中注入):。

添加一个hmr中间件,判断为主html时,插入hmrClient 相关代码:exportconsthmrMiddleware=()=>{returnasync(req:any,res:any,next:

any)=>{if(req.url===/__hmrClient){constresult=fs.readFileSync(path.resolve(__dirname,../client/client.js

),utf-8)res.setHeader(Content-Type,application/javascript)res.end(result)}elseif(req.url===/){constscript

=`import “/__hmrClient”`;lethtml=fs.readFileSync(path.resolve(__dirname

,../../index.html),utf-8)consttag=;html=html.replace(tag,script+tag);res.setHeader(Content-Type

,text/html;charset=utf-8)res.end(html)}else{awaitnext()}}}这里需要注意的是 __hmrClient 加载需要在 main.js 文件之后,保证 vue 相关代码已加载

watcher 函数 其实也属于 hmr,也可以整理到该中间件中模块热替换到这里,已经实现了信息通信,所以根据信息热替换就可以了,需要在客户端的注入文件 __hmrClient 中补充 template script 更改需要配合 vue 的 __VUE_HMR_RUNTIME__ ,需要给节点添加 hmr 相关属性:

// vite/middleware/vue.ts compileSFCMain 函数 export default 之前 out+=`\n__script.__hmrId = ${JSON.stringify

(pathname)}`__hmrClient(vite/client/client.js) 用来连接服务端 ws 以及处理 message 信息:var__VUE_HMR_RUNTIME__=window

.__VUE_HMR_RUNTIME__constsocket=newWebSocket(`ws://${location.host}`)socket.addEventListener(message,

({data})=>{const{type,path,id,index}=JSON.parse(data)switch(type){// … 根据 type 处理 }})处理各种类型信息:(path 和 id 规则和上一篇 middleware/vue 的内容是匹配的,可以对照看一下)

caseconnected:console.log(`[vds] connected.`)breakcasereload:import(`${path}?t=${Date.now()}`).then((

m)=>{__VUE_HMR_RUNTIME__.reload(path,m.default)console.log(`[vds] ${path} reloaded. ${JSON.stringify(

m.default)}`)})breakcasererender:import(`${path}?type=template&t=${Date.now()}`).then((m)=>{__VUE_HMR_RUNTIME__

.rerender(path,m.render)console.log(`[vds] ${path} template updated.`)})breakcasestyle-update:console

.log(`[vds] ${path} style${index>0?`#${index}`:“} updated.`)import(`${path}?type=style&index=${index

}&t=${Date.now()}`)breakcasestyle-remove:conststyle=document.getElementById(`vue-style-${id}`)if(style

){style.parentNode?.removeChild(style)}breakcasefull-reload:location.reload()小结vue 中间件中 需要给script添加 __hmrId 标记,且在 parse 时根据文件路径进行缓存

index 中声明 服务端 ws,并通过 ws.on(connection, socket => { // handleSocket }) 收集客户端连接来的sockethmr 中间件中,声明 watcher 文件监听函数,当触发 chokidar.watch(…dirpath).on(change, (file) => { // handleFile }) 时,根据第1步的缓存 处理文件更改类型 为 payload 信息,触发发送函数

index 中调用 watcher,并在发送函数内触发 socket.send(payload)hmr 中间件中,在 index.html 中注入 __hmrClient 文件,使浏览器端 声明socket,以触发第2步的ws.connection,根据在第4步接收的 payload信息 进行节点热替换

发现问题// todo优化// todonode 工具包包名功能备注chokidar监听文件变化wswebsocketpostcss通过js命令转换csspostcss-importpostcss插件:解析css中的@import语句

© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
相关推荐
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容