Skip to content

Umi 插件实战教程

Posted on:May 3, 2023 at 02:24 PM

引言

笔者最近开发了一款 umi 插件:plugin-umi-cmdk,该插件的功能主要是:在 umi 项目里可以方便的集成 cmd + k ,实现菜单等搜索

主体功能并不复杂,但是在集成作为 umi 插件过程中踩了不少坑,主要是 umi 官方文档的, 开发插件 | UmiJS实属写得烂,看完之后根本无法上手。

所以写一篇完整的插件开发教程,手把手上手 umi 插件开发。

准备工作

创建项目

新建一个文件夹umi-plugin-demo,直接通过 umi 的官方模版进行创建

yarn create umi

之后选择模板的时候选择: Umi Plugin

Pasted image 20230326160516

创建 example 目录用于测试

然后创建完之后在 umi-plugin-demo 的根目录新建一个 example 文件夹,用于测试。

将 example 初始化成一个 umi 项目:

cd example
// 然后
yarn create umi

根据你的需求选择一个模板

我选了 Ant Design Pro ,现在整个目录结构大致是这样。

挂载插件

1、在 src/index.ts 里增加 log

import type { IApi } from 'umi';

export default (api: IApi) => {
  // See https://umijs.org/docs/guides/plugins
  api.onStart(() => {
    console.log("欢迎关注前端桃园!");
  });
};

这个代码的意思就是在插件启动的时候打一个 log「插件开始加载了!!!」。

2、在 example/.umirc 里引入插件

import { defineConfig } from '@umijs/max';
import { join } from 'path';

export default defineConfig({
  plugins: [join(__dirname, '../src/index.ts')],
  // 其他的配置
 })

通过 plugins 这个配置,将插件文件进行引入,在启动 example 项目的时候插件就会被加载

3、查看 logo 然后在 example 下,通过 npm start 启动项目,即可看到控制台的 log。

当我们看到控制台输出了想要的日志,到这一步,准备工作已经就绪,接下来就可以开始写插件了。

更多挂载方式

除了通过 plugins 配置项挂载插件,umi 还提供了一种约定式的挂载方式。

umi 体系中,约定根目录下存在 plugin 文件夹作为本地插件的约定入口。只要存在该文件夹,其中的插件就会被自动挂载,无需再进行额外的配置。

例如,在我们的示例中,可以直接在 example 目录下创建一个 plugin.ts 文件,即可将插件挂载到 umi 中,无需在 .umirc 配置文件中添加插件配置。这种方式通常用于本地测试插件。

编写插件

一般的插件机制是通过暴露钩子来实现的,钩子会在运行时执行并提供一些属性供插件开发者使用。插件开发者可以在钩子中实现想要的功能,从而扩展插件的功能。因此,插件开发就是在钩子中编写代码实现自定义功能的过程。

所以一般在编写框架插件的时候就需要先了解一下该框架提供了哪些钩子,这个决定了开发者可以做哪些扩展的事情。

一般开发插件的基本流程如下: 1、 浏览 umi 提供的插件 API 2、确定插件的目的和功能 3、找到需要的插件 API 和生命周期方法

功能分析

我以我写的插件 cmdk 插件举例分析。

cmdk 搜索那个就是一个纯 react 组件的功能,作为插件的话其实就是想办法把这个插件在 umi 运行的时候就插入到整个 umi 的最外层。

另外就是支持配置一些参数,比如有些用户不想用 cmd + k,想用 cmd + m 来调出弹框。那么就需要配置快捷键的 key。

大致就这两个功能吧。

  1. 将 cmkd 的 react 组件插入到 umi。
  2. 支持配置快捷键

需要用到 API

  1. api. describe ()。用于在插件注册阶段执行,用于描述插件或者插件集的 key、配置信息和启用方式等。在 .umirc 配置快捷键的时候需要用到
  2. api. onGenerateFiles ()。生成临时文件的钩子,就是把运行时需要的文件生成到 .umi 目录下的那个钩子。
  3. api. writeTmpFile ().生成临时文件的方法,在 onGenerateFiles 阶段进行调用的,将临时文件写入进去。

看到这些文件,这些就是在 umi 运行时需要用到的文件,都是对应的插件生成的。umi 在运行时这些代码都是会执行的,在我们这里也是需要把 cmdk 那个 react 组件写到这里面来,这样在运行时才能用。

1. 给插件传递属性并做参数校验

比如我们需要将 .umirc 的快捷键配置传递给插件,配置文件大致是这样配置:

