json返回页面读取data里的值都是object_【一】尤大神都说Vite香,让我来手把手分析Vite原理...
戳藍字"前端優選"關注我們哦!
一.什么是Vite?
法語Vite(輕量,輕快)vite 是一個基于 Vue3單文件組件的非打包開發服務器,它做到了本地快速開發啟動、實現按需編譯、不再等待整個應用編譯完成的功能作用。
對于Vite的描述:針對Vue單頁面組件的無打包開發服務器,可以直接在瀏覽器運行請求的vue文件。
面向現代瀏覽器,Vite基于原生模塊系統 ESModule 實現了按需編譯,而在webpack的開發環境卻很慢,是因為其開發時需要將進行的編譯放到內存中,打包所有文件。
Vite有如此多的優點,那么它是如何實現的呢?
二.Vite的實現原理
我們先來總結下Vite的實現原理:
- Vite在瀏覽器端使用的是 export import 方式導入和導出的模塊;
- vite同時實現了按需加載;
- Vite高度依賴module script特性。
實現過程如下:
- 在 koa 中間件中獲取請求 body;
- 通過 es-module-lexer 解析資源 ast 并拿到 import 內容;
- 判斷 import 的資源是否是 npm 模塊;
- 返回處理后的資源路徑:"vue" => "/@modules/vue"
將要處理的template,script,style等所需依賴以http請求的形式、通過query參數的形式區分,并加載SFC(vue單文件)文件各個模塊內容。
接下來將自己手寫一個Vite來實現相同的功能:
三.手把手實現Vite
1.安裝依賴
實現Vite的環境需要es-module-lexer、koa、koa-static、magic-string模塊搭建:
npm?install?es-module-lexer?koa?koa-static?magic-string這些模塊的功能是:
- koa、koa-static 是vite內部使用的服務框架;
- es-module-lexer 用于分析ES6import語法;
- magic-string 用來實現重寫字符串內容。
2.基本結構搭建
Vite需要搭建一個koa服務:
const?Koa?=?require('koa');function?createServer()?{
????const?app?=?new?Koa();
????const?root?=?process.cwd();
????//?構建上下文對象
????const?context?=?{
????????app,
????????root
????}
????app.use((ctx,?next)?=>?{
????????//?擴展ctx屬性
????????Object.assign(ctx,?context);
????????return?next();
????});
????const?resolvedPlugins?=?[
????];
????//?依次注冊所有插件
????resolvedPlugins.forEach(plugin?=>?plugin(context));
????return?app;
}
createServer().listen(4000);
3.Koa靜態服務配置
用于處理項目中的靜態資源:
const?{serveStaticPlugin}?=?require('./serverPluginServeStatic');const?resolvedPlugins?=?[
?serveStaticPlugin
];
const?path?=?require('path');
function?serveStaticPlugin({app,root}){
????//?以當前根目錄作為靜態目錄
????app.use(require('koa-static')(root));
????//?以public目錄作為根目錄
????app.use(require('koa-static')(path.join(root,'public')))
}
exports.serveStaticPlugin?=?serveStaticPlugin;
目的是讓當前目錄下的文件和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的文件進行攔截
????????if?(ctx.body?&&?ctx.response.is('js'))?{
????????????//?讀取文件中的內容
????????????const?content?=?await?readBody(ctx.body);
????????????//?重寫import中無法識別的路徑
????????????const?r?=?rewriteImports(content);
????????????ctx.body?=?r;
????????}
????});
}
exports.moduleRewritePlugin?=?moduleRewritePlugin;
對js文件中的 import 語法進行路徑的重寫,改寫后的路徑會再次向服務器攔截請求
讀取文件內容:
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?開頭的路徑進行映射
????????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 開頭的路徑解析成對應的真實文件,并返回給瀏覽器
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運行
????const?runtimeDomPath?=?resolvePath('runtime-dom')
????//?核心運行
????const?runtimeCorePath?=?resolvePath('runtime-core')
????//?響應式模塊
????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規范,其他文件均使用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標簽中注入腳本
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');
????????//?獲取文件內容
????????let?{?parse,?compileTemplate?}?=?require(resolveVue(root).compiler);
????????let?{?descriptor?}?=?parse(content);?//?解析文件內容
????????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文件進行解析成如下結果
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,?"計數器:"?+?_toDisplayString(_ctx.state.count),?1?/*?TEXT?*/),
????_createVNode("button",?{
??????onClick:?_cache[1]?||?(_cache[1]?=?$event?=>?(_ctx.click($event)))
????},?"+")
??],?64?/*?STABLE_FRAGMENT?*/))
}
解析后的結果可以直接在createApp方法中進行使用
8.小結
到這里,基本的一個Vite就實現了。總結一下就是:通過Koa服務,實現了按需讀取文件,省掉了打包步驟,以此來提升項目啟動速度,這中間包含了一系列的處理,諸如解析代碼內容、靜態文件讀取、瀏覽器新特性實踐等等。
其實Vite的內容遠不止于此,這里我們實現了非打包開發服務器,那它是如何做到熱更新的呢,下次將手把手實現Vite熱更新原理~
歷史好文推薦:
1、Vue3之——和Vite不得不說的事? ? ? ? ? ? ? ? ? ? ? ??
2、大廠面試算法之斐波那契數列? ? ? ? ? ? ? ? ? ? ? ??
3、2020字節跳動面試題一面解析? ? ? ? ? ? ? ? ? ? ? ? ??
點個在看,大家都看?
總結
以上是生活随笔為你收集整理的json返回页面读取data里的值都是object_【一】尤大神都说Vite香,让我来手把手分析Vite原理...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高考粗心人系列:考生错把第十一中学当成十
- 下一篇: C语言的putpiel函数,C语言gra