Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

  • 深入浅出 Vite

    • 00-目录大纲
    • 07-Vite预构建上手
    • 09-Esbuild 插件开发实战
    • 10-Rollup 打包基本概念及使用
    • 11-Rollup打包机制及插件开发
    • 12-Vite插件开发实战
      • 0.前言
      • 1.概念梳理
        • 问题1: vite插件与 rollup 插件的关系,请叙述两者的兼容性与差异性?
      • 2.开发实战
        • 问题2:Vite独有的插件Hook有哪些,简述作用?
        • 问题3:Vite中插件Hook执行顺序是怎样的?
        • 问题4:插件enforce属性的作用和取值?
        • 问题5:如何制定Vite插件的应用场景(开发环境或生产环境)?
        • 问题6:使用过vite-plugin-inspect插件吗?作用是什么?
      • 3.插件实战
        • 问题7:请阐述如何开发虚拟模块插件?
        • 问题9:请阐述下如何开发 SVG 插件?
    • 13-Vite热更新
    • 16-Vite搭建 SSR 框架
  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

  • 百问掘金
  • 深入浅出 Vite
wangjiasheng
2023-04-05
目录

12-Vite插件开发实战

# 0.前言

本节课程代码的仓库 (opens new window):14-vite-plugin-development

本篇博客为《深入浅出 Vite 》掘金手册第十二章的总结。

# 1.概念梳理

# 问题1: vite插件与 rollup 插件的关系,请叙述两者的兼容性与差异性?

首先需要理解的是 vite 与 webpack 等传统的打包工具从架构上是很不相同的,传统打包工具,无论是开发环境还是生产环境从头到尾使用一套工具打包。这样有个明显的好处是开发与生产的构建完全是一套东西,开发者在开发结束后,直接使用 build 打包并发布。

但 vite 采用的是双构建引擎机制,即开发阶段为 esbuild ,生产上 rollup(ps:这句话不严谨,但是大部分人的认知如此),这样做的好处很明显,基于现代浏览器的 module 机制开发体验得到大幅提升,但代价是开发生产不一致的问题。(无解)

基于上述原因,为了尽量减少两者差异,从写法上,模拟兼容整套 rollup 构建机制,让开发者无需区分开发生产,开发阶段做的工作,在生产阶段也能用。

其中,vite 在开发阶段可调用的可兼容 rollup 的钩子如下:

  • 启动阶段:options 和 buildStart 钩子(在生产阶段为服务启动,生产阶段为脚本执行)
  • 请求响应阶段:浏览器发送请求,vite 会一次调用 resolveId => load => transform (生产阶段无此概念,最多就是 watch 模式监听源文件的变化,再次触发构建流程)
  • 关闭阶段:buildEnd => closeBundle(生产阶段中为脚本执行结束,开发阶段服务器关闭)

除上述钩子外,其余 rollup 均无法在生产阶段生效。如 moduleParsed、renderChunk 等,其实这块也很好理解,vite 的最大优势就是按需加载,只有当网页请求才会对应加载新的模块,因此开发阶段都没有 chunk 或者 bundle 的概念,最多处理下依赖解析工作 如 resovleId。

# 2.开发实战

# 问题2:Vite独有的插件Hook有哪些,简述作用?

