目 录CONTENT

文章目录

Vite插件开发

Hello!你好!我是村望~!
2022-12-30 / 0 评论 / 0 点赞 / 356 阅读 / 7,524 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

插件API文档

一个简单的插件示例

Vite 插件与 Rollup 插件结构类似,为一个name和各种插件 Hook 的对象:

{
  // 插件名称
  name: 'vite-plugin-xxx',
  load(code) {
    // 钩子逻辑
  },
}

如果插件是一个 npm 包,在package.json中的包命名也推荐以vite-plugin开头

一般情况下因为要考虑到外部传参,我们不会直接写一个对象,而是实现一个返回插件对象的工厂函数,如下代码所示:

// myPlugin.js
export function myVitePlugin(options) {
  console.log(options)
  return {
    name: 'vite-plugin-xxx',
    load(id) {
      // 在钩子逻辑中可以通过闭包访问外部的 options 传参
    }
  }
}

使用方式

// vite.config.ts
import { myVitePlugin } from './myVitePlugin';
export default {
  plugins: [myVitePlugin({ /* 给插件传参 */ })]
}

插件 Hook 介绍

1. 通用 Hook

Vite 开发阶段会模拟 Rollup 的行为

其中 Vite 会调用一系列与 Rollup 兼容的钩子,这个钩子主要分为三个阶段:

  • 服务器启动阶段: optionsbuildStart钩子会在服务启动时被调用。
  • 请求响应阶段: 当浏览器发起请求时,Vite 内部依次调用resolveIdloadtransform钩子。
  • 服务器关闭阶段: Vite 会依次执行buildEndcloseBundle钩子。

除了以上钩子,其他 Rollup 插件钩子(如moduleParsedrenderChunk)均不会在 Vite 开发阶段调用。因为 Vite 为了性能会避免完整的 AST 解析。

而生产环境下,由于 Vite 直接使用 Rollup,Vite 插件中所有 Rollup 的插件钩子都会生效。

2. 独有 Hook

接下来给大家介绍 Vite 中特有的一些 Hook,这些 Hook 只会在 Vite 内部调用,而放到 Rollup 中会被直接忽略。

2.1 处理配置文件内容: config 钩子

Vite 在读取完配置文件(即vite.config.ts)之后,会拿到用户导出的配置对象,然后执行 config 钩子。在这个钩子里面,你可以对配置文件导出的对象进行自定义的操作,如下代码所示:

// 返回部分配置(推荐)
import path from 'path';
const editConfigPlugin = () => ({
  name: 'vite-plugin-modify-config',
  config: () => ({
    resolve: {
      alias: {
        assets: path.resolve('src/assets')
      }
    }
  })
});
export default editConfigPlugin;

官方推荐的姿势是在 config 钩子中返回一个配置对象,这个配置对象会和 Vite 已有的配置进行深度的合并

可以看下面的两个相同的配置!

image-20221228105535644

拿去同时使用的时候,会吧这些配置进行合并!所以 alias 别名配置在使用的时候依然是可以全部生效的!

import React from 'react';
import style from 'src/App.module.scss';
import bg from 'src/assets/images/bg.png';
import { ReactComponent as MySvg } from 'assets/svgs/s1.svg';
function App() {
  return (
    <div className={style.logo}>
      <MySvg />
      <img src={bg} width="100px" height="100px" alt="" />
    </div>
  );
}

export default App;

image-20221228105815839

可以看到整个页面没有任何问题!

不过你也可以通过钩子的入参拿到 config 对象进行自定义的修改,如下代码所示

const mutateConfigPlugin = () => ({
  name: 'mutate-config',
  // command 为 `serve`(开发环境) 或者 `build`(生产环境)
  config(config, { command }) {
    // 生产环境中修改 root 参数
    if (command === 'build') {
      config.root = __dirname;
    }
  }
})

在一些比较深层的对象配置中,这种直接修改配置的方式会显得比较麻烦,如 optimizeDeps.esbuildOptions.plugins,需要写很多的样板代码,类似下面这样:

// 防止出现 undefined 的情况
config.optimizeDeps = config.optimizeDeps || {}
config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {}
config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins || []

因此这种情况下,建议直接返回一个配置对象,这样会方便很多:

