前端知识体系总结-前端工程化(Vite篇)

实现 Vite 核心功能(自测:Vite 核心功能和运行原理有哪些,由最简讲起,具体是怎么实现的)

Webpack 是先打包好文件再放到 dev server 运行

而 Vite 是先运行 dev server ,之后浏览器请求什么文件就在 dev server 中动态编译后再返回。核心是基于浏览器原生支持的 ES Modules (<script type="module">),当浏览器解析到 import 语句时,会向服务器发送 HTTP 请求,服务器拦截这些请求并实时编译文件与响应。

一、搭建服务返回index.html与编译js文件

1.1 搭建基础开发服务器

我们需要一个能拦截请求的 HTTP 服务器。这里使用 Koa (Vite 内部使用 connect,逻辑类似)。

目录结构:

text 复制代码
mini-vite/
  ├── src/
  │   ├── main.js
  │   └── App.vue
  ├── index.html
  ├── server.js  (我们将编写的代码)
  └── package.json

index.html: 关键在于 type="module",这告诉浏览器直接以 ES 模块方式加载 js。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<body>
  <div id="app"></div>
  <!-- 浏览器会发起 GET /src/main.js 请求 -->
  <script type="module" src="/src/main.js"></script>
</body>
</html>

server.js (第一步:静态文件服务): 浏览器请求 / 返回 HTML,请求 /src/main.js 返回 JS 内容。

js 复制代码
const Koa = require('koa');
const fs = require('fs');
const path = require('path');

const app = new Koa();

app.use(async (ctx) => {
  const url = ctx.request.url;
  
  // 1. 根路径返回 index.html
  if (url === '/') {
    ctx.type = 'text/html';
    ctx.body = fs.readFileSync('./index.html', 'utf-8');
    return;
  }
  
  // 2. JS文件请求处理 (如 /src/main.js)
  if (url.endsWith('.js')) {
    const p = path.join(__dirname, url);
    ctx.type = 'application/javascript';
    ctx.body = fs.readFileSync(p, 'utf-8');
    return;
  }
});

app.listen(3000, () => {
  console.log('Vite dev server running at http://localhost:3000');
});

二、实现第三方库导入处理

2.1 问题描述

src/main.js 中,我们通常这样写:

js 复制代码
import { createApp } from 'vue'; // ❌ 浏览器报错
import App from './App.vue';

浏览器遇到 import ... from 'vue' 时会报错,因为它不知道 'vue' 在哪里。浏览器只认识相对路径 (./, ../) 或绝对路径 (/)。

2.2 解决方案:路径重写

服务器需要在返回 JS 文件内容给浏览器之前 ,把内容里的 'vue' 替换成 '/@modules/vue',给它一个特殊标识。

修改 server.js:

js 复制代码
// 工具函数:把文件流转成字符串
function readStream(stream) {
  return new Promise((resolve, reject) => {
    let data = '';
    stream.on('data', chunk => data += chunk);
    stream.on('end', () => resolve(data));
  });
}

// 路径重写逻辑
function rewriteImport(content) {
  // 正则匹配: from 'vue' -> from '/@modules/vue'
  // s0: 匹配到的完整字符串
  // s1: 捕获组,即包名 'vue'
  return content.replace(/ from ['"](.*)['"]/g, (s0, s1) => {
    // 如果是相对路径 ./ 或 ../ 或 / 开头,不处理
    if (s1.startsWith('.') || s1.startsWith('/')) {
      return s0;
    }
    // 否则加上 /@modules/ 前缀
    return ` from '/@modules/${s1}'`;
  });
}

app.use(async (ctx) => {
  const url = ctx.request.url;

  if (url.endsWith('.js')) {
    const p = path.join(__dirname, url);
    const content = fs.readFileSync(p, 'utf-8');
    ctx.type = 'application/javascript';
    // 返回修改后的内容
    ctx.body = rewriteImport(content); 
    return;
  }
});

经过这一步,浏览器收到的代码变成了:

js 复制代码
import { createApp } from '/@modules/vue'; // ✅ 浏览器会发起新请求
import App from './App.vue';

2.3 获取真实文件路径

