当前位置: 首页 > news >正文

Vite 工作原理

当我们谈论 Vite 的 “快”,很多人会联想到它基于浏览器原生 ESM 的开发模式。但具体到实现层面,Vite 是如何处理模块加载、解析单文件组件的?结合这段简易的 Vite 实现代码,我们可以清晰地看到其核心工作流程。
一、JS 加载与裸模块路径重写:让浏览器看懂模块引用
浏览器原生支持 ESM(import/export),但有个严格限制:「只能识别相对路径或绝对路径」,无法直接加载 “裸模块”(如import 'vue’中的vue)。这也是 Vite 在开发阶段最核心的处理逻辑之一。
在代码中,我手动添加一个「rewriteImport」函数进行一个转换:

function rewriteImport(content) {  return content.replace(/ from ['"](.*)['"]/g, (s1, s2) => {    // 相对路径/绝对路径不处理    
if (s2.startsWith('./') || s2.startsWith('/') || s2.startsWith('../'))
{      
return s1;    
} else {     // 裸模块重写为 /@modules/xxx      return ` from '/@modules/${s2}'`;    }  });}

当服务器收到 JS 文件请求(如/src/main.js)时,会先读取文件内容,通过这个函数将所有裸模块引用重写。例如:

  • 「原代码:import Vue from ‘vue’」
  • 「重写后:import Vue from ‘/@modules/vue’」

这样一来,浏览器会向服务器发起/@modules/vue的请求,而不是直接报错,为后续的裸模块加载铺路。
二、裸模块加载:找到并返回真正的依赖文件
重写后的路径/@modules/vue会被服务器拦截处理,这一步的核心是「找到」「node_modules」「中对应的依赖文件」,并以浏览器可识别的 ESM 格式返回。
代码中对应的逻辑:

else if (url.startsWith("/@modules/")) {  
// 提取模块名(如从 /@modules/vue 中拿到 vue) const moduleName = url.replace("/@modules/", "");  // 拼接 node_modules 中该模块的路径  const prefix = path.join(__dirname, "node_modules", moduleName);  // 读取依赖的 package.json,找到 module 字段(ESM入口)  const modulePath = require(path.join(prefix, "package.json")).module;  // 读取入口文件内容  const result = fs.readFileSync(path.join(prefix, modulePath), "utf-8");  // 再次重写文件内的裸模块引用(递归处理)  ctx.body = rewriteImport(result);}

这里的关键是利用了 npm 包中package.json的module字段 —— 现代 npm 包通常会通过该字段指定 ESM 格式的入口文件(而非 CommonJS 的main字段)。Vite 直接读取这个入口文件,并再次通过rewriteImport处理其中的依赖引用,形成完整的模块加载链。
这也是 Vite “预构建” 的简化版逻辑:通过处理裸模块路径,让浏览器能直接加载node_modules中的依赖。
三、解析 SFC(.vue 文件):拆分单文件组件的三大块
Vue 的单文件组件(.vue)包含、

const res = compilerSFC.parse(fs.readFileSync(p, 'utf-8')); // 解析SFC为AST
const scriptContent = res.descriptor.script.content; 
// 提取<script>内容// 将 export default 改为常量,方便后续注入render函数
const script = scriptContent.replace("export default", "const __script = ");
// 支持 scoped: 预计算 scopeId 与样式导入
const hasScoped = res.descriptor.styles && res.descriptor.styles.some(s => s.scoped);const scopeId = hasScoped ? `data-v-${res.descriptor.id}` : '';const styleImports = (res.descriptor.styles || [])  .map((s, i) => `import '${url}?type=style&index=${i}&lang=${s.lang || 'css'}'`)  .join('\n');ctx.body = `  ${rewriteImport(script)}  import {render as __render} from '${url}?type=template'  ${styleImports}  ${hasScoped ? `__script.__scopeId = '${scopeId}'` : ''}  __script.render = __render  export default __script`;

相比之前的版本,这里新增了样式处理的关键逻辑:

  • 通过res.descriptor.styles检测组件是否包含样式,以及是否使用scoped属性
  • 生成scopeId(格式为data-v-xxx),用于隔离scoped 样式
  • 自动生成样式文件的导入语句(如import ‘xxx.vue?type=style&index=0’),触发样式处理流程

(二)处理模板请求(type=template)

else if (query.type === 'template') {  const tpl = res.descriptor.template.content; 
// 提取<template>内容  
const hasScoped = res.descriptor.styles && res.descriptor.styles.some(s => s.scoped);  const scopeId = hasScoped ? `data-v-${res.descriptor.id}` : undefined;  
// 编译为render函数(带 scopeId) const render = compilerDOM.compile(tpl, { mode: 'module', scopeId }).code;  ctx.body = rewriteImport(render); // 重写其中的模块引用}

当模板对应的组件使用了 scoped 样式时,编译模板时会传入scopeId,让生成的render函数在创建 DOM 元素时自动添加data-v-xxx属性,确保样式只作用于当前组件。
(三)处理样式请求(type=style)
这是新增的核心逻辑,用于解析

else if (query.type === "style") {  const index = parseInt(query.index);  
const style = res.descriptor.styles[index]; // 获取对应索引的样式块  // 使用 SFC 提供的样式编译,支持 scoped 与预处理器(scss 等)  
const id = `data-v-${res.descriptor.id}`;  
const result = await compilerSFC.compileStyleAsync({    
source: style.content, // 样式源代码    filename: p, // 文件名(用于错误提示)    id, // 用于 scoped 样式的标识   scoped: style.scoped, // 是否为 scoped 样式    preprocessLang: style.lang // 预处理器类型(如 'scss')  }); const cssContent = result.code; // 编译后的 CSS  // 将 CSS 注入到页面 ctx.body = `    const style = document.createElement('style');    style.setAttribute('type','text/css');    style.textContent = `${cssContent.replace(/`/g, '\`')}`;    document.head.appendChild(style);  `;}

这段代码实现了三个关键功能:

  1. 「样式编译」:通过compilerSFC.compileStyleAsync处理 scoped样式(自动添加data-v-xxx属性选择器)和预处理器语法(如 SCSS 转 CSS)
  2. 「动态注入」:将编译后的 CSS通过创建(style)标签的方式注入到页面头部
  3. 「兼容性处理」:使用模板字符串转义(replace(//g, ‘’))避免 CSS中的反引号破坏 JS 语法

四、代码生成与模板编译:从源码到可执行代码
整个流程的最终目标是「生成浏览器可直接运行的 ESM 代码」,这涉及两个关键环节:

  1. JS 代码生成:对于普通 JS文件,通过rewriteImport重写路径后直接返回;对于.vue文件的
  2. 模板编译:通过compilerDOM.compile将(template)转换为render函数,这一步与Vue 的运行时编译逻辑一致,最终生成的代码会包含虚拟 DOM创建逻辑(如createVNode)。
  3. 样式处理:将(style)标签内容编译为标准CSS,通过动态创建(style)标签注入页面,同时支持 scoped 隔离和预处理器。
    例如,一段带 scoped 的样式:
<template>  <div class="box">{{ msg }}</div></template><style scoped>.box { color: red; }</style>

会被处理为:

  1. 模板编译后的render函数会给div添加data-v-xxx属性
  2. 样式编译后变为.box[data-v-xxx] { color: red; }
  3. 通过(style)标签注入页面,只作用于当前组件的元素

总结:简易 Vite 的核心逻辑链
从代码到浏览器运行,整个流程可以概括为:
1. 入口 HTML:服务器返回index.html,浏览器解析并发起main.js请求;
2. JS 处理:重写裸模块路径,让浏览器能识别依赖引用;
3. 裸模块加载:通过/@modules/路径找到node_modules中的依赖,返回 ESM 格式代码;
4. SFC 解析:
5. 拆分.vue文件为template、script、style三部分处理脚本逻辑,生成样式和模板的导入语句编译模板为带 scopeId 的render函数(如使用 scoped 样式)编译样式为 CSS 并动态注入页面
6. 代码生成:合并脚本、模板和样式,生成完整的组件模块;浏览器执行:所有模块通过 ESM 链路加载,最终渲染页面。

这段简易手写代码虽然没有实现 Vite 的热更新、依赖预构建缓存等高级特性,但已经完整展现了其核心原理:利用浏览器原生 ESM,通过开发服务器动态处理模块引用和文件解析,跳过打包步骤,实现极速开发体验。这也是 Vite 区别于传统打包工具(如 Webpack)的根本所在。

http://www.dtcms.com/a/526079.html

相关文章:

  • 在vue-next-admin开源项目增加定义布局配置:页面缓存接口数据
  • 网站建设教学视频百度云盘wordpress 标签云 插件
  • 网站产品使用说明书怎么做wordpress站外连接
  • 网站资料如何做参考文献时尚网站模板
  • 欧美在线网站设计教程企业所得税税率2022
  • 【零基础学MySQL】第一章:MySQL介绍与安装
  • 邯郸有学做搭建网站的吗为客户创建网站必须
  • 网站加入wordpress免费网站app下载汅api
  • ftp链接网站空间苏州营销型网站开发公司
  • 2025妈妈杯大数据竞赛A题mathorcup大数据:集装箱智能破损检测问题手把手思路代码文章教学大学生数学建模
  • 网站备案资料申请平面设计与广告设计
  • 企业类网站模板免费下载dedecms网站乱码
  • 爱思唯尔期刊投稿经验
  • 山东城市建设职业学院图书馆网站抖音怎么推广
  • Hive 加载文件数据到表
  • C++中函数重载解析:从原理到应用
  • Toolhub — 一个干净实用的在线工具集合
  • 深圳网站优化运营安卓手机网页视频怎么下载
  • 做网站自动赚钱珠海网站建设策略
  • 制作公司网站结构图汽车网站模板下载
  • 南通网站建设报价成都装修网
  • 网站模板平台资源搜索引擎seo关键词优化
  • 优秀网站案例欣赏明会红网站
  • 优秀企业网站案例附近学电脑培训班
  • 不知此网站枉做男人的网站微网站 报价
  • 蔬菜网站模板城乡与建设厅网站首页
  • 【c++】this指针学习与理解
  • 【若依】若依框架中实现国际化多语言切换的步骤
  • 重庆城乡建设子网站一个完整的工程项目流程
  • 网站建设 安庆com网站怎么注册