vite原理之解析.vue文件

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: vite就是在本地启用了一个http服务器,然后将本地的文件通过浏览器的请求将本地的文件返回到浏览器;当然其中会有大量的解析,用于将文件内容解析为浏览器可以理解的内容

据说可以掌握的知识

  1. node express框架
  2. vue3
  3. ast树
  4. render函数
  5. 正则
  6. sfc
  7. esm模块

我的理解

按原文来讲,vite就是在本地启用了一个http服务器,然后将本地的文件通过浏览器的请求将本地的文件返回到浏览器;当然其中会有大量的解析,用于将文件内容解析为浏览器可以理解的内容

1. 依赖

"express": "^4.18.1", // 启动服务器
"vue": "^3.2.37" //处理和解析vue模板
AI 代码解读

2. 初始化文件,安装依赖

...略

3. 搭建服务器读取模板

3.1 启动文件server.js

const express = require("express")
var app = express()
const fs = require("fs")
const path = require("path")
const port = "3000"

app.get("/", (req, res, next)=>{
  //响应文本类型
  res.setHeader("content-type", "text/html")
  res.send(fs.readFileSync(path.resolve(__dirname, "./src/index.html")))
  res.end()
})

app.listen(port, ()=>{
  console.log(`the server is listen on ${port}`);
})
AI 代码解读

3.2 html模板文件src/index.html

<html>
<body>
  <div id="app">APP</div>
</body>
AI 代码解读

3.2 启动

node .server
AI 代码解读

3.3 效果

viteDemo-http预览.jpg

viteDemo-http预览.jpg

4. 读取vue文件

4.1 使用render函数读取

先使用vue的render函数来读取

  1. 新建 /src/index.js文件
import { createApp, h } from "vue";

createApp({
  render() {
    return h('div', {color: '#f00'}, "hello")
  }
})
.mount("#app")
AI 代码解读
  1. 修改server.js,以支持解析解析读取.js文件
const express = require("express")
var app = express()

const fs = require("fs")
const port = "3000"

app.get("/", (req, res, next)=>{
  res.setHeader("content-type", "text/html")
  res.send(fs.readFileSync("./src/index.html"))
  res.end()
})

app.get(/(.*)\.js$/, (req, res, next)=>{
  console.log(req.url, path.resolve(__dirname, "./src/"+ req.url));
  const p = fs.readFileSync(path.resolve(__dirname, "./src/"+ req.url), "utf-8");
  res.setHeader("content-type", "text/javascript")
  res.send(p)  
  res.end()
})
AI 代码解读
  1. 查看预览效果:

vite-vue-h函数预览,解析页面空白.png

vite-vue-h函数预览,解析页面空白.png

  1. 导致这个的原因是因为没法处理from 'vue',需要对/src/server.js进行一下改造:
app.get(/(.*)\.js$/, (req, res, next)=>{
  console.log(req.url, path.resolve(__dirname, "./src/"+ req.url));
  const p = fs.readFileSync(path.resolve(__dirname, "./src/"+ req.url), "utf-8");
  res.setHeader("content-type", "text/javascript")
-  res.send(p)  
+  res.send(reWritePath(p))
  res.end()
})

app.get(/^\/@module/, (req, res, next)=>{
  console.log(req.url, req.url.slice(9));
  # 将最终文件指向/node_modules/vue/package.json里的module配置
  const moduleFolder = path.resolve(__dirname, `./node_modules/${req.url.slice(9)}`)
  const modulePackageContent = require(moduleFolder+"/package.json").module
  const finalModulePath = path.join(moduleFolder, modulePackageContent)
  console.log("finalModulePath: ", finalModulePath);
  res.setHeader("content-type", "text/javascript")
  res.send(reWritePath(fs.readFileSync(finalModulePath, "utf-8")))
  res.end()
})