当浏览器请求 http://localhost:3000/@modules/vue 时,服务器需要去 node_modules 里找到 vue 的入口文件。

查找步骤:

  1. 找到 node_modules/vue 文件夹。
  2. 读取 package.jsonmodule 字段 (ESM 入口) 或 main 字段。
  3. 读取该入口文件的内容返回。

2.4 server.js 新增逻辑

js 复制代码
app.use(async (ctx) => {
  const url = ctx.request.url;

  // 3. 处理第三方模块请求
  if (url.startsWith('/@modules/')) {
    // 提取模块名,例如 'vue'
    const moduleName = url.replace('/@modules/', '');
    
    // 在 node_modules 中找到该模块文件夹
    const prefix = path.join(__dirname, './node_modules', moduleName);
    
    // 读取 package.json
    const packageJSON = require(path.join(prefix, 'package.json'));
    
    // 获取入口文件路径 (优先使用 module 字段,因为是 ESM)
    const entryPath = path.join(prefix, packageJSON.module);
    
    // 读取文件内容
    const content = fs.readFileSync(entryPath, 'utf-8');
    
    ctx.type = 'application/javascript';
    // 第三方库内部可能也引用了其他库,也需要重写路径
    ctx.body = rewriteImport(content);
    return;
  }
  
  // ... 其他逻辑
});

三、处理 .vue 单文件组件 (SFC)

3.1 浏览器不认识 .vue

浏览器请求 App.vue 时,服务器不能直接返回 Vue 源码,需要把 .vue 编译成 JS。

Vite 使用 vue 官方提供的 @vue/compiler-sfc 进行编译。

3.2 server.js 新增 Vue 处理逻辑

js 复制代码
const compilerSfc = require('@vue/compiler-sfc');

app.use(async (ctx) => {
  const url = ctx.request.url;
  
  // 4. 处理 .vue 文件
 if (ctx.url.endsWith(".vue")) {
	ctx.type = "application/javascript; utf-8";
	const content = fs.readFileSync(path.join(__dirname, ctx.url), "utf-8");
	const { descriptor } = compilerSfc.parse(content);

	// 使用 inlineTemplate 选项,让 compileScript 直接生成包含 render 的完整组件
	const compiled = compilerSfc.compileScript(descriptor, {
		id: ctx.url,
		inlineTemplate: true, // 关键:内联编译模板,setup 直接返回 render 函数
	});

	ctx.body = rewriteImport(compiled.content);
	return;
	}
});

四、Vite 核心功能总结

实现一个简易 Vite 只需要解决三个问题:

  1. 服务器:用 Koa 拦截浏览器发起的文件获取 HTTP 请求并实时编译与返回。
  2. JS 处理 :遇到 import 'vue' 这种裸模块导入,重写路径为 /@modules/vue,并去 node_modules 里找文件返回。
  3. Vue 处理 :遇到 .vue 文件,使用 compiler-sfc 编译。先把 Script 发给浏览器,再让浏览器回头取 Template 的编译结果,最后拼在一起。

这种模式下,开发环境启动速度与项目大小无关,因为只有当你点击了某个页面,浏览器发起了请求,服务器才开始编译那个页面用到的文件。

Vite HMR实现原理(自测:更新一个文件后,wbp和vite分别会经过什么流程进行网页的热更新)

一、先回顾 Webpack 热更新原理

假如你的项目有1000个JS模块,你修改了其中一个文件 src/components/Header.vue

Webpack的处理方式

  1. Wepack Compiler 监听工作区:Webpack监听到文件保存动作。
  2. 重新构建被修改的模块:loader 链转换文件为 JS 可执行代码 -> AST 解析代码并识别 import、export 代码进行依赖图的增加或删除 -> 对新发现的依赖进行递归处理
  3. 打包:生成 Manifest JSON 文件,告诉浏览器这次更新涉及哪些模块;生成 Update Chunk JS 文件,包含被修改那个模块的新代码。
  4. 推送:HMR Server通过WebSocket推送更新通知给浏览器。
  5. 替换:浏览器的 HMR Runtime 请求清单文件,并根据清单文件请求被更新的模块代码,接着找到模块是否有自己的 module.hot.accept ,否则冒泡沿着依赖图向上查找,在 accept 回调中 import 并执行新的JS代码进行视图的更新。