config() {
  return {
    optimizeDeps: {
      esbuildOptions: {
        plugins: []
      }
    }
  }
}

2.2 拿到最终配置内容的钩子: configResolved

Vite 在解析完配置之后会调用configResolved钩子,这个钩子一般用来记录最终的配置信息,而不建议再修改配置,用法如下,依旧拿我们上面的插件案例来看

// 返回部分配置(推荐)
import path from 'path';
const editConfigPlugin = () => ({
  name: 'vite-plugin-modify-config',
  config: () => ({
    resolve: {
      alias: {
        assets: path.resolve('src/assets')
      }
    }
  }),
  configResolved(resolvedConfig) {
    // 记录最终配置
    console.log(resolvedConfig.resolve.alias);
  },

});
export default editConfigPlugin;

这里我们打印了,配置的 resolve.alias

[
  { find: 'src', replacement: '/Users/hope/vite-sdy/src' },
  { find: 'assets', replacement: '/Users/hope/vite-sdy/src/assets' },
  { find: /^[\/]?@vite\/env/, replacement: [Function: replacement] },
  { find: /^[\/]?@vite\/client/, replacement: [Function: replacement] }
]

可以直观的看到,将插件和原有的配置文件的配置的确合在了一起,而且也看到了一些内置的别名配置!

【使用闭包的方式在其他的钩子中访问到配置信息!】

// 返回部分配置(推荐)
import path from 'path';
const editConfigPlugin = () => {
  let config = {};
  return {
    name: 'vite-plugin-modify-config',
    config: () => ({
      resolve: {
        alias: {
          assets: path.resolve('src/assets')
        }
      }
    }),
    configResolved(resolvedConfig) {
      // 记录最终配置
      config = resolvedConfig;
    },
    // 在其他钩子中使用存储的配置
    transform(code, id) {

      if (config.command === 'serve') {
        console.log("build");

      } else {
        // build: 由 Rollup 调用的插件
        console.log("rollup");
        
      }
    }
  };
};
export default editConfigPlugin;

image-20221228111751393

2.3 获取 Dev Server 实例: configureServer

这个钩子仅在开发阶段会被调用,用于扩展 Vite 的 Dev Server,一般用于增加自定义 server 中间件,如下代码所示:

是用于配置开发服务器的钩子。最常见的用例是在内部 connect 应用程序中添加自定义中间件:

const myPlugin = () => ({
  name: 'configure-server',
  configureServer(server) {
    server.middlewares.use((req, res, next) => {
      // 自定义请求处理...
    })
  },
})

注入后置中间件 ( 自定义中间件 后 return 一个中间件出去! )

configureServer 钩子将在内部中间件被安装前调用,所以自定义的中间件将会默认会比内部中间件早运行。如果你想注入一个在内部中间件 之后 运行的中间件,你可以从 configureServer 返回一个函数,将会在内部中间件安装后被调用:

const myPlugin = () => ({
  name: 'configure-server',
  configureServer(server) {
    // 返回一个在内部中间件安装后
    // 被调用的后置钩子
    return () => {
      server.middlewares.use((req, res, next) => {
        // 自定义请求处理...
      })
    }
  },
})

闭包存储服务器实例对象,供其他钩子去访问

在某些情况下,其他插件钩子可能需要访问开发服务器实例(例如访问 websocket 服务器、文件系统监视程序或模块图)。这个钩子也可以用来存储服务器实例以供其他钩子访问:

const myPlugin = () => {
  let server
  return {
    name: 'configure-server',
    configureServer(_server) {
      server = _server
    },
    transform(code, id) {
      if (server) {
        // 使用 server...
      }
    },
  }
}

注意 configureServer 在运行生产版本时不会被调用,所以其他钩子需要防范它缺失。

2.4 预览服务器实例 configurePreviewServer

configureServer 相同但是作为预览服务器。它提供了一个 connect 服务器实例及其底层的 http server。与 configureServer 类似,configurePreviewServer 这个钩子也是在其他中间件安装前被调用的。如果你想要在其他中间件 之后 安装一个插件,你可以从 configurePreviewServer 返回一个函数,它将会在内部中间件被安装之后再调用:

