json返回页面读取data里的值都是object_【一】尤大神都说Vite香,让我来手把手分析Vite原理...
戳藍(lán)字"前端優(yōu)選"關(guān)注我們哦!
一.什么是Vite?
法語Vite(輕量,輕快)vite 是一個(gè)基于 Vue3單文件組件的非打包開發(fā)服務(wù)器,它做到了本地快速開發(fā)啟動(dòng)、實(shí)現(xiàn)按需編譯、不再等待整個(gè)應(yīng)用編譯完成的功能作用。
對于Vite的描述:針對Vue單頁面組件的無打包開發(fā)服務(wù)器,可以直接在瀏覽器運(yùn)行請求的vue文件。
面向現(xiàn)代瀏覽器,Vite基于原生模塊系統(tǒng) ESModule 實(shí)現(xiàn)了按需編譯,而在webpack的開發(fā)環(huán)境卻很慢,是因?yàn)槠溟_發(fā)時(shí)需要將進(jìn)行的編譯放到內(nèi)存中,打包所有文件。
Vite有如此多的優(yōu)點(diǎn),那么它是如何實(shí)現(xiàn)的呢?
二.Vite的實(shí)現(xiàn)原理
我們先來總結(jié)下Vite的實(shí)現(xiàn)原理:
- Vite在瀏覽器端使用的是 export import 方式導(dǎo)入和導(dǎo)出的模塊;
- vite同時(shí)實(shí)現(xiàn)了按需加載;
- Vite高度依賴module script特性。
實(shí)現(xiàn)過程如下:
- 在 koa 中間件中獲取請求 body;
- 通過 es-module-lexer 解析資源 ast 并拿到 import 內(nèi)容;
- 判斷 import 的資源是否是 npm 模塊;
- 返回處理后的資源路徑:"vue" => "/@modules/vue"
將要處理的template,script,style等所需依賴以http請求的形式、通過query參數(shù)的形式區(qū)分,并加載SFC(vue單文件)文件各個(gè)模塊內(nèi)容。
接下來將自己手寫一個(gè)Vite來實(shí)現(xiàn)相同的功能:
三.手把手實(shí)現(xiàn)Vite
1.安裝依賴
實(shí)現(xiàn)Vite的環(huán)境需要es-module-lexer、koa、koa-static、magic-string模塊搭建:
npm?install?es-module-lexer?koa?koa-static?magic-string這些模塊的功能是:
- koa、koa-static 是vite內(nèi)部使用的服務(wù)框架;
- es-module-lexer 用于分析ES6import語法;
- magic-string 用來實(shí)現(xiàn)重寫字符串內(nèi)容。
2.基本結(jié)構(gòu)搭建
Vite需要搭建一個(gè)koa服務(wù):
const?Koa?=?require('koa');function?createServer()?{
????const?app?=?new?Koa();
????const?root?=?process.cwd();
????//?構(gòu)建上下文對象
????const?context?=?{
????????app,
????????root
????}
????app.use((ctx,?next)?=>?{
????????//?擴(kuò)展ctx屬性
????????Object.assign(ctx,?context);
????????return?next();
????});
????const?resolvedPlugins?=?[
????];
????//?依次注冊所有插件
????resolvedPlugins.forEach(plugin?=>?plugin(context));
????return?app;
}
createServer().listen(4000);
3.Koa靜態(tài)服務(wù)配置
用于處理項(xiàng)目中的靜態(tài)資源:
const?{serveStaticPlugin}?=?require('./serverPluginServeStatic');const?resolvedPlugins?=?[
?serveStaticPlugin
];
const?path?=?require('path');
function?serveStaticPlugin({app,root}){
????//?以當(dāng)前根目錄作為靜態(tài)目錄
????app.use(require('koa-static')(root));
????//?以public目錄作為根目錄
????app.use(require('koa-static')(path.join(root,'public')))
}
exports.serveStaticPlugin?=?serveStaticPlugin;
目的是讓當(dāng)前目錄下的文件和public目錄下的文件可以直接被訪問
4.重寫模塊路徑
const?{moduleRewritePlugin}?=?require('./serverPluginModuleRewrite');const?resolvedPlugins?=?[
????moduleRewritePlugin,
????serveStaticPlugin
];
const?{?readBody?}?=?require("./utils");
const?{?parse?}?=?require('es-module-lexer');
const?MagicString?=?require('magic-string');
function?rewriteImports(source)?{
????let?imports?=?parse(source)[0];
????const?magicString?=?new?MagicString(source);
????if?(imports.length)?{
????????for?(let?i?=?0;?i?????????????const?{?s,?e?}?=?imports[i];
????????????let?id?=?source.substring(s,?e);
????????????if?(/^[^\/\.]/.test(id))?{
????????????????id?=?`/@modules/${id}`;
????????????????//?修改路徑增加?/@modules?前綴
????????????????magicString.overwrite(s,?e,?id);
????????????}
????????}
????}
????return?magicString.toString();
}
function?moduleRewritePlugin({?app,?root?})?{
????app.use(async?(ctx,?next)?=>?{
????????await?next();
????????//?對類型是js的文件進(jìn)行攔截
????????if?(ctx.body?&&?ctx.response.is('js'))?{
????????????//?讀取文件中的內(nèi)容
????????????const?content?=?await?readBody(ctx.body);
????????????//?重寫import中無法識別的路徑
????????????const?r?=?rewriteImports(content);
????????????ctx.body?=?r;
????????}
????});
}
exports.moduleRewritePlugin?=?moduleRewritePlugin;
對js文件中的 import 語法進(jìn)行路徑的重寫,改寫后的路徑會(huì)再次向服務(wù)器攔截請求
讀取文件內(nèi)容:
const?{?Readable?}?=?require('stream')async?function?readBody(stream)?{
????if?(stream?instanceof?Readable)?{?//?
????????return?new?Promise((resolve,?reject)?=>?{
????????????let?res?=?'';
????????????stream
????????????????.on('data',?(chunk)?=>?res?+=?chunk)
????????????????.on('end',?()?=>?resolve(res));
????????})
????}else{
????????return?stream.toString()
????}
}
exports.readBody?=?readBody
5.解析 /@modules 文件
const?{moduleResolvePlugin}?=?require('./serverPluginModuleResolve');const?resolvedPlugins?=?[
????moduleRewritePlugin,
????moduleResolvePlugin,
????serveStaticPlugin
];
const?fs?=?require('fs').promises;
const?path?=?require('path');
const?{?resolve?}?=?require('path');
const?moduleRE?=?/^\/@modules\//;?
const?{resolveVue}?=?require('./utils')
function?moduleResolvePlugin({?app,?root?})?{
????const?vueResolved?=?resolveVue(root)
????app.use(async?(ctx,?next)?=>?{
????????//?對?/@modules?開頭的路徑進(jìn)行映射
????????if(!moduleRE.test(ctx.path)){?
????????????return?next();
????????}
????????//?去掉?/@modules/路徑
????????const?id?=?ctx.path.replace(moduleRE,'');
????????ctx.type?=?'js';
????????const?content?=?await?fs.readFile(vueResolved[id],'utf8');
????????ctx.body?=?content
????});
}
exports.moduleResolvePlugin?=?moduleResolvePlugin;
將/@modules 開頭的路徑解析成對應(yīng)的真實(shí)文件,并返回給瀏覽器
const?path?=?require('path');function?resolveVue(root)?{
????const?compilerPkgPath?=?path.resolve(root,?'node_modules',?'@vue/compiler-sfc/package.json');
????const?compilerPkg?=?require(compilerPkgPath);
????//?編譯模塊的路徑??node中編譯
????const?compilerPath?=?path.join(path.dirname(compilerPkgPath),?compilerPkg.main);
????const?resolvePath?=?(name)?=>?path.resolve(root,?'node_modules',?`@vue/${name}/dist/${name}.esm-bundler.js`);
????//?dom運(yùn)行
????const?runtimeDomPath?=?resolvePath('runtime-dom')
????//?核心運(yùn)行
????const?runtimeCorePath?=?resolvePath('runtime-core')
????//?響應(yīng)式模塊
????const?reactivityPath?=?resolvePath('reactivity')
????//?共享模塊
????const?sharedPath?=?resolvePath('shared')
????return?{
????????vue:?runtimeDomPath,
????????'@vue/runtime-dom':?runtimeDomPath,
????????'@vue/runtime-core':?runtimeCorePath,
????????'@vue/reactivity':?reactivityPath,
????????'@vue/shared':?sharedPath,
????????compiler:?compilerPath,
????}
}
編譯的模塊使用commonjs規(guī)范,其他文件均使用es6模塊
6.處理process的問題
瀏覽器中并沒有process變量,所以我們需要在html中注入process變量
const?{htmlRewritePlugin}?=?require('./serverPluginHtml');const?resolvedPlugins?=?[
????htmlRewritePlugin,
????moduleRewritePlugin,
????moduleResolvePlugin,
????serveStaticPlugin
];
const?{?readBody?}?=?require("./utils");
function?htmlRewritePlugin({root,app}){
????const?devInjection?=?`
????`
????app.use(async(ctx,next)=>{
????????await?next();
????????if(ctx.response.is('html')){
????????????const?html?=?await?readBody(ctx.body);
????????????ctx.body?=?html.replace(//,`$&${devInjection}`)
????????}
????})
}
exports.htmlRewritePlugin?=?htmlRewritePlugin
在html的head標(biāo)簽中注入腳本
7.處理.vue后綴文件
const?{vuePlugin}?=?require('./serverPluginVue')const?resolvedPlugins?=?[
????htmlRewritePlugin,
????moduleRewritePlugin,
????moduleResolvePlugin,
????vuePlugin,
????serveStaticPlugin
];
const?path?=?require('path');
const?fs?=?require('fs').promises;
const?{?resolveVue?}?=?require('./utils');
const?defaultExportRE?=?/((?:^|\n|;)\s*)export?default/
function?vuePlugin({?app,?root?})?{
????app.use(async?(ctx,?next)?=>?{
????????if?(!ctx.path.endsWith('.vue'))?{
????????????return?next();
????????}
????????//?vue文件處理
????????const?filePath?=?path.join(root,?ctx.path);
????????const?content?=?await?fs.readFile(filePath,?'utf8');
????????//?獲取文件內(nèi)容
????????let?{?parse,?compileTemplate?}?=?require(resolveVue(root).compiler);
????????let?{?descriptor?}?=?parse(content);?//?解析文件內(nèi)容
????????if?(!ctx.query.type)?{
????????????let?code?=?``;
????????????if?(descriptor.script)?{
????????????????let?content?=?descriptor.script.content;
????????????????let?replaced?=?content.replace(defaultExportRE,?'$1const?__script?=');
????????????????code?+=?replaced;
????????????}
????????????if?(descriptor.template)?{
????????????????const?templateRequest?=?ctx.path?+?`?type=template`
????????????????code?+=?`\nimport?{?render?as?__render?}?from?${JSON.stringify(
????????????????????templateRequest
????????????????)}`;
????????????????code?+=?`\n__script.render?=?__render`
????????????}
????????????ctx.type?=?'js'
????????????code?+=?`\nexport?default?__script`;
????????????ctx.body?=?code;
????????}
????????if?(ctx.query.type?==?'template')?{
????????????ctx.type?=?'js';
????????????let?content?=?descriptor.template.content;
????????????const?{?code?}?=?compileTemplate({?source:?content?});
????????????ctx.body?=?code;
????????}
????})
}
exports.vuePlugin?=?vuePlugin;
在后端將.vue文件進(jìn)行解析成如下結(jié)果
import?{reactive}?from?'/@modules/vue';const?__script?=?{
??setup()?{
????let?state?=?reactive({count:0});
????function?click(){
??????state.count+=?1
????}
????return?{
??????state,
??????click
????}
??}
}
import?{?render?as?__render?}?from?"/src/App.vue?type=template"
__script.render?=?__render
export?default?__script
import?{?toDisplayString?as?_toDisplayString,?createVNode?as?_createVNode,?Fragment?as?_Fragment,?openBlock?as?_openBlock,?createBlock?as?_createBlock?}?from?"/@modules/vue"
export?function?render(_ctx,?_cache)?{
??return?(_openBlock(),?_createBlock(_Fragment,?null,?[
????_createVNode("div",?null,?"計(jì)數(shù)器:"?+?_toDisplayString(_ctx.state.count),?1?/*?TEXT?*/),
????_createVNode("button",?{
??????onClick:?_cache[1]?||?(_cache[1]?=?$event?=>?(_ctx.click($event)))
????},?"+")
??],?64?/*?STABLE_FRAGMENT?*/))
}
解析后的結(jié)果可以直接在createApp方法中進(jìn)行使用
8.小結(jié)
到這里,基本的一個(gè)Vite就實(shí)現(xiàn)了。總結(jié)一下就是:通過Koa服務(wù),實(shí)現(xiàn)了按需讀取文件,省掉了打包步驟,以此來提升項(xiàng)目啟動(dòng)速度,這中間包含了一系列的處理,諸如解析代碼內(nèi)容、靜態(tài)文件讀取、瀏覽器新特性實(shí)踐等等。
其實(shí)Vite的內(nèi)容遠(yuǎn)不止于此,這里我們實(shí)現(xiàn)了非打包開發(fā)服務(wù)器,那它是如何做到熱更新的呢,下次將手把手實(shí)現(xiàn)Vite熱更新原理~
歷史好文推薦:
1、Vue3之——和Vite不得不說的事? ? ? ? ? ? ? ? ? ? ? ??
2、大廠面試算法之斐波那契數(shù)列? ? ? ? ? ? ? ? ? ? ? ??
3、2020字節(jié)跳動(dòng)面試題一面解析? ? ? ? ? ? ? ? ? ? ? ? ??
點(diǎn)個(gè)在看,大家都看?
總結(jié)
以上是生活随笔為你收集整理的json返回页面读取data里的值都是object_【一】尤大神都说Vite香,让我来手把手分析Vite原理...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高考粗心人系列:考生错把第十一中学当成十
- 下一篇: C语言的putpiel函数,C语言gra