三、Vite HMR 具体工作流程

1. 建立连接

客户端(浏览器)连接 Vite 开发服务器的 WebSocket。

2. 文件修改与通知

当你保存 Header.vue 时:

  1. Vite 文件监听器检测到变化。
  2. 解析该文件导出内容,确定它是Vue组件。
  3. 通过 WebSocket 向客户端发送一段JSON消息。

消息内容示例:

json 复制代码
{
  "type": "update",
  "updates": [
    {
      "type": "js-update",
      "timestamp": 1678888888,
      "path": "/src/components/Header.vue",
      "acceptedPath": "/src/components/Header.vue"
    }
  ]
}

3. 浏览器重新请求

Vite 在浏览器端注入的客户端代码(vite/client)收到消息。它不会像 Webpack 那样去执行一段新推过来的 JS 代码块,而是利用浏览器动态导入功能

具体操作: 浏览器构造一个新的 import URL,带上时间戳以强制让浏览器认为这是一个新文件,从而避开缓存。

js 复制代码
// 浏览器端逻辑模拟
import('/src/components/Header.vue?t=1678888888')
  .then((newModule) => {
    // 获取到新的模块内容,进行替换
  });

4. 模块替换

对于Vue组件,Vite使用了 vue-loader 类似的逻辑(vite-plugin-vue)。

  • 旧的 Header.vue 组件实例还保留在内存中。
  • 新的模块加载后,框架(Vue/React)利用 HMR API 重新渲染该组件,保留组件内的 data/state 状态,仅更新 render 函数或样式。

四、Vite HMR API:import.meta.hot

Webpack使用 module.hot,而 Vite 使用 ESM 标准的 import.meta.hot

开发者的代码(通常由插件自动注入):

js 复制代码
// src/components/Header.vue 编译后的JS代码
// ... 组件代码 ...

export default _sfc_main;

// HMR 逻辑
if (import.meta.hot) {
  // 接受自身更新
  import.meta.hot.accept((newModule) => {
    if (newModule) {
      // 执行组件重渲染逻辑
      __VUE_HMR_RUNTIME__.reload('组件HashID', newModule.default);
    }
  });
}

实现逻辑:

  1. import.meta.hot.accept:告诉 Vite,如果这个文件变了,不需要刷新页面,我自己能处理。
  2. 回调函数 :当新文件被 import(...) 加载成功后,执行这个回调,传入新模块内容。

五、所以为什么 Vite HMR 速度快

  1. 无需重构依赖图:文件保存后无需重新分析依赖图的更改,本质是因为 Vite 不需要构建依赖图去生成 bundle,而是通过浏览器 ESM 能力提供所需文件即可。
  2. 无需打包:Vite 只需编译一次文件,而 Webpack 需要将受影响的模块及其相关依赖(修改模块本身、父节点可能更新对子模块的Module ID引用代码、所属Chunk)重新打包与合并,涉及 n 个文件的修改。
  3. 全量代码下发:Webpack 下发包含新代码的 HMR 更新包,而 Vite 只发送一个指向修改该文件的 HTTP 请求,由浏览器重新请求。

Vite Plugin 实现原理与实战(自测:实现一个vite-plugin-svg-icons)

在前文中,我们了解了 Webpack 的打包流程:读取入口 -> 分析 AST -> 递归依赖 -> 转换代码 -> 生成 Bundle

Vite 的工作方式完全不同。在开发环境下,Vite 不打包 。它利用浏览器对 ES Modules 的原生支持。当浏览器发起请求(如 GET /src/main.js)时,Vite 服务器拦截请求,进行必要的代码转换,然后直接返回 JS 内容。

Vite 插件 就是用来拦截处理这些请求的工具。

Vite 插件基于 Rollup 的插件接口设计,同时扩展了一些 Vite 独有的钩子(Hooks)。

一、Vite 插件的核心钩子 (Hooks)

Webpack 将功能分为 Loader(转换文件)和 Plugin(监听构建生命周期)。Vite 将这两者合并了。一个 Vite 插件本质上是一个返回配置对象的函数

