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 中的内容了。

参考

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试