configurePreviewServer(server) {
  // 姿势 1: 在 Vite 内置中间件之前执行
  server.middlewares.use((req, res, next) => {
    // 自定义请求处理逻辑
    console.log(1);
    next();
  });
  // 姿势 2: 在 Vite 内置中间件之后执行
  return () => {
    server.middlewares.use((req, res, next) => {
      // 自定义请求处理逻辑
    console.log(2);
      next();
    });
  };
}

打包完成后,vite preview 的时候会调用这个钩子函数!

image-20221228141740440

2.4 拦截转换 HTML 内容: transformIndexHtml

这个钩子用来灵活控制 HTML 的内容,你可以拿到原始的 html 内容后进行任意的转换:

基础示例:

const htmlPlugin = () => {
  return {
    name: 'html-transform',
    transformIndexHtml(html) {
      return html.replace(
        /<title>(.*?)<\/title>/,
        `<title>Title replaced!</title>`,
      )
    },
  }
}

image-20221228142418913

可以看到已经替换成功了我们本地的HTML的文件内容!

2.5 热更新钩子: handleHotUpdate

这个钩子会在 Vite 服务端处理热更新时被调用,你可以在这个钩子中拿到热更新相关的上下文信息,进行热更模块的过滤,或者进行自定义的热更处理。下面是一个简单的例子:

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 ctx.read());
  // 自定义处理 HMR 事件
  ctx.server.ws.send({
    type: 'custom',
    event: 'my-plugin-update',
    data: { a: 1 }
  });
  return ctx.modules;
};
  • modules 是受更改文件影响的模块数组。它是一个数组,因为单个文件可能映射到多个服务模块(例如 Vue 单文件组件)。
  • read 这是一个异步读函数,它返回文件的内容。之所以这样做,是因为在某些系统上,文件更改的回调函数可能会在编辑器完成文件更新之前过快地触发,使得 fs.readFile 直接会返回空内容。传入的 read 函数规范了这种行为。

如果你想自定义HMR 处理 !你可以选择使用ws,并且返回空数组,这样就不会有受更改文件去触发热更新了!

async handleHotUpdate(ctx) {
  return [];
}

cancle file effect update

这样就触发不了热更新了,下面我们自己定义一个HMR触发一下哈哈!

async handleHotUpdate(ctx) {
  // 使用ws 监听一个事件,并传递一些参数过去!
  ctx.server.ws.send({
    type: 'custom',
    event: 'my-plugin-update',
    data: { a: 1 }
  });
  return [];
}

然后在前端代码中去监听!

// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.scss';
// 前端代码中加入
if (import.meta.hot) {
  import.meta.hot.on('my-plugin-update', (data) => {
    // 执行自定义更新
    // { a: 1 }
    console.log(data);
    setTimeout(()=>{
      window.location.reload();
    },1000)
  });
}
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <App />
);

custom update event

具体更多细节可以参考一下官方文档!(标题内置链接!)

3. 插件 Hook 执行顺序

这么多的钩子,到底谁先执行、谁后执行呢?

下面,我们就来复盘一下上述的两类钩子,并且通过一个具体的代码示例来汇总一下所有的钩子。我们可以在 Vite 的脚手架工程中新建 test-hooks-plugin.ts:

// test-hooks-plugin.ts
// 注: 请求响应阶段的钩子
// 如 resolveId, load, transform, transformIndexHtml在下文介绍
// 以下为服务启动和关闭的钩子
export default function testHookPlugin() {
  return {
    name: 'test-hooks-plugin',
    // Vite 独有钩子
    config(config) {
      console.log('config');
    },
    // Vite 独有钩子
    configResolved(resolvedCofnig) {
      console.log('configResolved');
    },
    // 通用钩子
    options(opts) {
      console.log('options');
      return opts;
    },
    // Vite 独有钩子
    configureServer(server) {
      console.log('configureServer');
      setTimeout(() => {
        // 手动退出进程
        process.kill(process.pid, 'SIGTERM');
      }, 3000);
    },
    // 通用钩子
    buildStart() {
      console.log('buildStart');
    },
    // 通用钩子
    buildEnd() {
      console.log('buildEnd');
    },
    // 通用钩子
    closeBundle() {
      console.log('closeBundle');
    }
  };
}

将插件加入到 Vite 配置文件中,然后启动,你可以观察到各个 Hook 的执行顺序:

image-20221228161023986

由此我们可以梳理出 Vite 插件的执行顺序:image-20221228161609365

  • 服务启动阶段: configconfigResolvedoptionsconfigureServerbuildStart
    • 读读配置
    • 启动一下服务器
    • 构建开始准备
  • 请求响应阶段: 如果是 html 文件,仅执行transformIndexHtml钩子;对于非 HTML 文件,则依次执行resolveIdloadtransform钩子。相信大家学过 Rollup 的插件机制,已经对这三个钩子比较熟悉了。
    • 解析文件路径
    • 转换
  • 热更新阶段: 执行handleHotUpdate钩子。
  • 服务关闭阶段: 依次执行buildEndcloseBundle钩子。
    • 结束构建

插件应用位置

梳理完 Vite 的各个钩子函数之后,接下来让我们来了解一下 Vite 插件的应用情景应用顺序

默认情况下 Vite 插件同时被用于开发环境和生产环境,你可以通过apply属性来决定应用场景:

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

apply参数还可以配置成一个函数,进行更灵活的控制:

apply(config, { command }) {
  // 只用于非 SSR 情况下的生产环境构建
  return command === 'build' && !config.build.ssr
}

同时,你也可以通过enforce属性来指定插件的执行顺序:

{
  // 默认为`normal`,可取值还有`pre`和`post`
  enforce: 'pre'
}

Vite 中插件的执行顺序如下图所示:

image-20221228162609838

Vite 会依次执行如下的插件:

  • Alias (路径别名)相关的插件。
  • ⭐️ 带有 enforce: 'pre' 的用户插件。
  • Vite 核心插件。
  • ⭐️ 没有 enforce 值的用户插件,也叫普通插件
  • Vite 生产环境构建用的插件。
  • ⭐️ 带有 enforce: 'post' 的用户插件。
  • Vite 后置构建插件(如压缩插件)。

插件开发实战

接下来我们进入插件开发的实战环节中,在这个部分我们将一起编写两个 Vite 插件,分别是虚拟模块加载插件Svgr 插件,你将学会从插件开发的常见套路和各种开发技巧。话不多说,让我们现在开始实战吧。

实战案例 1: 虚拟模块加载

首先我们来实现一个虚拟模块的加载插件,可能你会有疑问: 什么是虚拟模块呢?

作为构建工具,一般需要处理两种形式的模块,一种存在于真实的磁盘文件系统中,另一种并不在磁盘而在内存当中,也就是虚拟模块。(其实就是把一些代码字符串的形式写在插件内部,而不是出现在项目代码中)

通过虚拟模块,我们既可以把自己手写的一些代码字符串作为单独的模块内容,又可以将内存中某些经过计算得出的变量作为模块内容进行加载,非常灵活和方便。

// plugins/virtual-module.ts
import { Plugin } from 'vite';

// 虚拟模块名称
const virtualFibModuleId = 'virtual:fib';

// Vite 中约定对于虚拟模块,解析后的路径需要加上`\0`前缀
const resolvedFibVirtualModuleId = '\0' + virtualFibModuleId;

export default function virtualFibModulePlugin(): Plugin {
  return {
    name: 'vite-plugin-virtual-module',
    resolveId(id) {
      if (id === virtualFibModuleId) {
        return resolvedFibVirtualModuleId;
      }
    },
    load(id) {
      // 加载虚拟模块 字符串代码!
      if (id === resolvedFibVirtualModuleId) {
        return 'export default function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }';
      }
    }
  };
}

然后配置这个插件到配置文件

// vite.config.ts
{
  plugins: [
  	vm()
	],
}

接着我们在项目代码中使用这个虚拟模块!

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import fib from 'virtual:fib';
import './index.scss';

console.log(fib(10));

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <App />
);
image-20221229092327506

这里我们使用了 virtual:fib 这个虚拟模块,虽然这个模块不存在真实的文件系统中,但你打开浏览器后可以发现这个模块导出的函数是可以正常执行的

除接着我们来尝试一下如何通过虚拟模块来读取内存中的变量,比如读取配置文件的内容注入到虚拟模块字符串代码中,使用configResolved 钩子,获取到完整的配置文件,然后通过闭包保存,load的时候返回你自定义任意的配置信息!具体代码如下:

// plugins/virtual-module.ts
import { Plugin, ResolvedConfig } from 'vite';

// 虚拟模块名称
const virtualEnvModuleId = 'virtual:env';

// Vite 中约定对于虚拟模块,解析后的路径需要加上`\0`前缀
const resolvedEnvVirtualModuleId = '\0' + virtualEnvModuleId;

export default function virtualFibModulePlugin(): Plugin {
  
  let config: ResolvedConfig | null = null;
  
  return {
    name: 'vite-plugin-virtual-module',
    configResolved(c: ResolvedConfig) {
      config = c;
    },
    resolveId(id) {
      if (id === virtualEnvModuleId) {
        return resolvedEnvVirtualModuleId;
      }
    },
    load(id) {
      if (id === resolvedEnvVirtualModuleId) {
        return `export default ${JSON.stringify(config)}`;
      }
    }
  };
}

然后在业务代码中使用:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import vmEnv from 'virtual:env';
import './index.scss';

console.log(vmEnv);

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <App />
);

你就可以在页面中拿到所有的vite配置对象了!

image-20221229095718030

Vite 环境变量能正确地在浏览器中打印出来,说明在内存中计算出来的virtual:env模块的确被成功地加载了。

从中你可以看到,虚拟模块的内容完全能够被动态计算出来,因此它的灵活性和可定制程度非常高,实用性也很强,在 Vite 内部的插件被深度地使用,社区当中也有不少知名的插件(如 vite-plugin-windicssvite-plugin-svg-icons等)也使用了虚拟模块的技术。

实战案例 2: Svg 组件形式加载

在一般的项目开发过程中,我们有时候希望能将 svg 当做一个组件来引入,这样我们可以很方便地修改 svg 的各种属性,相比于img标签的引入方式也更加优雅。

但 Vite 本身并不支持将 svg 转换为组件的代码,需要我们通过插件来实现。

接下来我们就来写一个 Vite 插件,实现在 React 项目能够通过组件方式来使用 svg 资源。首先安装一下需要的依赖:

  • resolve 实现了节点require.resolve()算法,这样你可以require.resolve()代表一个文件异步和同步
  • @svgr/core SVGR 的nodejs版本文档!svg转换成React组件 svgr文档

第一步,我们先把 svg 转成 react 组件

async transform(code, id) {
  // 1. 获取 svg  模块路径 (id)! 
  if (!(id as string).endsWith('.svg')) {
    return code;
  }
	console.log(id); // 这里的id 输出的就是 svg 的路径  /Users/hope/vite-sdy/src/assets/svgs/s1.svg
  
  // 2. 然后 使用node 的文件模块 读取 svg 源码内容
  const svg = await fs.promises.readFile(id, 'utf8');
  
  // 3. 利用 `@svgr/core` 将 svg 转换为 React 组件代码
  const svgrResult = await svgrTransform(
    svg,
    {},
    { componentName: 'ReactComponent' }
  );
}

来我们打印一下这个 svgrResult,可以看到的确是 React 组件代码!

