Vite本地构建:手写核心原理 - 技术经验 -天下标王
 经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » Vue.js » 查看文章
Vite本地构建:手写核心原理
来源:cnblogs  作者:前端南玖  时间:2024/7/29 9:28:16  对本文有异议

前言

接上篇文章,我们了解到vite的本地构建原理主要是:启动一个 connect 服务器拦截由浏览器请求 ESM的请求。通过请求的路径找到目录下对应的文件做一下编译最终以 ESM的格式返回给浏览器。

基于这个核心思想,我们可以尝试来动手实现一下。

搭建静态服务器

基于koa搭建一个项目:

项目结构如上,服务使用koa搭建,bin指定cli可执行文件的位置

#!/usr/bin/env node
// 代表该脚本使用node执行

const koa = require('koa');
const send = require('koa-send');



const App = new koa()

App.listen(3000, () => {
    console.log('Server is running at http://localhost:3000');
});

这样一个服务就搭建好了,为了方便调试,我们在该工作目录下执行npm link,这样可以将该项目链接支全局的npm,相当于全局安装了这个npm包。

接着我们在任意项目下执行my-vite就能够启动该服务了!

处理根目录html文件

由于上面服务我们没有对任何路由进行处理,当访问http://localhost:3000会发现什么也没有,我门首先需要将项目的模版文件index.html返回给浏览器

const root = process.cwd(); // 获取当前工作目录
console.log('当前工作目录:', process.cwd());

// 静态文件服务区
App.use(async (ctx, next) => {
    // 处理根路径,返回index.html
    await send(ctx, ctx.path, { root: process.cwd() ,index: 'index.html'});
    await next();
});

index.html模版文件如下:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue + TS</title>
  </head>
  <body>
    <div id="app"></div>
    <script>
      window.process = { env: { NODE_ENV: 'development' } };
    </script>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

就是以ESM的方式加载了vue的入口文件main.ts

加完这段代码,我们在vue3项目下执行一下my-vite

来到浏览器看一下此时的情况:

此时浏览器加载了main.ts,该文件如下:它通过import引入了两个模块

import { createApp } from 'vue'
import App from './App.vue'


createApp(App).mount('#app')

按理来说,浏览器此时应该会接着发起请求,去获取这两个模块,但现在却并没有??

此时控制台有个错误:

意思就是加载模块,必须以相对路径才可以(/、./、../)

所以我们现在需要来处理这些模块的加载路径问题

处理模块加载路径

由于三方模块都是直接以模块名来加载的,所以这里我们需要将这些模块的引用路径转换成相对路径。

