vite 开发服务器搭建原理

我们来创建一个简单的 vite 开发服务器,来了解服务器的原理。

启动服务,开启监听

通过下面的命令创建一个项目:vite-dev-server

bash 复制代码
mkdir vite-dev-server
cd vite-dev-server
yarn init -y # -y 的作用是:初始化 yarn 时,全部使用默认配置

添加 koa,koa 是 Express 的下一代基于 Node.js 的 web 框架。

bash 复制代码
yarn add koa

创建 index.js,使用 koa 监听 5173 端口。

javascripty 复制代码
const Koa = require("koa");

const app = new Koa();

app.listen(5173, () => {
    console.log("vite dev server listen on 5173");
});

注意: index.js 是通过 node 执行的,所以导入模块时,必须使用 common js 规范,而不能使用 es modules 的规范。

使用 node 运行 index.js

bash 复制代码
node index.js

此时使用浏览器访问:http://localhost:5173,将会返回 Not Found。

响应 html 文件

对于任何请求,app 将调用 app.use注入的异步函数处理请求:

javascript 复制代码
const Koa = require("koa");

const app = new Koa();

// 当请求来临的时候,会执行 use 注入的回调中
app.use(async (ctx) => {
    console.log(ctx.request);
    console.log(ctx.response);
});

app.listen(5173, () => {
    console.log("vite dev server listen on 5173");
});

此时,我们就可以获取到请求和回复内容:

ruby 复制代码
// request
{          
  method: 'GET',
  url: '/',     
  header: {
    host: 'localhost:5173',
    connection: 'keep-alive',
    'cache-control': 'max-age=0',
    'sec-ch-ua': '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'upgrade-insecure-requests': '1',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
    accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'sec-fetch-site': 'none',
    'sec-fetch-mode': 'navigate',
    'sec-fetch-user': '?1',
    'sec-fetch-dest': 'document',
    'accept-encoding': 'gzip, deflate, br',
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
    cookie: 'Webstorm-32b69a0d=a5b6a987-8d4c-4c91-bfc5-9d9f4e2f897f'
  }
}
// response
{
  status: 404,
  message: 'Not Found',
  header: [Object: null prototype] {},
  body: undefined
}

node 服务最频繁做的事情就是在处理请求和操作文件。在处理文件和路径时,node 会使用到 fspath模块。

注意: 在 node 环境下,不需要通过yarn add或者npm install安装fspath模块。这两个模块是 node 来提供的。不同的 js 宿主环境,会赋予 js 不同的能力,比如:

  • 在浏览器环境中,浏览器将提供的特殊能力注入到 window 下面的。然后,我们就可以使用这些特殊的能力,例如:通过 document.getElementById(id 名) 来获取指定 ID 的元素。
  • 在 node 的执行环境中,在遇到导入的模块是 node 提供的模块时,就会在 node 模块中查找。node 模块中没有的时候,才会从 node_modules 中查找。

在处理请求获取根路径的信息时,一般返回一个 html 页面作为响应。创建一个 index.html 文件,并设置下面的内容。

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Vite dev server</title>
  </head>
  <body>
    hello vite dev server
  </body>
</html>

在响应的回调函数中,通过 ctx.request.url来获取请求的路径,通过设置 ctx.response.body 来设置响应体,通过"Content-Type"来设置响应的格式,从而影响到接收方以怎样的方式来解析接收到的内容:

ini 复制代码
const Koa = require("koa");
const fs = require("fs");
const path = require("path");

const app = new Koa();

app.use(async (ctx) => {
    console.log("ctx:", ctx.request, ctx.response);

    if (ctx.request.url === "/") {
        const indexContent = await fs.promises.readFile(path.resolve(__dirname, "./index.html")); // 在服务端一般不这么写,而是使用文件流的方式
      	// 设置响应体发给请求资源的对象  
      	ctx.response.body = indexContent; 
        // 设置响应格式
        ctx.response.set("Content-Type", "text/html");
    }
});

app.listen(5173, () => {
    console.log("vite dev server listen on 5173");
});

此时,在浏览器访问:http://localhost:5173,将会接收到我们返回的 html 的内容,并正确的解析。

响应 vue 文件

假如浏览器请求了一个 vue 文件,vite 会对 vue 文件做 AST 语法分析,通过 createElement() 来构建原生的 dom 等一系列操作,最终生成原生的 javascripty 内容,然后返回给浏览器。简单的说,vite 服务器会对 vue 文件中的字符串内容做一个字符串替换,例如会对 <template> 标签进行字符串替换,生成原生的 javascript 内容。

所以在请求 vue 文件时,并不是直接把读取到的内容返回给浏览器,而是将生成的原生 JavaScript 作为返回内容。此时浏览器在遇到 .vue后缀的文件后,依然无法解析。这时,就可以通过设置 "Content-Type"来告诉浏览器,以 JavaScript 文件来解析。

这就是为什么浏览器会处理 .vue 格式的文件了,因为:

  1. 浏览器接收到的是处理之后的原始 JavaScript 内容,并不是将原始的 vue 文件中的内容直接返回给了浏览器;
  2. 服务器通过设置 "Content-Type" 告诉浏览器以 JavaScript 文件来解析文件内容。

例如,在 index.html 中,添加一个引用 ./main.js 文件:

xml 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>

    <title>Vite dev server</title>
  </head>
  <body>
    hello vite dev server
    <script type="module" src="./main.js"></script>
  </body>
</html>

main.js 内容如下

arduino 复制代码
import "./App.vue"

console.log("main 123");

App.vue 文件的内容如下:

arduino 复制代码
console.log("app vue 123");

index.js 中,添加处理获取 main.js 和 App.vue 的请求:

javascript 复制代码
const Koa = require("koa");
const fs = require("fs");
const path = require("path");

const app = new Koa();

app.use(async (ctx) => {

    if (ctx.request.url === "/") {
        const indexContent = await fs.promises.readFile(path.resolve(__dirname, "./index.html")); // 在服务端一般不这么写,而是使用文件流的方式
        ctx.response.body = indexContent; 
        ctx.response.set("Content-Type", "text/html");
    }

    if (ctx.request.url === "/main.js") {
        const mainContent = await  fs.promises.readFile(path.resolve(__dirname, "./main.js"));
        ctx.response.body = mainContent;
        ctx.response.set("Content-Type", "text/javascript");
    }
    if (ctx.request.url === "/App.vue") {
        const appContent = await fs.promises.readFile(path.resolve(__dirname, "./App.vue"));
        // 省略将 appContent 解析为原生 javascripty 的过程
        ctx.response.body = appContent;
        ctx.response.set("Content-Type", "text/javascript");
    }
});

app.listen(5173, () => {
    console.log("vite dev server listen on 5173");
});

此时,浏览器中就可以正确的运行 main.js 和 App.vue 中的内容了。

参考

相关推荐
GISer_Jing2 小时前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪3 小时前
CSS复习
前端·css
咖啡の猫5 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲7 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5818 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路8 小时前
GeoTools 读取影像元数据
前端
ssshooter9 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry9 小时前
Jetpack Compose 中的状态
前端
dae bal10 小时前
关于RSA和AES加密
前端·vue.js
柳杉10 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化