import * as React from "react";
const ReactComponent = props => <svg xmlSpace="preserve" style={{
  enableBackground: "new 0 0 1024 1024"
}} viewBox="0 0 1024 1024" y="0px" x="0px" xmlnsXlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" id="\u56FE\u5C42_1" {...props}><style type="text/css">{"\n\t.st0{fill:#4966A2;}\n\t.st1{fill:#3D5890;}\n\t.st2{fill:#F3B5AF;}\n\t.st3{fill:#FFFFFF;}\n\t.st4{fill:#E08476;}\n\t.st5{fill:#FFEEF0;}\n\t.st6{fill:#DCD3D4;}\n\t.st7{fill:#142546;}\n"}</style><path d="M238.2,402.5l24.5,61.5c0,0,54.6,1.3,158.8-21.7c0,0,50.7,266.8,146.4,266.8s122.7-93,122.7-93 s-73.4-23.7-97.2-101.8L488.2,366.1c0,0-32.9-31.9-86.3-14.8L238.2,402.5z" className="st0" /><path d="M245.3,402.5c0,0,99.6,50.8,219.8,12.1l3.4,8.9c0,0-101.9,43.7-200.1,39.3L245.3,402.5z" className="st1" /><path d="M423.6,451.9c0,0,67.7,405,249.4,205.7c0,0-147.2,66.7-214.7-227.6l-36.6,12.4L423.6,451.9z" className="st1" /><path d="M653.3,477.5l-58.1,42.1c0,0,28.9,90.7,95.4,96.4l56.3-46.1L653.3,477.5z" className="st2" /><path d="M205.8,414.1l32.4-11.6l24.6,64.1h-36.2L205.8,414.1z" className="st3" /><path d="M878.5,197.5c0,0,34.3-51.9,93-16.9c26,15.7,40.8,44.9,37.9,75.2c-2.5,27.4-10.7,66.4-33.6,113.1 c-6.4,12.8-11.8,26.1-16.3,39.8c-10.7,33.4-38.4,97.4-88.3,75c0,0,29.8-18.4,23.9-65.6s29.4-83.7,29.4-83.7s51.5-72.7,1.6-110.8 c-49.9-38.1-50.5-25.8-50.5-25.8" className="st0" /><path d="M941.5,241.4c-22.9-35-62.9-44-62.9-44s-51.9,77.2-30.5,120.1c2.3,4.7,5.3,9.1,9,12.9l-21.8,29.6l56,45.6 c0,0,12-46.2,26.9-65.2c1.6-0.5,2.5-0.9,2.5-0.9S974.7,292,941.5,241.4L941.5,241.4z" className="st4" /><path d="M205.8,414.2c0,0-171.5,74.3-192.9,84.7c0,0,13.4,28.4,94,0c0,0,34.8,11.1,69.5,7.7 c34.8-3.4,45.6-50.8,45.6-50.8L205.8,414.2z M1003.4,223.5l-95,22l-179.2,7.3c0,0-55.7,11.5-23.8,57l65.1,92.9 c0.2,2.4,3.1,31.4,29,45.7c16.1,8.9,38.4,10.8,56.1-1c0.7-0.4,1.3-0.9,1.9-1.3c23.4-17.3,23.4-52.6,0.7-70.9l-4.1-3.4l-79.6-68.1 l142.5-17.3c0,0,38.8,15.1,65.1-21.1s29.8-38.1,29.8-38.1L1003.4,223.5z" className="st2" /><path d="M757.7,384.4c0,0-72.4,4.1-104.3,93.1l93.5,92.4c0,0,160.3-76.2,148.4-152c-0.6-3.5-1.6-6.8-2.9-10.1 c-6.2-14.8-18.1-26.3-33.1-32c13.4,25.1,9.9,51.1-2.9,64.4c-7.2,7.5-17.3,9.5-22.9,10.7c-22.8,4.7-41.4-5.1-47.6-8.7 c0,0-17-18.4-15.1-39.6L757.7,384.4z" className="st5" /><path d="M766.3,557.1l-16.6,14.3l-93.2-95.9c16.6-46.4,44.2-69.7,66.8-81.4c-14.2,18.5-22,41.2-22,64.6 C701.2,502.3,740.6,531,766.3,557.1L766.3,557.1z" className="st6" /><path d="M935,697.9h73.8v24.5H935L935,697.9z M1.6,697.9h73.8v24.5H1.6V697.9z M1011.5,197.3 c-17.8-26-51.1-43.1-82.9-42.3c-26,0.3-50,14.1-63.5,36.3l-0.6,0.8v0.1c-0.2,0.4-0.5,0.8-0.7,1.2l-2,4.4c-0.1,0.3-0.3,0.6-0.4,0.9 l-18.1,37l-112.1,4.5c-1.1,0-2.2,0.1-3.3,0.2c-16.9,1.6-31.6,12.4-38.3,28.1c-7,16-4.8,34.5,5.8,48.4l41.5,59.3 c-36.2,10.7-66.1,36.2-82.4,70.2l-11.1,23.1l-40.9,32.2l-0.6,0.6c-0.6,0.7-1.5,1-2.5,0.9c-0.9-0.1-1.7-0.5-2.2-1.3l-91.6-133.9 c-20.6-30.3-58.6-43.7-93.6-33l-210,67.2c-3.4,1.1-6.8,2.3-10,3.6l-20.4,8.1L35.1,476.7l-16.3,3c-12.4,2.4-20.6,14.5-18.2,26.9 c2.2,11.2,12.3,19.1,23.7,18.6l7.8-0.4c6.9,2.3,14.1,3.4,21.4,3.4c12.1,0,27.7-3,45.1-13.6c10.4-6.4,19.9-3.9,35.7,1 c14.8,4.6,33.1,10.2,53.3,1.6c16.1-7.1,28.1-21.2,32.4-38.2l14.7,0.7h0.5c67.5,0.6,132.4-12.1,177.4-23.7l25.3,90.9 c10.4,40.3,25.6,79.1,45.3,115.8c7.2,13.5,17.1,25.5,29,35.1h-385v24.5h761.8v-24.5H652.4c19.4-14.6,34.1-34.5,42.4-57.2l6.1-16.9 c6.3-4.8,22.5-17.3,44.2-33.6c29.2-22,59.4-42.7,90.4-62c13.6-8.5,26.1-18.7,37.2-30.3c12.4,4.9,26.3,4.8,38.6-0.2 c20.4-8,34.5-26.6,39.6-41.4l2.4-5.9l54.5-132.5l0.1-0.3c9.1-23.5,14.5-48.3,16-73.5C1025.4,227.4,1021,210.9,1011.5,197.3z  M196.8,471.4c-2.2,10.3-9.1,19-18.7,23.3c-11.8,5.1-23.2,1.6-36.3-2.5c-15.7-4.9-35.3-10.9-55.7,1.5c-26.8,16.3-45.8,8-46.5,7.7 l-0.8-0.4l3.8-0.7l138.5-63.5l16.9-6.7l11,26.2C202.8,459.2,198.3,464.7,196.8,471.4L196.8,471.4z M929.2,179.5 c23.4-0.6,49.1,12.4,62.1,31.6c0.2,0.3,0.4,0.7,0.6,1L943.5,224c-7.9-9.7-17.9-17.6-29.2-23l-18.4-8.9 C905.1,184.1,917,179.6,929.2,179.5z M881.8,212.6l22,10.6c4.4,2.1,8.5,4.7,12.3,7.8l-9.3,2.3l-35.5,1.4L881.8,212.6z M235.8,455.2 h-0.7l-13.9-33.1l10.2-3.3l14.6,36.4C242.7,455.2,239.2,455.2,235.8,455.2L235.8,455.2z M671.7,632.2c-10.8,29.7-35,52.6-65.2,61.9 c-1.2,0.4-2.5,0.7-3.8,1.1c-6.4,1.7-12.9,2.6-19.5,2.7h-3.4c-31.5-0.9-60.2-18.8-74.8-46.7c-18.7-34.9-33.2-72-43.2-110.4 l-25.3-91.2c22-6.4,35.7-11.4,37.3-12l-8.7-23c-1,0.4-90.7,33.3-193,39.6L255,411.3l164.3-52.6c24.8-7.5,51.6,2,66.2,23.4 l91.6,133.9c1.7,2.4,3.8,4.6,6.1,6.4c6.1,17.2,31.5,77.1,91.6,101.2L671.7,632.2z M688.2,602.4c-43.6-15.6-67.2-55.2-77.2-76.9 c2.9-1.3,5.6-3.1,8-5.4l33.4-26.3l77.7,76.7C716.1,581.2,702.1,591.8,688.2,602.4L688.2,602.4z M822.5,507.2 c-24.7,15.4-48.9,31.7-72.5,48.7L668.2,475l8.6-17.8c14.6-30.4,42.4-52.3,75.4-59.3l6.6,9.4c1.2,8.6,5.8,29.8,22.9,43.9 c10.5,8.7,23.6,13,39,13c4.2,0,8.5-0.3,12.7-0.9l-3.4-24.3c-13.8,1.9-24.8-0.3-32.6-6.7c-13.1-10.7-14.5-30-14.5-30.2l-0.2-3.5 l-67.1-95.8l-0.3-0.4c-5.3-6.8-6.4-16-2.9-23.9c3.1-7.5,10.1-12.7,18.1-13.4l1.9-0.1l177.9-7.2l87.7-21.8h0.2 c0.1,0.3,0.1,0.6,0.1,0.9c-0.1,0.2-0.2,0.4-0.4,0.6c-2.7,2.5-17.3,13.8-29,23l-10.4,8.2c-8.9,7-20.5,9.7-31.6,7.5l-9.2-1.8 l-164.7,20l3,24.3l12.4-1.5l87.9,72.3l1.3,0.6c10.3,5,18.3,13.8,22.3,24.5c0.6,1.7,1.2,3.4,1.6,5.2c0.2,3.8,0.6,6.1,0.7,6.5h0.4 c1,10-1.4,20-6.8,28.5l-0.4,0.6C861.7,476.3,843.7,494,822.5,507.2L822.5,507.2z M881,328.3c-9.9-4.7-18-12.5-23.1-22.2l58.6-7.1 l5.3,1.1c3.3,0.6,6.6,1,9.9,1.2l-6.1,12.6c-3.1,6.5-8.4,11.7-14.8,14.7c-3.5,1.7-7.2,2.7-11,3.1C893.3,332.3,886.8,331.1,881,328.3 L881,328.3z M893.9,356.3c-3.7,7.5-6.7,15.3-8.8,23.4c-4.7-4.4-10-8.2-15.7-11.1l-15.4-12.7l7.3-10.6c2.9,1.9,5.9,3.6,9,5.1 C877.7,353.9,885.8,355.9,893.9,356.3L893.9,356.3z M843.2,328.4l-8.2,11.9l-33.3-27.4l30.7-3.7C835,316.1,838.7,322.6,843.2,328.4z  M930.5,441l-2.5,6.4l-0.2,0.6c-3.1,9.3-13.1,21.8-25.4,26.7c-3.9,1.6-8.1,2.3-12.3,2c2-2.7,3.9-5.5,5.7-8.4l0.4-0.6 c9.7-15.3,13.1-33.7,9.4-51.5c-0.6-13.3,0.9-46,27.3-72.9c6.3-5.1,11.4-11.6,14.9-18.9l6.7-13.8c2.2-4.7,4-9.6,5.3-14.7 c4.9-2.2,9.4-4.9,13.6-8.2l10.4-8.2L996,270c-2.5,13.1-6.2,25.9-10.9,38.3L930.5,441z" className="st7" /></svg>;
export default ReactComponent;

下一步呢,我们还需要将React 组件代码 转换成浏览器可以运行的代码!

// 利用 esbuild,将组件中的 jsx 代码转译为浏览器可运行的代码;
const result = await vite.transformWithEsbuild(svgrResult, id, {
  loader: 'jsx'
});
let resultCode = result.code;
resultCode += `
  //   自定插件SVG组件生成! ${new Date()}
`;
return {
  code: resultCode,
  map: null // TODO
};

然后拿去业务视图组件去使用

import React from 'react';
import style from 'src/App.module.scss';
import MySvg from 'assets/svgs/s1.svg';

function App() {
  return (
    <div className={style.logo}>
      <MySvg />
    </div>
  );
}

export default App;

image-20221229152954636

💋💋调试插件技巧💋💋(插件vite-plugin-inspect )

在开发调试插件的过程,推荐在本地装上vite-plugin-inspect插件,并在 Vite 中使用它:

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

// 返回的配置
{
  plugins: [
    // 省略其它插件
    inspect()
  ]
}

这样当你再次启动项目时,会发现多出一个调试地址:

你可以通过这个地址来查看项目中各个模块的编译结果:

image-20221229155048976

点击特定的文件后,你可以看到这个模块经过各个插件处理后的中间结果,如下图所示:

image-20221229155129022

通过这个面板,我们可以很清楚地看到相应模块经过插件处理后变成了什么样子,让插件的调试更加方便。

如果遇到了一些类型报错

[虚拟模块加载中的类型报错 👈](# 实战案例 1: 虚拟模块加载)

这个地方的导入虚拟模块的时候会遇到类型报错!

image-20221229091740125

这种情况我们在去声明一个 针对虚拟模块的类型就好了!

// src/types/virtual.d.ts
declare module 'virtual:*' {
  const anyLib;
  export default anyLib;
}

image-20221229093705032

如果遇到了这个eslint的报错,那么需要去改eslint的配置中的rules,允许any作为类型就可以了!

rules: {
  "@typescript-eslint/no-explicit-any": "off"
}
0

评论区