Umi 插件實戰(zhàn)教程,你學(xué)會了嗎?
引言
筆者最近開發(fā)了一款 umi 插件:plugin-umi-cmdk[1],該插件的功能主要是:在 umi 項目里可以方便的集成 cmd + k ,實現(xiàn)菜單等搜索。
主體功能并不復(fù)雜,但是在集成作為 umi 插件過程中踩了不少坑,主要是 umi 官方文檔的, 開發(fā)插件 | UmiJS[2]實屬寫得爛,看完之后根本無法上手。
所以寫一篇完整的插件開發(fā)教程,手把手上手 umi 插件開發(fā)。
準(zhǔn)備工作
創(chuàng)建項目
新建一個文件夾umi-plugin-demo,直接通過 umi 的官方模版進(jìn)行創(chuàng)建。
yarn create umi
之后選擇模板的時候選擇:Umi Plugin。
創(chuàng)建 example 目錄用于測試
然后創(chuàng)建完之后在 umi-plugin-demo 的根目錄新建一個 example 文件夾,用于測試。
將 example 初始化成一個 umi 項目:
cd example
// 然后
yarn create umi
根據(jù)你的需求選擇一個模板。
我選了 Ant Design Pro ,現(xiàn)在整個目錄結(jié)構(gòu)大致是這樣。
掛載插件
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("歡迎關(guān)注前端桃園!");
});
};
這個代碼的意思就是在插件啟動的時候打一個 log「插件開始加載了!??!」。
2、在 example/.umirc 里引入插件。
import { defineConfig } from '@umijs/max';
import { join } from 'path';
export default defineConfig({
plugins: [join(__dirname, '../src/index.ts')],
// 其他的配置
})
通過 plugins 這個配置,將插件文件進(jìn)行引入,在啟動 example 項目的時候插件就會被加載。
3、查看 logo 然后在 example 下,通過 npm start 啟動項目,即可看到控制臺的 log。
當(dāng)我們看到控制臺輸出了想要的日志,到這一步,準(zhǔn)備工作已經(jīng)就緒,接下來就可以開始寫插件了。
更多掛載方式
除了通過 plugins 配置項掛載插件,umi 還提供了一種約定式的掛載方式。
在 umi 體系中,約定根目錄下存在 plugin 文件夾作為本地插件的約定入口。只要存在該文件夾,其中的插件就會被自動掛載,無需再進(jìn)行額外的配置。
例如,在我們的示例中,可以直接在 example 目錄下創(chuàng)建一個 plugin.ts 文件,即可將插件掛載到 umi 中,無需在 .umirc 配置文件中添加插件配置。這種方式通常用于本地測試插件。
編寫插件
一般的插件機(jī)制是通過暴露鉤子來實現(xiàn)的,鉤子會在運(yùn)行時執(zhí)行并提供一些屬性供插件開發(fā)者使用。插件開發(fā)者可以在鉤子中實現(xiàn)想要的功能,從而擴(kuò)展插件的功能。因此,插件開發(fā)就是在鉤子中編寫代碼實現(xiàn)自定義功能的過程。
所以一般在編寫框架插件的時候就需要先了解一下該框架提供了哪些鉤子,這個決定了開發(fā)者可以做哪些擴(kuò)展的事情。
一般開發(fā)插件的基本流程如下:1、 瀏覽 umi 提供的插件 API2、確定插件的目的和功能 3、找到需要的插件 API 和生命周期方法
功能分析
我以我寫的插件 cmdk 插件舉例分析。
cmdk 搜索那個就是一個純 react 組件的功能,作為插件的話其實就是想辦法把這個插件在 umi 運(yùn)行的時候就插入到整個 umi 的最外層。
另外就是支持配置一些參數(shù),比如有些用戶不想用 cmd + k,想用 cmd + m 來調(diào)出彈框。那么就需要配置快捷鍵的 key。
大致就這兩個功能吧。
- 將 cmkd 的 react 組件插入到 umi。
- 支持配置快捷鍵
需要用到 API
- [api. describe ()](https://umijs.org/docs/api/plugin-api#describe "api. describe ( "api. describe ()")")。用于在插件注冊階段執(zhí)行,用于描述插件或者插件集的 key、配置信息和啟用方式等。在 .umirc 配置快捷鍵的時候需要用到。
- [api. onGenerateFiles ()](https://umijs.org/docs/api/plugin-api#ongeneratefiles "api. onGenerateFiles ( "api. onGenerateFiles ()")")。生成臨時文件的鉤子,就是把運(yùn)行時需要的文件生成到 .umi 目錄下的那個鉤子。
- [api. writeTmpFile ()](https://umijs.org/docs/api/plugin-api#writetmpfile "api. writeTmpFile ( "api. writeTmpFile ()")").生成臨時文件的方法,在 onGenerateFiles 階段進(jìn)行調(diào)用的,將臨時文件寫入進(jìn)去。
看到這些文件,這些就是在 umi 運(yùn)行時需要用到的文件,都是對應(yīng)的插件生成的。umi 在運(yùn)行時這些代碼都是會執(zhí)行的,在我們這里也是需要把 cmdk 那個 react 組件寫到這里面來,這樣在運(yùn)行時才能用。
1、給插件傳遞屬性并做參數(shù)校驗
比如我們需要將 .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 結(jié)構(gòu),我們只需要這個對象里有一個 keyFilter 的字符串。
return joi.object({
keyFilter: joi.string(),
});
},
},
enableBy: api.EnableBy.config,
})
}
這樣就可以實現(xiàn)對配置參數(shù)的校驗了。我們可以通過 api.userConfig.cmdk 拿到配置。
2、給 react 組件傳遞屬性
可以通過 api.userConfig.cmdk 拿到了配置參數(shù),最終目的是要傳遞給 react 組件的。
在 src 下新建一個文件 cmdk.tpl 用于方 cmdk 的 react 代碼。
然后通過 [api. writeTmpFile ()](https://umijs.org/docs/api/plugin-api#writetmpfile "api. writeTmpFile ( "api. writeTmpFile ()")") 將配置參數(shù)傳進(jìn)去,在 umi 插件里由于不是 react 組件,沒辦法通過 props 傳遞屬性,所以只能通過用模板的方式進(jìn)行傳遞參數(shù)。
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 作為參數(shù),寫入到 props 這個模板參數(shù)里.
umi 會自動寫到對應(yīng)的插件目錄下。
在 cmdk.tpl 里可以這樣拿到傳遞的參數(shù)。
const _props = {{{ props }}};
const { keyFilter = 'meta.k' } = _props;
第一行是 Mustache 模板引擎的語法,用于變量替換,第二行就是簡單的一個解構(gòu)。這樣就可以拿到 keyFilter 了。
大致思路就是這樣了。
另外--如何拿到整個 rootContainer
因為我們需要把我們的 react 組件,放到組件的最外層,可以通過 rootContainer 這個函數(shù)進(jìn)行處理。代碼如下:
export function rootContainer(container) {
return <>
{container}
<CommandMenu></CommandMenu>
</>
}
CommandMenu 就是寫的 React 組件,跟 container 平級放就好了,container 就是整個 react 容器。
到此,基本功能就可以了,接下來就是發(fā)布了。
發(fā)布
打包構(gòu)建
由于該例子是基于 umi 腳手架創(chuàng)建的,本身集成了 umi,打包什么的都很方便。
只需要關(guān)注 2 個點:
- .fatherrc.ts 的配置是否正確:
import { defineConfig } from 'father';
export default defineConfig({
cjs: { output: 'dist' },
esm: { output: 'es' },
});
主要關(guān)注需要打包的模塊化文件,cjs 是 commonjs 的,如果需要在 node 環(huán)境,就需要配置一下,如果只是瀏覽器環(huán)境用的包,只需要配置 esm 即可。
需要關(guān)注一下 package.json 里的入口文件是否跟輸出的文件匹配:
module 填的是 es module 的入口文件。
最后再執(zhí)行一下 npm run build ,即可將產(chǎn)物構(gòu)建出來。
發(fā)布到 npm
產(chǎn)物構(gòu)建出來之后,然后再執(zhí)行:
npm publish
將模塊發(fā)布到 npm 上供別人使用。
小結(jié)
本文介紹了如何編寫 umi 插件,包括插件的結(jié)構(gòu)和編寫過程。通過編寫 umi 插件,你可以擴(kuò)展 umi 的功能,提高開發(fā)效率,并且可以將你的插件分享給其他人使用。
當(dāng)你開始編寫 umi 插件時,建議你先了解 umi 的基本用法和原理,并且熟悉 React、Webpack 和 Node. js 等相關(guān)技術(shù)。同時,你也可以參考 umi 官方提供的插件列表和開源社區(qū)中的插件,以了解其他人是如何編寫 umi 插件的,并從中學(xué)習(xí)到一些有用的技巧和經(jīng)驗。
希望本文能對你有所幫助,祝你編寫出高質(zhì)量的 umi 插件!
參考文章
- [插件開發(fā) (umijs.org)](https://v3.umijs.org/zh-CN/guide/plugin-develop "插件開發(fā) (umijs.org "插件開發(fā) (umijs.org)")")。
參考資料
[1]plugin-umi-cmdk:https://github.com/crazylxr/plugin-umi-cmdk。
[2]開發(fā)插件 | UmiJS:https://umijs.org/docs/guides/plugins。