大前端備戰(zhàn)2021年,使用vite構(gòu)建React !
寫(xiě)在開(kāi)頭
由于 vite這個(gè)構(gòu)建工具被用在了vue3上門(mén),而且它的構(gòu)建思路我覺(jué)得優(yōu)于webpack,底層也是使用了esbuild,性能上更優(yōu)那么為了照顧一些小伙伴之前沒(méi)有學(xué)習(xí)過(guò) vite的,我們先來(lái)看看什么是vite
什么是vite
Vite,一個(gè)基于瀏覽器原生 ES imports 的開(kāi)發(fā)服務(wù)器。利用瀏覽器去解析 imports,在服務(wù)器端按需編譯返回,完全跳過(guò)了打包這個(gè)概念,服務(wù)器隨起隨用,支持熱更新,而且熱更新的速度不會(huì)隨著模塊增多而變慢。針對(duì)生產(chǎn)環(huán)境則可以把同一份代碼用 rollup 打包 vite的天然優(yōu)勢(shì):快速冷啟動(dòng)服務(wù)器 即時(shí)熱模塊更換(HMR) 真正的按需編譯
vite工作原理
當(dāng)聲明一個(gè) script 標(biāo)簽類(lèi)型為 module 時(shí)
如:??
瀏覽器就會(huì)像服務(wù)器發(fā)起一個(gè)GET http://localhost:3000/src/main.js請(qǐng)求main.js文件:
//?/src/main.js:
import?{?createApp?}?from?'vue'
import?App?from?'./App.vue'
createApp(App).mount('#app')
瀏覽器請(qǐng)求到了main.js文件,檢測(cè)到內(nèi)部含有import引入的包,又會(huì)對(duì)其內(nèi)部的 import 引用發(fā)起 HTTP 請(qǐng)求獲取模塊的內(nèi)容文件 如: GET http://localhost:3000/@modules/vue.js如: GET http://localhost:3000/src/App.vue其Vite 的主要功能就是通過(guò)劫持瀏覽器的這些請(qǐng)求,并在后端進(jìn)行相應(yīng)的處理將項(xiàng)目中使用的文件通過(guò)簡(jiǎn)單的分解與整合,然后再返回給瀏覽器渲染頁(yè)面,vite整個(gè)過(guò)程中沒(méi)有對(duì)文件進(jìn)行打包編譯,所以其運(yùn)行速度比原始的webpack開(kāi)發(fā)編譯速度快出許多
簡(jiǎn)單實(shí)現(xiàn)vite
由于代碼量有一些大,我就不自己去寫(xiě)了,直接拿了別人的代碼過(guò)來(lái),原文地址是:
https://juejin.cn/post/6898116372887240712
首先是koa啟動(dòng)監(jiān)聽(tīng)端口,用于訪問(wèn)熱更新服務(wù)
function?createServer()?{
?let?app?=?new?Koa()
?const?context?=?{?????//?直接創(chuàng)建一個(gè)上下文?來(lái)給不同的插件共享功能
??app,
??root:?process.cwd()?//執(zhí)行node命令的那個(gè)命令路徑
?}
?//?運(yùn)行koa中間件(就是我們的vite插件)
?resolvePlugin.forEach(plugin?=>?plugin(context))
?return?app
}
createServer().listen(4000,?()?=>?{
})
編寫(xiě)對(duì)應(yīng)插件處理 首先處理模塊的引用,因?yàn)闉g覽器只有相對(duì)路徑和絕對(duì)路徑
這里
readBody其實(shí)就是一個(gè)讀取文件流的方法,封裝過(guò)而已,看成普通的讀取流方法即可
koa中間件處理
首先處理重寫(xiě)路徑,因?yàn)闉g覽器只有絕對(duì)路徑和相對(duì)路徑
??app.use(async?(ctx,?next)?=>?{
???await?next();?//?靜態(tài)服務(wù)
???//?默認(rèn)會(huì)先執(zhí)行?靜態(tài)服務(wù)中間件?會(huì)將結(jié)果放到?ctx.body
???//?需要將流轉(zhuǎn)換成字符串?,?只需要處理js中的引用問(wèn)題
???if?(ctx.body?&&?ctx.response.is('js'))?{
????let?r?=?await?readBody(ctx.body);?//?vue?=>?/@modules
????const?result?=?rewriteImports(r);
????ctx.body?=?result;
???}
??})
?},
重寫(xiě)完了路徑后,需要攔截 .vue文件和帶@module(重寫(xiě)路徑之前就是node_modules里面的文件)
?//?2.?攔截含有/@modules/vue的請(qǐng)求,?去node_modules引入對(duì)應(yīng)的模塊并返回
?({?app,?root?})?=>?{
??const?reg?=?/^\/@modules\//
??app.use(async?(ctx,?next)?=>?{
???//?如果沒(méi)有匹配到?/@modules/vue?就往下執(zhí)行即可
???if?(!reg.test(ctx.path))?{
????return?next();
???}
???const?id?=?ctx.path.replace(reg,?'');
???let?mapping?=?{
????vue:?path.resolve(root,?'node_modules',?'@vue/runtime-dom/dist/runtime-dom.esm-browser.js'),
???}
???const?content?=?await?fs.readFile(mapping[id],?'utf8');
???ctx.type?=?'js';?//?返回的文件是js
???ctx.body?=?content;
??})
?},
當(dāng)解析處理完路徑后,我們需要解析vue的模板文件,(如果是react的jsx代碼,同理)
?//?3.?解析.vue文件
?({?app,?root?})?=>?{
??app.use(async?(ctx,?next)?=>?{
???if?(!ctx.path.endsWith('.vue'))?{
????return?next();
???}
???const?filePath?=?path.join(root,?ctx.path);
???const?content?=?await?fs.readFile(filePath,?'utf8');
???//?引入.vue文件解析模板
???const?{?compileTemplate,?parse?}?=?require(path.resolve(root,?'node_modules',?'@vue/compiler-sfc/dist/compiler-sfc.cjs'))
???let?{?descriptor?}?=?parse(content);
???if?(!ctx.query.type)?{
????//App.vue
????let?code?=?''
????if?(descriptor.script)?{
?????let?content?=?descriptor.script.content;
?????code?+=?content.replace(/((?:^|\n|;)\s*)export?default/,?'$1const?__script=');
????}
????if?(descriptor.template)?{
?????const?requestPath?=?ctx.path?+?`?type=template`;
?????code?+=?`\nimport?{?render?as?__render?}?from?"${requestPath}"`;
?????code?+=?`\n__script.render?=?__render`
????}
????code?+=?`\nexport?default?__script`
????ctx.type?=?'js';
????ctx.body?=?code
???}
???if?(ctx.query.type?==?'template')?{
????ctx.type?=?'js';
????let?content?=?descriptor.template.content
????const?{?code?}?=?compileTemplate({?source:?content?});?//?將app.vue中的模板?轉(zhuǎn)換成render函數(shù)
????ctx.body?=?code;
???}
??})
?},
?//?4.?靜態(tài)服務(wù)插件?實(shí)現(xiàn)可以返回文件的功能
?({?app,?root?})?=>?{
??app.use(static(root))
??app.use(static(path.resolve(root,?'public')))
?}
]
function?createServer()?{
?let?app?=?new?Koa()
?const?context?=?{?????//?直接創(chuàng)建一個(gè)上下文?來(lái)給不同的插件共享功能
??app,
??root:?process.cwd()?//?C:\Users\...\my-vite-vue3
?}
?//?運(yùn)行中間件
?resolvePlugin.forEach(plugin?=>?plugin(context))
?return?app
}
下面是兩個(gè)工具函數(shù):一個(gè)是流的讀取,一個(gè)是重寫(xiě)路徑的函數(shù)
//讀取body方法
async?function?readBody(stream)?{
?if?(stream?instanceof?Readable)?{
??return?new?Promise((resolve)?=>?{
???let?res?=?''
???stream.on('data',?function?(chunk)?{
????res?+=?chunk
???});
???stream.on('end',?function?()?{
????resolve(res)
???})
??})
?}?else?{
??return?stream;
?}
}
重寫(xiě)路徑中間件
const?resolvePlugin?=?[
?//?1.?重寫(xiě)引入模塊路徑前面加上/@modules/vue,?重寫(xiě)后瀏覽器會(huì)再次發(fā)送請(qǐng)求
?({?app,?root?})?=>?{
??function?rewriteImports(source)?{
???let?imports?=?parse(source)[0];
???let?ms?=?new?MagicString(source);
???if?(imports.length?>?0)?{
????for?(let?i?=?0;?i??????let?{?s,?e?}?=?imports[i];
?????let?id?=?source.slice(s,?e);?//?應(yīng)用的標(biāo)識(shí)?vue??./App.vue
?????//?不是./?或者?/
?????if?(/^[^\/\.]/.test(id))?{
??????id?=?`/@modules/${id}`;
??????ms.overwrite(s,?e,?id)
?????}
????}
???}
???return?ms.toString();
??}
??
這樣一個(gè)簡(jiǎn)單的vite就完成了
開(kāi)始在react中使用
vite算是一個(gè)新的技術(shù),而且在國(guó)內(nèi)目前不夠流行,為了避免踩坑,我們直接采用官方推薦的模板生成
npm?init?vite-app?--template?react
生成模板完成后,執(zhí)行命令啟動(dòng)項(xiàng)目
yarn
yarn?dev
這樣一個(gè) react的項(xiàng)目就搭建好了,默認(rèn)使用的是17.0.0版本的react,這樣createElement方法再也不用從react里面導(dǎo)出了,我想這樣jsx風(fēng)格代碼也會(huì)更容易被遷移到其他框架項(xiàng)目中
??"dependencies":?{
????"react":?"^17.0.0",
????"react-dom":?"^17.0.0"
??},
??"devDependencies":?{
????"vite":?"^1.0.0-rc.13",
????"vite-plugin-react":?"^4.0.0"
??}
這個(gè)模板生成的是自帶熱更新的,相對(duì)比較簡(jiǎn)單,如果是有特殊需求,可以使用更多的plugin,在vite.config.js中設(shè)置 默認(rèn)的配置
//?@ts-check
import?reactPlugin?from?'vite-plugin-react'
/**
?*?@type?{?import('vite').UserConfig?}
?*/
const?config?=?{
??jsx:?'react',
??plugins:?[reactPlugin]
}
export?default?config
寫(xiě)在最后
本文更多是在講 vite的實(shí)現(xiàn)原理,目前我還沒(méi)有把它使用在生產(chǎn)環(huán)境中在我看來(lái), vite如果生態(tài)能發(fā)展起來(lái),可能我們就用不到wepback6這個(gè)版本了(當(dāng)然未來(lái)不可猜測(cè))通過(guò)閱讀本文,你肯定能清楚了解vite的原理和react構(gòu)建使用了,感覺(jué)不錯(cuò)的話,幫我點(diǎn)個(gè) 贊/在看,關(guān)注一下【前端巔峰】公眾號(hào)吧
參考資料:
https://juejin.cn/post/6898116372887240712
https://juejin.cn/post/6844904146915573773
https://github.com/vitejs/vite
評(píng)論
圖片
表情