vite 独有的钩子有 5 个:

  1. config :进一步修改配置。

    有些配置无法一开始就在 vite.config.ts 中配置好,或者我们封装 config.ts 时,希望对外暴露一个最简易的配置文件。

    以下为推荐写法,返回的是一个对象是,vite 内部会自动 深合并。

    // 返回部分配置(推荐)
    const editConfigPlugin = () => ({
      name: 'vite-plugin-modify-config',
      config: () => ({
        alias: {
          react: require.resolve('react')
        }
      })
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    当然也可以直接修改 config 对象

    const mutateConfigPlugin = () => ({
      name: 'mutate-config',
      // command 为 `serve`(开发环境) 或者 `build`(生产环境)
      config(config, { command }) {
        // 生产环境中修改 root 参数
        if (command === 'build') {
          config.root = __dirname;
        }
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    但是对于深合并的逻辑就需要自己处理了

    // 防止出现 undefined 的情况
    config.optimizeDeps = config.optimizeDeps || {}
    config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {}
    config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins || []
    
    1
    2
    3
    4
  2. configResolved:获取完整的配置文件

    可以用此技巧将最终版的conifg 缓存下来,但是注意的是这个钩子后就不要再修改 config ,要改就直接在 config 钩子里头改。

    const exmaplePlugin = () => {
      let config
      return {
        name: 'read-config',
        configResolved(resolvedConfig) {
          // 记录最终配置
          config = resolvedConfig
        },
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  3. configureServer:配置中间件

    常用操作:可以自定义 template html 文件。

    const myPlugin = () => ({
      name: 'configure-server',
      configureServer(server) {
        // 姿势 1: 在 Vite 内置中间件之前执行
        server.middlewares.use((req, res, next) => {
          // 自定义请求处理逻辑
        })
        // 姿势 2: 在 Vite 内置中间件之后执行 
        return () => {
          server.middlewares.use((req, res, next) => {
            // 自定义请求处理逻辑
          })
        }
      }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    上面的例子还是太抽象了,可以参考我的另一个项目:

    hello-island-dev-indexHtml (opens new window)

    实现功能:通过 island dev docs 启动项目时,自动加载一个 index.html 模板代码兜底,这部分代码需要卸载 return 的回调函数中 ()=>{} 。

    export function pluginIndexHtml(): Plugin {
      return {
        name: "island:index-html", 
        configureServer(server) {
          return () => {
            server.middlewares.use(async (req, res, next) => {
              let html = await readFile(DEFAULT_HTML_PATH, "utf-8");
              /* 将 html 返回给 server */
              try {
                html = await server.transformIndexHtml(
                  req.url,
                  html,
                  req.originalUrl
                );
                res.statusCode = 200;
                res.setHeader("Content-Type", "text/html");
                res.end(html);
              } catch (e) {
                return next(e);
              }
            });
          };
        },
      };
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    hello-island-dev-config (opens new window)

    读取配置文件时,除了需要启动一个开发服务器,还使用sirv 中间件对静态资源进行代理。

    注:这里不能直接使用默认的 vite dev 作为静态服务器,因为当访问时,会直接下载资源文件。

    当时在开发时就发现这个问题,这样处理开发阶段图片能正常访问,生产阶段需要手动将 public 文件夹移动到产物中。

    export function pluginConfig(
      config: SiteConfig,
      restartServer?: () => Promise<void>
    ): Plugin {
      return {
        name: "island:config",
        configureServer(server) {
          const publicDir = path.join(config.root, "public");
          if (fs.pathExistsSync(publicDir)) {
            server.middlewares.use(sirv(publicDir));
          }
        },
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  4. transformIndexHtml:当去请求一个 html 时,提供了一套更为灵活的转换方式。

    const htmlPlugin = () => {
      return {
        name: 'html-transform',
        transformIndexHtml(html) {
          return html.replace(
            /<title>(.*?)</title>/,
            `<title>换了个标题</title>`
          )
        }
      }
    }
    // 也可以返回如下的对象结构,一般用于添加某些标签
    const htmlPlugin = () => {
      return {
        name: 'html-transform',
        transformIndexHtml(html) {
          return {
            html,
            // 注入标签
            tags: [
              {
                // 放到 body 末尾,可取值还有`head`|`head-prepend`|`body-prepend`,顾名思义
                injectTo: 'body',
                // 标签属性定义
                attrs: { type: 'module', src: './index.ts' },
                // 标签名
                tag: 'script',
              },
            ],
          }
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
  5. handleHotUpdata:vite 的 hmr 是模块化加载的最大亮点哈。

    获取热更新的上下文,并进行自定义的热更新处理,或热更模块的过滤

    const handleHmrPlugin = () => {
      return {
        async handleHotUpdate(ctx) {
          // 需要热更的文件
          console.log(ctx.file)
          // 需要热更的模块,如一个 Vue 单文件会涉及多个模块
          console.log(ctx.modules)
          // 时间戳
          console.log(ctx.timestamp)
          // Vite Dev Server 实例
          console.log(ctx.server)
          // 读取最新的文件内容
          console.log(await read())
          // 自行处理 HMR 事件, 可后续捕获(在 island.js 中就监听 mdx 的变化)
          ctx.server.ws.send({
            type: 'custom',
            event: 'special-update',
            data: { a: 1 }
          })
          return []
        }
      }
    }
    
    // 前端代码中加入
    if (import.meta.hot) {
      import.meta.hot.on('special-update', (data) => {
        // 执行自定义更新
        // { a: 1 }
        console.log(data)
        window.location.reload();
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

# 问题3:Vite中插件Hook执行顺序是怎样的?

执行仓库中:npm run lifecycle 脚本

import { Plugin } from "vite";
export default function viteLogLifeCycle(options = {}): Plugin {
  return {
    name: "vite-plugin-log-lifecycle",
    // Vite 独有钩子
    config(config) {
      console.log("config");
    },
    // Vite 独有钩子
    configResolved(resolvedCofnig) {
      console.log("configResolved");
    },
    // 通用钩子
    options(opts) {
      console.log("options", opts);
      return opts;
    },
    // Vite 独有钩子
    configureServer(server) {
      console.log("configureServer");
      console.log("3s 后自动关闭");
      setTimeout(() => {
        // 手动退出进程
        process.kill(process.pid, "SIGTERM");
      }, 3000);
    },
    // 通用钩子
    buildStart() {
      console.log("buildStart");
    },
    // 通用钩子
    buildEnd() {
      console.log("buildEnd");
    },
    // 通用钩子
    closeBundle() {
      console.log("closeBundle");
    },
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

结果如下:

config
configResolved
options {}
configureServer
3s 后自动关闭
buildStart
Port 5173 is in use, trying another one...

  VITE v4.2.1  ready in 252 ms

  ➜  Local:   http://127.0.0.1:5174/
  ➜  Network: use --host to expose
  ➜  press h to show help
buildEnd
closeBundle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 问题4:插件enforce属性的作用和取值?

以上为内部的插件执行图,一般为普通用户插件,根据测试执行次序取决于数组中的书写次序。

通过插件中指定 {enforce: "post"} 可以提到不仅可以将插件安排在末尾,并且要晚于内置的vite 生产环境的钩子,相当于兜底。

举例来说:在 island 这个项目 (opens new window)中,生产环境由于是 island 架构,所有静态文件均由mpa 打包的静态html 提供,我们在水合的过程只需要提供交互相关的代码,因此需要构建产物中所有的 assets 文件。

此时则可以将插件指定到末尾执行。

# 问题5:如何制定Vite插件的应用场景(开发环境或生产环境)?

默认情况下,vite 插件会同时在开发和生产环境中生效,但仍可以通过 apply 属性决定场景。

{
  // 'serve' 表示仅用于开发环境,'build'表示仅用于生产环境
  apply: 'serve'
}
1
2
3
4

一开始不是很理解这个钩子作用的,因为在前面不是钩子的执行环境已经区分好了吗?比如 renderChunk 就不会再 dev 环境触发,因此这里的 apply 这里主要是为了区分 dev 和 prod 同时触发的场景。

比如我的另一个项目中:hello-island-dev (opens new window)

对于 mdx 文件 hmr 时,需要通过 transform 时往其中注入 "import.meta.hot.accept()" 语句。

由于 transform 钩子会同时在 dev 或者 prod 时触发,因此必须通过 apply 以作区分。

除此以外,apply 支持改为函数,此时返回值为 true or false

apply(config,{command}){
  return command === "build" & !config.build.ssr
}
1
2
3

# 问题6:使用过vite-plugin-inspect插件吗?作用是什么?

用过,直接 vite-plugin-inspect

配置如下:

// vite.config.ts
import inspect from 'vite-plugin-inspect';

// 返回的配置
{
  plugins: [
    // 省略其它插件
    inspect()
  ]
}
1
2
3
4
5
6
7
8
9
10

启动后,还可以获取完整存在的内置的插件耗时面板:

可以发现,其中的 pre 插件有:

  1. vite:react-babel react代码转化(不知道和下面有啥区别)
  2. vite:react-refresh 控制react 组件刷新的。
  3. vite-plugin-inspect 面板插件
  4. vite:react-jsx react 代码转化

# 3.插件实战

# 问题7:请阐述如何开发虚拟模块插件?

代码见仓库 (opens new window)

与 rollup 开发基本没区别,只有两处需要注意:

  1. vite 插件需要添加前缀:"\0"

  2. 在 vite-env.d.ts 中添加对应的 typescript 定义

    declare module "virtual:*" {
      const data: any;
      export default data;
    }
    
    1
    2
    3
    4

完整代码示例:

import { Plugin, ResolvedConfig } from "vite";

/* 虚拟模块名称 */
const virtualModuleId = "virtual:env";

const virtualConfigId = "virtual:config";

export default function virtualModulePlugin(): Plugin {
  let config: ResolvedConfig | null = null;
  return {
    /* 对于 vite 插件推荐使用 vite-plugin 的方式命名 */
    name: "vite-plugin-virtual-module",
    resolveId(id) {
      if (id === virtualModuleId) {
        // Vite 中约定对于虚拟模块,解析后的路径需要加上`\0`前缀
        return "\0" + virtualModuleId;
      }
      if (id === virtualConfigId) {
        return "\0" + virtualConfigId;
      }
    },
    configResolved(c: ResolvedConfig) {
      config = c;
    },
    load(id) {
      /* 加载虚拟模块 */
      if (id === "\0" + virtualModuleId) {
        return `export default ${JSON.stringify(process.env)}`;
      }
      if (id === "\0" + virtualConfigId) {
        return `export default ${JSON.stringify(config.env)}`;
      }
    },
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

启动页面效果,将编译时信息打印如下:

# 问题9:请阐述下如何开发 SVG 插件?

代码见仓库 (opens new window)

实现思路:

  1. 先思考需要用到的钩子? transform(code,id)打印如下:

    code svg 会被 file-loader 处理
    > 'export default "/src/assets/react.svg"'
    
    id 模块路径
    > '/Users/.../14-vite-plugin-development/src/assets/react.svg'
    
    1
    2
    3
    4
    5
  2. 根据 id 获取 svg 图片内容

     const svg = await readFile(id, "utf8"); 
    
    1
  3. 使用 @svgr/core 去将 svg 转化为 react 组件。

    经测试 @svgr/core 需要使用 6 版本,7 版本有问题,见测试代码 (opens new window)

    /* 注: 需安装 6 版本  */
    const svgrTransform = await import("@svgr/core");
    
    /* 利用 `@svgr/core` 将 svg 转化为 React 组件 */
    const svgrResult = await svgrTransform.transform(
      svg,
      { icon: true },
      { componentName: "ReactComponent" },
    );
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    此时接受额外 options 参数,支持 url 参数,此时需要转译下代码:

    /* 当为 url 时,仍将组件格式导出,以 url 的形式默认导出 */
    if (defaultExport === "url") {
      componentCode += code;
      componentCode = componentCode.replace(
        "export default ReactComponent",
        "export { ReactComponent }",
      );
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  4. 由于 react 组件无法直接渲染在页面上,因此需要转化下 jsx 转化为 js 代码。

    此时会个问题?用什么转?

    答:使用 esbuild.transform(code,{loader:"jsx"}) 的 loader 转

    /* 这里:使用 esbuild(起的 babel 作用) 处理 jsx 代码 */
    const esbuildPackagePath = resolve.sync("esbuild", {
      basedir: resolve.sync("vite"),
    });
    
    1
    2
    3
    4

    由于 vite 默认安装 esbuild ,如何使用内部的 esbuild?

    答:在 esm 中可使用 resolve.sync 找 esbuild;在 cjs 可直接使用 require.resolve

    /* 这里:使用 esbuild(起的 babel 作用) 处理 jsx 代码 */
    const esbuildPackagePath = resolve.sync("esbuild", {
      basedir: resolve.sync("vite"),
    });      
    
    1
    2
    3
    4

完整代码:

interface SvgrOptions {
  /* 导出为一个 url 还是一个 React 组件 */
  defaultExport?: "url" | "component";
}

export default function viteSvgrPlugin(options: SvgrOptions = {}): Plugin {
  const { defaultExport = "component" } = options;
  return {
    name: "vite-plugin-svgr",
    async transform(code, id) {
      /* 转换逻辑 svg => React 组件 */
      /* 1.根据 id 过滤出 svg 资源 */
      if (path.extname(id) !== ".svg") {
        return code;
      }

      /* 注: 需安装 6 版本  */
      const svgrTransform = await import("@svgr/core");

      /* 这里:使用 esbuild(起的 babel 作用) 处理 jsx 代码 */
      const esbuildPackagePath = resolve.sync("esbuild", {
        basedir: resolve.sync("vite"),
      });
      const esbuild = await import(esbuildPackagePath);

      // 2. 读取 svg 文件内容;
      const svg = await readFile(id, "utf8");

      /* 3. 利用 `@svgr/core` 将 svg 转化为 React 组件 */
      const svgrResult = await svgrTransform.transform(
        svg,
        { icon: true },
        { componentName: "ReactComponent" },
      );

      let componentCode = svgrResult;
      /* 4. 当为 url 时,仍将组件格式导出,以 url 的形式默认导出 */
      if (defaultExport === "url") {
        componentCode += code;
        componentCode = componentCode.replace(
          "export default ReactComponent",
          "export { ReactComponent }",
        );
      }
      /* 5. 利用 esbuild 将组件中的 jsx 代码转译为浏览器可运行的代码 */
      /* 即使用 React.createElement 去处理 */
      const result = await esbuild.transform(componentCode, {
        loader: "jsx",
      });
      return {
        code: result.code,
        map: null, //
      };
    },
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

代码使用:

import ReactLogo from "./assets/react.svg";
function App() {
  return (
    <div className="App">
      <ReactLogo />
    </div>
  );
}
export default App;
1
2
3
4
5
6
7
8
9
编辑 (opens new window)
上次更新: 2023/04/06, 20:04:00
11-Rollup打包机制及插件开发
13-Vite热更新

← 11-Rollup打包机制及插件开发 13-Vite热更新→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式