处理一个文件请求时,主要经过以下三个核心钩子:

  1. resolveId(source, importer) : 找文件
    • 输入 : 代码中的导入路径(如 import x from './a' 中的 './a')。
    • 作用: 告诉 Vite 这个文件的绝对路径在哪里,或者标记这是一个"虚拟模块"。
    • 返回: 文件的绝对路径或 ID。
  2. load(id) : 读文件
    • 输入 : resolveId 返回的绝对路径或 ID。
    • 作用: 读取文件内容。通常用于加载磁盘文件或生成虚拟文件内容。
    • 返回: 文件内容的字符串。
  3. transform(code, id) : 改代码 (相当于 Webpack Loader)
    • 输入 : load 返回的代码字符串,以及文件 ID。
    • 作用: 将非 JS 代码(如 Vue, CSS, TS)转换为浏览器能识别的 JS 代码。
    • 返回: 转换后的 JS 代码。

二、实战:实现一个虚拟模块插件

场景:你需要在一个项目中引入一个并不存在于磁盘上的文件,比如构建时的环境变量信息。

目标代码

javascript 复制代码
// main.js
import env from 'virtual:env'; // 这个文件在磁盘上不存在
console.log(env); 

插件实现

javascript 复制代码
export default function myVirtualPlugin() {
  const virtualModuleId = 'virtual:env';
  const resolvedVirtualModuleId = '\0' + virtualModuleId; // \0 是 Rollup 的约定,表示这是一个虚拟模块,不要去磁盘找

  return {
    name: 'my-virtual-plugin', // 插件名称,必填

    // 1. 拦截 import
    resolveId(source) {
      if (source === virtualModuleId) {
        // 如果 import 的是 'virtual:env',返回我们自定义的 ID
        return resolvedVirtualModuleId;
      }
      return null; // 其他文件不管,交给 Vite 处理
    },

    // 2. 加载内容
    load(id) {
      if (id === resolvedVirtualModuleId) {
        // 匹配到自定义 ID,直接返回一段 JS 代码
        return `export default { 
            user: "admin", 
            buildTime: "${new Date().toISOString()}" 
        }`;
      }
      return null; // 其他文件不管,读取磁盘
    }
  };
}

配置 vite.config.js:

javascript 复制代码
import myVirtualPlugin from './plugins/myVirtualPlugin';

export default {
  plugins: [myVirtualPlugin()]
};

三、实战:实现一个 vite-plugin-svg-icons

首先明确插件功能:扫描 指定目录下的 SVG 文件 -> 转换<symbol> 标签并合并 -> 提供 虚拟模块 virtual:svg-register import 注入页面 -> 支持 HMR 热更新。

为了更好理解插件功能,我们看看在实际场景中它的作用:

你正在开发一个企业级后台管理系统,设计师提供了一套自定义 SVG 图标(如 nav-order.svg, action-edit.svg),要求图标颜色能随文字颜色变化(如菜单 Hover 时变蓝),且会有数十个图标散落在各个页面。

使用img标签,第一个是无法改变颜色需要重新提供另一版本svg,并且还需要根据hover事件动态切换src,非常麻烦;使用内联svg代码,代码很臃肿,可读性差;使用手动import,若一个页面需要的svg很多,会产生大量import语句

我们的插件目标:

  1. 零配置引用 :只需将 SVG 文件丢入 src/icons 文件夹,无需任何 import 语句,直接通过文件名即可使用。
  2. CSS 样式控制 :插件生成的 SVG Sprite 支持 currentColor,图标就像文字一样,可以用 CSS 随意控制颜色大小
  3. 高性能 :所有图标被合并成一段 JS 注入 HTML,零 HTTP 请求,且按需加载。

使用效果演示:

js 复制代码
// main.ts
import 'virtual:svg-register' // 一行代码,所有图标自动打包注入
html 复制代码
<!-- 无需 import,直接使用 -->
<svg class="icon" aria-hidden="true">
  <use xlink:href="#icon-nav-order" />
</svg>

<style>
.icon {
  color: grey;       /* 默认灰色 */
  font-size: 20px;   /* 控制大小 */
}
.icon:hover {
  color: blue;       /* 悬停自动变蓝,无需 JS */
}
</style>