# 处理 import from "vue" 导出,将其转换成可识别路径
function reWritePath(content) {
  let reg = / from ['"](.*)['"]/g
  return content.replace(reg, (s1, s2)=>{
    console.log(s1, " ", s2);
    if(s2.startsWith(".") || s2.startsWith("./") || s2.startsWith("../")){
      return s1
    }else {
      return `from '/@module/${s2}'`
    }
  })
}
AI 代码解读
  1. 修改模板文件/src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script src="./index.js" type="module"></script>
</body>
</html>
AI 代码解读
  1. 预览效果

vite-vue-h函数预览.png
vite-vue-h函数预览.png

4.2 那么如何成功读取.vue文件,并将其渲染到浏览器页面呢?

这就需要用到改造/src/index文件

1. 新建/src/app.vue文件

<template>
  {{title}}
</template>
<script>
import {ref} from "vue"
export default {
  setup() {
    const title = ref("hello")
    return {
      title
    }
  }
}
</script>

<style>
*{
  color:#f00;
}
</style>
AI 代码解读

2. 修改/src/index.js文件

import { createApp} from "vue";
import app from "./app.vue"

createApp(app)
.mount("#app")
AI 代码解读

3. 预览报错

vite-vue-文件不识别.png

vite-vue-文件不识别.png

4. 这个错误是不识别vue后缀的请求导致的

4.1 解决方法就是添加处理以vue为后缀的请求

  1. 修改/server.js
app.get(/(.*)\.vue$/, (req, res, next)=>{
  // exclude request with ? which is template or style requestion
  const questUrl = req.url
  console.log("vue: ", req.url, path.resolve(__dirname, `./src/${questUrl}`));
  const content = fs.readFileSync(path.resolve(__dirname, `./src/${questUrl}`), 'utf-8')
  const contentAST = compilerSFC.parse(reWritePath(content))
  console.log("ast: ", contentAST);
  console.log("query: ", req.query);
})
AI 代码解读

此时,会返回一串ast树代码,然后query是个空对象
vite-vue-astTree.png

vite-vue-astTree.png

  1. 继续修改/server.js
app.get(/(.*)\.vue$/, (req, res, next)=>{
  // exclude request with ? which is template or style requestion
  const questUrl = req.url
  console.log("vue: ", req.url, path.resolve(__dirname, `./src/${questUrl}`));
  const content = fs.readFileSync(path.resolve(__dirname, `./src/${questUrl}`), 'utf-8')
  const contentAST = compilerSFC.parse(reWritePath(content))
  console.log("ast: ", contentAST);
  console.log("query: ", req.query);
  const scriptContent = contentAST.descriptor.script.content
  const script = scriptContent.replace("export default", "const _script = ")
  res.setHeader("content-type", "text/javascript")
  res.send(`
    ${reWritePath(script)}
    import {render as _render} from '${req.url}?type=template';
    import '${req.url}?type=style'
    _script.render = _render
    export default _script
  `)
  res.end()
})
AI 代码解读

通过compilerSFC插件,将.vue文件解析为ast树;从中的script字段的content获取到js内容;并且再次请求app.vue?type=template(请求模板内容)和app.vue?type=style(请求样式);将export default替换为const _script,并导出

  1. 预览效果:

vite-vue-v文件请求.png

vite-vue-v文件请求.png

  1. 请求返回内容

vite-vue-vue后缀请求返回内容.png

vite-vue-vue后缀请求返回内容.png

4.2 处理app.vue?type=templateapp.vue?type=style请求,继续修改/src/server文件

app.get(/(.*)\.vue$/, (req, res, next)=>{
  // exclude request with ? which is template or style requestion
  const questUrl = req.url.split('?')[0]
  console.log("vue: ", req.url, path.resolve(__dirname, `./src/${questUrl}`));
  const content = fs.readFileSync(path.resolve(__dirname, `./src/${questUrl}`), 'utf-8')
  const contentAST = compilerSFC.parse(reWritePath(content))
  console.log("ast: ", contentAST);
  console.log("query: ", req.query);
  // deal with sfc when req.query.type is empty
  if(!req.query.type) {
    const scriptContent = contentAST.descriptor.script.content
    const script = scriptContent.replace("export default", "const _script = ")
    res.setHeader("content-type", "text/javascript")
    res.send(`
      ${reWritePath(script)}
      import {render as _render} from '${req.url}?type=template';
      import '${req.url}?type=style'
      _script.render = _render
      export default _script
    `)
    res.end()
  }else if(req.query.type === 'template') {
    const templateContent = contentAST.descriptor.template.content;
    console.log("compiler: ", compilerDOM.compile(templateContent, {mode: "module"}));
    const renderContent = compilerDOM.compile(templateContent, {mode: "module"}).code
    console.log("code: ", renderContent);
    res.setHeader("content-type", "text/javascript")
    res.send(reWritePath(renderContent));
    res.end()
  }else if(req.query.type === 'style') {
    console.log(contentAST.descriptor.styles[0].content);
    const styleContent = contentAST.descriptor.styles[0].content.replace(/\s/g, "")
    res.setHeader("content-type", "text/javascript")
    res.send(`
      const style = document.createElement('style');
      style.innerHTML = '${styleContent}'
      document.head.appendChild(style)
    `);
    res.end()
  }
})
AI 代码解读

预览效果:
vite-vue-预览效果之没有process参数.png

vite-vue-预览效果之没有process参数.png

4.3. 修改/src/index.js,处理process参数

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script src="./index.js" type="module"></script>
+  <script>
+    window.process = {
+      env: {
+        NODE_ENV: "development"
+      }
+    }
+  </script>
</body>
</html>
AI 代码解读

预览效果:
添加process参数后的预览效果.png

添加process参数后的预览效果.png


5. 至此,vite解析.vue的原理基本脉络就清楚了

vite-vue-目录结构.png

vite-vue-目录结构.png

  1. package.json
{
  "name": "demo-viteCore",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.1",
    "vue": "^3.2.37"
  }
}
AI 代码解读
  1. /src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script src="./index.js" type="module"></script>
  <script>
    window.process = {
      env: {
        NODE_ENV: "development"
      }
    }
  </script>
</body>
</html>
AI 代码解读
  1. /src/app.vue
<template>
  {{title}}
