首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Vite本地构建:手写核心原理

编程知识
2024年07月28日 16:00

前言

接上篇文章,我们了解到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></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的深入工作原理感兴趣,可以去深入阅读它的源码,在那里我们能够学习到更多知识。

From:https://www.cnblogs.com/songyao666/p/18328452
本文地址: http://shuzixingkong.net/article/518
0评论
提交 加载更多评论
其他文章 基于Hive的大数据分析系统
1.概述 在构建大数据分析系统的过程中,我们面对着海量、多源的数据挑战,如何有效地解决这些零散数据的分析问题一直是大数据领域研究的核心关注点。大数据分析处理平台作为应对这一挑战的利器,致力于整合当前主流的各种大数据处理分析框架和工具,以实现对数据的全面挖掘和深入分析。本篇博客笔者将为大家介绍如何构建
基于Hive的大数据分析系统 基于Hive的大数据分析系统 基于Hive的大数据分析系统
XCode 编译 PAG 源码
最近工作中要使用PAG替换Lottie,为了方便阅读源码,使用XCode对其源码进行了编译。 1 下载源码 编译源码首先要下载源码,有关PAG源码可直接到github上下载。 2 添加相关依赖 下载源码之后,进入到PAG项目根目录,执行如下脚本: ./sync_deps.sh 3 构建 iOS PA
XCode 编译 PAG  源码 XCode 编译 PAG  源码 XCode 编译 PAG  源码
七天.NET 8操作SQLite入门到实战 - 第七天Blazor学生管理页面编写和接口对接(3)
前言 本章节我们的主要内容是完善Blazor学生管理页面的编写和接口对接。 七天.NET 8 操作 SQLite 入门到实战详细教程 第一天 SQLite 简介 第二天 在 Windows 上配置 SQLite 环境 第三天 SQLite 快速入门 第四天 EasySQLite 前后端项目框架搭建
七天.NET 8操作SQLite入门到实战 - 第七天Blazor学生管理页面编写和接口对接(3) 七天.NET 8操作SQLite入门到实战 - 第七天Blazor学生管理页面编写和接口对接(3) 七天.NET 8操作SQLite入门到实战 - 第七天Blazor学生管理页面编写和接口对接(3)
马斯克: 教育是解决问题, 而不是教工具
1. 马斯克: 教育是解决问题, 而不是教工具 [3,4,6] 2. 老天爷的教的方法 我理解这就跟游戏一样, 从环境中持续获得反馈, 体会乐趣, 修正不足, 而不是在工具方法和原理中消磨意志力. 实践中学习是我们天生的 比如我们学说话,不是先学拼音,学走路,也不先学力学原理,而是直接模仿,边说边学
马斯克: 教育是解决问题, 而不是教工具 马斯克: 教育是解决问题, 而不是教工具
STM32F103 SPI详解及示例代码
SPI是嵌入式中使用比较广泛的协议之一,本文从该协议的原理入手对其进行了详细介绍,并结合STM32F103ZET主控芯片对其进行了说明,最后给出了两个实例代码demo供大家做参考。
STM32F103 SPI详解及示例代码 STM32F103 SPI详解及示例代码 STM32F103 SPI详解及示例代码
简单网页制作
网页效果预览 这个网页包含图片,链接,字体设置,表格等 初学者最好手敲代码,更快熟悉元素和结构 完整的代码放在最后了 一:代码怎么变成网页 之前我们安装了xampp,启动xampp里的apache及sql 在xampp下找到htdocs目录 新建文件夹改名后缀为.php即可 将新建文件用记事本打开
简单网页制作 简单网页制作 简单网页制作
DP进阶合集
(ps:本集合为Star_F总结的dp进阶知识,持续更新~。 转载本文章需要联系我,否则视为侵权!!) 前置知识:线性dp,背包,树形dp,区间dp 内容预览: 状压dp 数位dp dp优化(前缀和,单调队列,斜率优化) 1. 状压dp: 思路:如果题目中 \(n\) 的范围特别小(\(&lt;=2
ComfyUI插件:ComfyUI Impact 节点(二)
前言: 学习ComfyUI是一场持久战,而 ComfyUI Impact 是一个庞大的模块节点库,内置许多非常实用且强大的功能节点 ,例如检测器、细节强化器、预览桥、通配符、Hook、图片发送器、图片接收器等等。通过这些节点的组合运用,我们可以实现的工作有很多,例如自动人脸检测和优化修复、区域增强、
ComfyUI插件:ComfyUI Impact 节点(二) ComfyUI插件:ComfyUI Impact 节点(二) ComfyUI插件:ComfyUI Impact 节点(二)