可以封装为一个组件:

html 复制代码
<!-- src/components/SvgIcon.vue -->
<template>
  <svg class="svg-icon" aria-hidden="true">
    <use :xlink:href="symbolId" />
  </svg>
</template>

<script setup lang="ts">
import { computed } from 'vue'

const props = defineProps({
  name: { type: String, required: true }, // 传入图标文件名,如 'truck'
  prefix: { type: String, default: 'icon' }
})

const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>

<style scoped>
.svg-icon {
  width: 1em; height: 1em; /* 默认跟随字体大小 */
  vertical-align: -0.15em;
  fill: currentColor; /* 关键:让图标颜色跟随文字颜色 */
  overflow: hidden;
}
</style>

现在我们来实现这个插件功能

首先理解"虚拟模块"

你可以在浏览器端 import 一个不存在于文件系统中的文件

目标:用户在代码里写 import 'virtual:svg-register',插件可以正确识别和拦截。

具体实现:使用 resolvedId 属性进行配置,当文件路径是我们的虚拟模块时,直接返回不需要解析,并且在 load 阶段返回我们自定义的代码交给程序执行

第二步:实战代码编写

新建一个 my-svg-plugin.js

我们可以安装一个依赖来方便找文件:npm install fast-glob

javascript 复制代码
// my-svg-plugin.js
import path from 'path'
import fs from 'fs'
import fg from 'fast-glob'

export default function mySvgPlugin(options) {
  // 1. 配置虚拟模块 ID
  const VIRTUAL_MODULE_ID = 'virtual:svg-register'
  const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID

  return {
    name: 'vite-plugin-my-svg-sprite', // 插件名称

    // 2. resolveId: 告诉 Vite 这个 import 归我管
    resolveId(id) {
      if (id === VIRTUAL_MODULE_ID) {
        return RESOLVED_VIRTUAL_MODULE_ID
      }
    },

    // 3. load: 返回这个虚拟模块的具体代码
    async load(id) {
      if (id === RESOLVED_VIRTUAL_MODULE_ID) {
        
        // --- 核心逻辑开始 ---
        
        // A. 找到所有 SVG 文件
        const { iconDir } = options
        const svgFiles = await fg('**/*.svg', { cwd: iconDir, absolute: true })

        // B. 遍历并读取内容,拼接成 Symbol 字符串
        let symbols = ''
        
        svgFiles.forEach((file) => {
	         if (file.endsWith(".svg")) {
		 const content = fs.readFileSync(path.join(iconDir, file), "utf-8");
		 const viewBox = content.match(/viewBox="([^"]+)"/)?.[1] || "0 0 24 24";
		 const pathContent = content.match(/<svg[^>]*>(.*)<\/svg>/s)?.[1] || "";
		 const iconName = file.replace(".svg", "");

		 symbols += `<symbol id="icon-${iconName}" viewBox="${viewBox}">${pathContent}</symbol>`;
		}
	});

        // C. 构造最终的 JS 代码
        // 返回在页面中注入 SVG sprite 的代码
		return `		
                const svgSprite = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
		     svgSprite.style.position = 'absolute';
		     svgSprite.style.width = '0';
		     svgSprite.style.height = '0';
		     svgSprite.innerHTML = \`${symbols}\`;
		     document.body.insertBefore(svgSprite, document.body.firstChild);
			`;
        // --- 核心逻辑结束 ---
      }
    }
  }
}
第三步:在项目中使用 (验证效果)
  1. 配置 vite.config.js:

    javascript 复制代码
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import path from 'path'
    import mySvgPlugin from './my-svg-plugin' // 引入你写的插件
    
    export default defineConfig({
      plugins: [
        vue(),
        mySvgPlugin({ 
            iconDir: path.resolve(__dirname, 'src/icons') // 假设你的图标都在这里
        })
      ]
    })
  2. 准备素材 : 在 src/icons 下放几个 svg 文件,比如 vue.svg, react.svg

  3. 引入注册 : 在 src/main.js (或 main.ts) 中引入虚拟模块:

    javascript 复制代码
    import { createApp } from 'vue'
    import App from './App.vue'
    
    // 这一行会触发你插件的 resolveId -> load,
    // 然后在浏览器执行那段插入 DOM 的 JS 代码
    import 'virtual:svg-register' 
    
    createApp(App).mount('#app')
  4. 组件使用: 在 Vue 组件里写:

    html 复制代码
    <template>
      <div>
        <!-- 使用图标 -->
        <svg style="width: 50px; height: 50px; fill: red;">
          <use xlink:href="#icon-vue"></use>
        </svg>
        <svg style="width: 50px; height: 50px; fill: blue;">
          <use xlink:href="#icon-react"></use>
        </svg>
      </div>
    </template>