</template>
<script>
import {ref} from "vue"
export default {
  setup() {
    const title = ref("hello")
    return {
      title
    }
  }
}
</script>

<style>
*{
  color:#f00;
}
</style>
AI 代码解读
  1. /src/index.js
import { createApp} from "vue";
import app from "./app.vue"
createApp(app)
.mount("#app")
AI 代码解读
  1. /server.js
const express = require("express")
var app = express()

const compilerSFC = require("@vue/compiler-sfc")
const compilerDOM = require("@vue/compiler-dom")

const path = require("path")
const fs = require("fs")
const port = "3000"

app.get("/", (req, res, next)=>{
  res.setHeader("content-type", "text/html")
  res.send(fs.readFileSync("./src/index.html"))
  res.end()
})

app.get(/(.*)\.js$/, (req, res, next)=>{
  console.log(req.url, path.resolve(__dirname, "./src/"+ req.url));
  const p = fs.readFileSync(path.resolve(__dirname, "./src/"+ req.url), "utf-8");
  res.setHeader("content-type", "text/javascript")
  res.send(reWritePath(p))
  res.end()
})

app.get(/(.*)\.vue$/, (req, res, next)=>{
  // exclude request with ? which is template or style requestion
  const questUrl = req.url.split('?')[0]
  console.log("vue: ", req.url, path.resolve(__dirname, `./src/${questUrl}`));
  const content = fs.readFileSync(path.resolve(__dirname, `./src/${questUrl}`), 'utf-8')
  const contentAST = compilerSFC.parse(reWritePath(content))
  console.log("ast: ", contentAST);
  console.log("query: ", req.query);
  // deal with sfc when req.query.type is empty
  if(!req.query.type) {
    const scriptContent = contentAST.descriptor.script.content
    const script = scriptContent.replace("export default", "const _script = ")
    res.setHeader("content-type", "text/javascript")
    res.send(`
      ${reWritePath(script)}
      import {render as _render} from '${req.url}?type=template';
      import '${req.url}?type=style'
      _script.render = _render
      export default _script
    `)
    res.end()
  }else if(req.query.type === 'template') {
    const templateContent = contentAST.descriptor.template.content;
    console.log("compiler: ", compilerDOM.compile(templateContent, {mode: "module"}));
    const renderContent = compilerDOM.compile(templateContent, {mode: "module"}).code
    console.log("code: ", renderContent);
    res.setHeader("content-type", "text/javascript")
    res.send(reWritePath(renderContent));
    res.end()
  }else if(req.query.type === 'style') {
    console.log(contentAST.descriptor.styles[0].content);
    const styleContent = contentAST.descriptor.styles[0].content.replace(/\s/g, "")
    res.setHeader("content-type", "text/javascript")
    res.send(`
      const style = document.createElement('style');
      style.innerHTML = '${styleContent}'
      document.head.appendChild(style)
    `);
    res.end()
  }
})

app.get(/^\/@module/, (req, res, next)=>{
  console.log(req.url, req.url.slice(9));
  const moduleFolder = path.resolve(__dirname, `./node_modules/${req.url.slice(9)}`)
  const modulePackageContent = require(moduleFolder+"/package.json").module
  const finalModulePath = path.join(moduleFolder, modulePackageContent)
  console.log("finalModulePath: ", finalModulePath);
  res.setHeader("content-type", "text/javascript")
  res.send(reWritePath(fs.readFileSync(finalModulePath, "utf-8")))
  res.end()
})

function reWritePath(content) {
  let reg = / from ['"](.*)['"]/g
  return content.replace(reg, (s1, s2)=>{
    console.log(s1, " ", s2);
    if(s2.startsWith(".") || s2.startsWith("./") || s2.startsWith("../")){
      return s1
    }else {
      return `from '/@module/${s2}'`
    }
  })
}


app.listen(port, ()=>{
  console.log(`the server is listen on ${port}`);
})
AI 代码解读
目录
打赏
0
0
0
0
0
分享
相关文章
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
64 22
解析静态代理IP改善游戏体验的原理
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
77 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
46 12
解锁鸿蒙装饰器:应用、原理与优势全解析
ArkTS提供了多维度的状态管理机制。在UI开发框架中,与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或跨设备传递。
21 2
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
Vue Router 核心原理
Vue Router 是 Vue.js 的官方路由管理器,用于实现单页面应用(SPA)的路由功能。其核心原理包括路由配置、监听浏览器事件和组件渲染等。通过定义路径与组件的映射关系,Vue Router 将用户访问的路径与对应的组件关联,支持哈希和历史模式监听 URL 变化,确保页面导航时正确渲染组件。
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
16 0
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
297 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
智能文件解析:体验阿里云多模态信息提取解决方案
在当今数据驱动的时代,信息的获取和处理效率直接影响着企业决策的速度和质量。然而,面对日益多样化的文件格式(文本、图像、音频、视频),传统的处理方法显然已经无法满足需求。
106 4
智能文件解析:体验阿里云多模态信息提取解决方案

热门文章

最新文章

推荐镜像

更多