// 处理模块导入
const importAction = (content) => {
    return content.replace(/(from\s+['"])(?!\.\/)/g, '$1/@modules/')
}

// 修改第三方模块的路径
App.use(async (ctx, next) => {
    // console.log('ctx.path', ctx.type, ctx.path);
    // 处理ts或者js文件
    if (ctx.path.endsWith('.ts') || ctx.path.endsWith('.js')){
        const content = await fileToString(ctx.body); // 获取文件内容
        ctx.type = 'application/javascript'; // 设置响应类型为js
        ctx.body = importAction(content);   // 处理import加载路径
    }
    await next();
});

在这个中间件中,我们使用正则表达式将模块的引用路径替换成了/@modules开头,这样就符合浏览器的引用规则了。

接着再到浏览器中来观察此时的情况:

此时浏览器已经可以发出另外两个请求,分别去加载vue模块以及App.vue组件了。

可以看到vue模块的加载路径已经变成了/@modules开头了,虽然现在该路径还是404,但最起码比起之前我们又往前走一步了。

其实404也很好理解,因为我们的服务现在压根就还没处理这类路径,所以接下来就该处理/@modules这类path并加载模块内容

加载第三方模块

这里我们只需要去拦截刚刚/@modules开头的路径,并找到该路径下的模块的真正位置,最后返回给浏览器就可以了。

// 加载第三方模块
App.use(async (ctx, next) => {
    if (ctx.path.startsWith('/@modules/')) {
        const moduleName = ctx.path.substr(10); // 获取模块名称
        const modulePath = path.join(root, 'node_modules', moduleName); // 获取模块路径
        const package = require(modulePath + '/package.json'); // 获取模块的package.json
        // console.log('modulePath', modulePath);
        ctx.path = path.join('/node_modules', moduleName, package.module); // 重写路径
    } 
    await next();
    
});

我们可以通过读取package.json文件中的module字段,来找到第三方模块的入口文件。

该中间件需要在处理模块加载路径的中间件之前执行

此时再来到浏览器中查看:

可以看到,此时的vue模块已经能够重新加载了,但下面又多加载了四个模块,它们又是从哪来的呢?

可以看到vue模块中又引入了runtime-dom模块,并且它们的加载路径也被转成了/@modules开头,这就是上面提到的加载模块的中间件需要在处理模块加载路径的中间件之前执行,模块加载回来之后又经过了处理加载路径的中间件,所以就相当于递归把模块的路径全都转换成相对路径了

runtime-dom模块又引入了runtime-coreshared模块,而runtime-core模块又引入了reactivity模块,所以会看到上图中这样的一种加载顺序。

模块的加载引入都正确了,但页面还是没又任何渲染内容出现

这是因为此时的App.vue还没经过任何编译处理,浏览器并不能直接识别并执行该文件

所以接下来的重点是需要将App.vue文件编译成浏览器能够执行的javascript内容(render函数)

处理Vue单文件组件

这里我们需要使用Vue的编译模块@vue/compiler-sfc@vue/compiler-dom来对vue文件进行编译处理。

处理script

const content = await fileToString(ctx.body); // 获取文件内容
const { descriptor } = compilerSfc.parse(content); // 解析单文件组件
const compileScript = importAction(
   compilerSfc.compileScript(
     descriptor, 
     { 
       id: descriptor.filename 
     }
   ).content); // 编译script

处理template

const compileRender =importAction(compilerDom.compile(descriptor.template.content, 
                // 编译template, render函数中变量从setup中获取
            {   mode: 'module',
                sourceMap: true,
                filename: path.basename(ctx.path),
                __isScriptSetup: true, // 标记是否是setup
                compatConfig: { MODE: 3 }, // 兼容vue3
            }).code); // 编译template

处理style

let styles = '';
if(descriptor.styles.length){
  console.log('descriptor.styles', descriptor.styles);
  // 处理样式
  styles = descriptor.styles.map((style,index) => {
    return `
             import '${ctx.path}?type=style&index=${index}';
    `
  }).join('\n');

} // 处理样式

这里是通过让它另外发起一次请求来对style进行处理,这样隔离开逻辑能够更清晰

处理样式的请求

在中间件中通过拦截typestyle的请求来进行处理

if (ctx.query.type === 'style') {
  // 处理样式
  const styleBlock = descriptor.styles[ctx.query.index];
  console.log('styleBlock', styleBlock);
  ctx.type = 'application/javascript';
  ctx.body = `
            const _style = (css) => {
                const __style = document.createElement('style');
                __style.type = 'text/css';
                __style.innerHTML = css;
                document.head.appendChild(__style);
                }
                _style(${JSON.stringify(styleBlock.content)});
                export default _style;
            `;
}

最后验证

总结

在深入探索了vite的工作流程之后,你可能会发现,尽管从概念上看似简单,但vite背后的实现却相当复杂且精妙。我们刚刚通过走一遍其核心流程,对vite如何加载模块、解析和编译文件有了初步的认识。然而,这仅仅是冰山一角。

总的来说,vite的工作原理虽然可以通过一个简化的示例来理解,但其真正的强大和复杂性远不止于此。如果对vite的深入工作原理感兴趣,可以去深入阅读它的源码,在那里我们能够学习到更多知识。

原文链接:https://www.cnblogs.com/songyao666/p/18328452

 友情链接:直通硅谷  点职佳

相关内容推荐

如何优化网站pj金手指霸屏惠州seo网站优化什么价格网站优化二级域名优缺点如何判断网站是否利于优化牟平区网站优化公司庐阳区搜狗网站优化企业网站优化哪家有实力巩义网站推广优化怎样收费嵊州网站优化设计是什么网站seo如何优化手机端百度首页网站优化排名开关网站seo优化费用网站优化工作室名字设计网站优化检索工具日本优化网站网站源码如何优化怎么优化公司网站论文麻涌网站关键词优化费用崂山网站优化价格泰安正规网站优化费用网站seo优化最重要的是什么网站排名优化 必宙r斯方法湖南网站优化怎么收费高邮网站搜索优化网站优化推广建议网站优化实时更新有用吗遵义县网站优化公司网站建设优化服务渠道优化网站方法慧择火29星分享网站seo优化白银网站快速优化哪个网站优化服务好需要优化的网站有哪些问题涉县seo网站优化隆化网站优化肇庆企业网站关键词优化工具网站优化与世界知名企业同行扬州市优化网站网站整站优化相信它易速达天津网站优化体验庆阳网站优化推广哪个公司好搜狐网的网站优化分析网站搜索排名优化哪家价格便宜舟山网站seo优化推广百家号网站优化优化网站一站式服务崇州网站seo优化服务胶州网站单词优化敦煌关键词网站优化网站建设怎样通过外链进行优化简述网站优化的内容陕西网站优化排名服务邵阳网站优化公司有哪些美容养生网站优化比较好宝典网站排名优化宝山区百度网站优化费用郑州百度网站优化系统周口网站seo优化报价google优化网站排名怎么把网站名字优化到首页宝塔网站的优化月嫂行业网站优化策划网站优化中页面收录率太低网站排名优化哪家公司比较好昭通网站优化开发自助建站网站优化便宜丹东正规的seo优化网站价格襄县网站优化首页郑州360网站推广优化新乡百度网站优化哪家便宜网站优化实验结论武进区网站优化方式柘城个性化网站关键词优化价格湖南省网站优化如何把网站优化做得更好汽车行业网站优化比较好从化seo网站优化推广教程分享网站seo优化越秀区网站优化排名报价揭阳公司网站关键词优化排名陕西省网站优化工具天津网站优化方案邵阳网站首页优化海南优化网站品牌优化seo网站黄石网站运营优化系统新站网站优化系统鄂州市网站关键词优化怎么做网站导航优化的描述错误网站seo优化加盟费用天津哪个网站优化服务好廊坊百度网站快速优化成都专业的珠宝行业网站优化广州本地网站优化培训网站的内链优化是什么淮南福州网站优化碑林区网站优化泰安网站优化企业绵阳外贸网站优化甘肃网站优化好不好seo网站优化解决方案上虞网站优化怎么选河南百度网站优化外包网站如何利用长尾关键词优化做神马网站优化排名软铁岭网站排名优化网站关键词优化惫云速捷豪杰恩施网站优化形式网站的优化询问j火18星来广东全网推广网站优化效果优化网站建设人员组成机械行业网站优化方法汇咖网站优化张裕网站营销优化诊断训练茌平网站优化小额贷款网站优化策划商城网站优化计划浦东网站营销优化网站优化霸屏推广毕节网站优化推广seo网站排名优化哪个公司做得好汉中网站优化哪家好许昌企业seo网站优化工具杭州网站优化在哪里网站关键词排名优化公司价格教育网站优化最好的方法怎么做优化网站新乡网站优化哪家有名网站优化公司哪家靠谱吴中网站优化收费优化网站软件辶要金手指专业梅州网站优化出售台州外贸网站优化遵义县网站优化公司网站制作优化qt宀云速捷聊城专业网站优化费用网站排名优化收费剑河县网站优化公司成都seo怎么优化网站济宁网站seo优化报价济宁网站优化哪家便宜网站排名优化顺序上海官网网站优化平台网站推荐优化方法新建网站优化教程网站如何优化在首页网站优化效果和域名有关系吗网站做优化顶火16星网站不同层次目录优化湖北襄樊网站优化网络推广方式李沧网站排名优化电器建材网站SEO优化托管古冶网站优化温州优化网站多少钱网站大数据优化巩义网站自然优化收费低网站首页优化公司哪家效果好港北区网站seo优化排名莱芜加盟网站优化公司潞城网站如何做优化长沙个人网站优化萧县pc网站优化义安区网站seo优化排名张家港网站搜索优化企业杭州做seo网站优化费用高么杭州,网站优化怎样百度优化自己网站潜山网站优化公司盛泽网站优化费用罗湖网站优化公司有哪些金昌网站优化推广联系方式魏县网站优化排名新津互联网网站优化网站优化管理月薪多少钱华为网站搜索引擎优化电商网站主题模型优化霍州门户网站seo优化什么叫外贸网站seo优化网站优化没流量关于网站seo优化你知道多少广州金属制品网站seo优化佛山网站优化哪家快深圳网站建设优化公司西城网站优化推广晴隆网站seo优化公司罗湖网站排名优化价格php网站打卡速度优化马龙网站搜索优化价格福州网站优化点网站优化过程及注意事项网站优化 扣费 黑幕雁塔区网站seo优化排名做一个简单的网站优化加盟行业网站优化宣传网站诊断及优化方案论文曹妃甸网站优化淘宝上做的网站可以优化吗某网站搜索引擎优化前言昆明 网站优化网站建设收录排名优化

合作伙伴

天下标王

龙岗网络公司
深圳网站优化
龙岗网站建设
坪山网站建设
百度标王推广
天下网标王
SEO优化按天计费
SEO按天计费系统