四、Vite 独有的钩子:configureServer

Vite 插件不仅仅是构建工具,还是一个开发服务器。configureServer 钩子允许我们在 Vite 的 Node.js 服务器(基于 connect 库)中添加中间件。这在 Webpack Plugin 中很难直接做到。

场景 :实现一个简易的 API Mock 功能。当请求 /api/user 时,拦截请求并返回假数据,不经过后端。

插件实现

javascript 复制代码
export default function myMockPlugin() {
  return {
    name: 'my-mock-plugin',

    configureServer(server) {
      // server 是 Vite 开发服务器实例
      // server.middlewares 是一个 connect 实例,用法类似 Express
      
      server.middlewares.use((req, res, next) => {
        // 拦截 /api/user 请求
        if (req.url === '/api/user') {
          res.setHeader('Content-Type', 'application/json');
          res.end(JSON.stringify({ id: 1, name: 'Mock User' }));
          return; // 结束请求
        }
        
        // 其他请求放行
        next();
      });
    }
  };
}

五、Vite 的热更新 (HMR) 钩子:handleHotUpdate

在 Webpack 中实现 HMR 需要修改打包逻辑。在 Vite 中,插件可以直接介入 HMR 流程。

场景 :当 .txt 文件修改时,不刷新页面,只通过自定义事件通知浏览器更新。

插件实现 (服务端)

javascript 复制代码
export default function myHmrPlugin() {
  return {
    name: 'my-hmr-plugin',

    handleHotUpdate({ file, server, modules }) {
      if (file.endsWith('.txt')) {
        // 1. 读取更新后的文件内容
        const content = require('fs').readFileSync(file, 'utf-8');

        // 2. 向浏览器发送自定义 Websocket 消息
        server.ws.send({
          type: 'custom',
          event: 'txt-update',
          data: { file, content } // 发送新内容
        });

        // 3. 返回空数组,告诉 Vite:这个文件我处理了,你不需要执行默认的 HMR 逻辑(默认逻辑通常是重新加载模块)
        return [];
      }
    }
  };
}

客户端代码 (Client)

javascript 复制代码
// 在 main.js 中接收消息
if (import.meta.hot) {
  import.meta.hot.on('txt-update', (data) => {
    console.log(`文件 ${data.file} 变了,新内容是: ${data.content}`);
    // 在这里手动更新 DOM
    document.querySelector('#app').innerText = data.content;
  });
}

六、总结:Webpack vs Vite 插件开发对比

功能点 Webpack 实现方式 Vite 实现方式
引入非 JS 文件 Loader (如 css-loader) Plugintransform 钩子
寻找模块路径 resolve.alias 配置或 Resolver 插件 PluginresolveId 钩子
读取文件内容 Loader 读取 Pluginload 钩子
开发服务器拦截 devServer.before 配置 PluginconfigureServer 钩子
热更新控制 注入 Runtime 代码,较复杂 PluginhandleHotUpdate + import.meta.hot

开发思维转变:

  • Webpack 插件像是在一条已经铺好的流水线(Compiler Hooks)上安装传感器和机械臂。
  • Vite 插件更像是拦截器。浏览器请求文件 -> 你的插件拦截 -> 告诉你 ID -> 你给它内容 -> 你转换内容 -> 返回给浏览器。
相关推荐
掘金者阿豪1 小时前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen1 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端2 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid2 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger3 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4533 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
自由路飞4 小时前
RAG 混合检索深挖:BM25 和向量分数为什么不能直接相加?
面试
lichenyang4534 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端