export default defineConfig({
  plugins: [join(__dirname, '../src/index.ts')],
  cmdk: {
    keyFilter: "cmd+w",
  },
}

意思就是插件的 key 叫 cmdk,有一个 keyFilter 的配置。 插件里的代码就可以这样的写

import type { IApi } from 'umi';

export default (api: IApi) => {
 api.describe({
    key: 'cmdk',  // 定义插件名称,跟 .umirc 的配置的 key 相同。
    config: {
      schema(joi) {  // 返回值,定义配置的 schema 结构,我们只需要这个对象里有一个 keyFilter 的字符串。
        return joi.object({
          keyFilter: joi.string(),    
        });
      },
    },
    enableBy: api.EnableBy.config,
  })
}

这样就可以实现对配置参数的校验了。我们可以通过 api.userConfig.cmdk 拿到配置。

2. 给 react 组件传递属性

可以通过 api.userConfig.cmdk 拿到了配置参数,最终目的是要传递给 react 组件的。

src 下新建一个文件 cmdk.tpl 用于方 cmdk 的 react 代码。

然后通过 api. writeTmpFile () 将配置参数传进去,在 umi 插件里由于不是 react 组件,没办法通过 props 传递属性,所以只能通过用模板的方式进行传递参数。

umi 插件用的模板引擎是 Mustache 。

所以插件代码这里就可以这么写:

import type { IApi } from 'umi';

export default (api: IApi) => {
  // 其他代码
	api.onGenerateFiles({
		fn() {
		  const runtimeTpl = readFileSync(
			join(__dirname, 'cmdk.tpl'),
			'utf-8',
		  );
		
		  api.writeTmpFile({
			path: 'runtime.tsx',
			content: Mustache.render(runtimeTpl, {
			  props: JSON.stringify(api.userConfig.cmdk)
			}),
		  });
	}
  })
}

这段代码的意思就是在 onGenerateFiles 钩子里,就是生成临时文件的钩子。

先通过 readFileSync 从插件的源码里读取要写入临时文件的模板文件 cmkd.tpl,然后再通过 writeTmpFile 写入到 .umi 下去,同时将 api.userConfig.cmdk 作为参数,写入到 props 这个模板参数里.

umi 会自动写到对应的插件目录下。

cmdk.tpl 里可以这样拿到传递的参数。

const _props =  {{{ props }}};
const { keyFilter = 'meta.k' } =  _props;

第一行是 Mustache 模板引擎的语法,用于变量替换,第二行就是简单的一个解构。这样就可以拿到 keyFilter 了。

大致思路就是这样了。

另外—如何拿到整个 rootContainer

因为我们需要把我们的 react 组件,放到组件的最外层,可以通过 rootContainer 这个函数进行处理。代码如下:

export function rootContainer(container) {
  return <>
    {container}
    <CommandMenu></CommandMenu>
  </>
}

CommandMenu 就是写的 React 组件,跟 container 平级放就好了,container 就是整个 react 容器。

到此,基本功能就可以了,接下来就是发布了。

发布

打包构建

由于该例子是基于 umi 脚手架创建的,本身集成了 umi,打包什么的都很方便。

只需要关注 2 个点:

  1. .fatherrc.ts 的配置是否正确:
import { defineConfig } from 'father';

export default defineConfig({
  cjs: { output: 'dist' },  
  esm: { output: 'es' },
});

主要关注需要打包的模块化文件,cjs 是 commonjs 的,如果需要在 node 环境,就需要配置一下,如果只是浏览器环境用的包,只需要配置 esm 即可。 2. 需要关注一下 package.json 里的入口文件是否跟输出的文件匹配: module 填的是 es module 的入口文件。

最后再执行一下 npm run build ,即可将产物构建出来。

发布到 npm

产物构建出来之后,然后再执行:

npm publish

将模块发布到 npm 上供别人使用。

小结

本文介绍了如何编写 umi 插件,包括插件的结构和编写过程。通过编写 umi 插件,你可以扩展 umi 的功能,提高开发效率,并且可以将你的插件分享给其他人使用。

当你开始编写 umi 插件时,建议你先了解 umi 的基本用法和原理,并且熟悉 React、Webpack 和 Node. js 等相关技术。同时,你也可以参考 umi 官方提供的插件列表和开源社区中的插件,以了解其他人是如何编写 umi 插件的,并从中学习到一些有用的技巧和经验。

希望本文能对你有所帮助,祝你编写出高质量的 umi 插件!

参考文章