携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情。
希望你阅读这个专栏的时候,是已经看过了我前一个专栏 Umi@4 开发实战教程,因为一些 umi 常识性的内容,我在之前的专栏中已经讲解过了,如果我记得我讲过,那我会选择性的忽略,如果我不记得我讲过,我可能会再复述一遍,如果你看到有重复的内容,或者我跳过了一部分你无法理解的内容,欢迎你通过评论区和我交流。
首先你知道什么是 umi
吗,真的不是把 umi + preset
,@umijs/max
,ant-design-pro
等这些概念都当作是 umi
。这很重要,因为你首先要明白你要写的插件,是为谁
写的插件,比如你完全基于 umi
来开发插件,你就仅仅需要考虑,你的插件如何被加载,具备什么功能。而如果你是编写一个 umi + preset
的插件,那你还需要考虑多个插件之间是否需要相互影响和互相成就。比如官方的 access
就基于 initialState
插件的能力实现,而 initialState
插件又基于 models
插件。
谁才是真正的 umi?
举个简单的例子吧,我们如何形容什么是车?四个轮子能跑用于交通运输的东西我们称它是车。贴膜镀金我们还是叫它是车。引擎升级之后我们也称它是车。类似的我们把这个能跑的框架叫做 umi,它配套了一些好用的插件之后,我们还是称它是 umi。把这些好用的插件搞成插件集合捆绑在一起它还是 umi。不讲道理的话,其实 alita 也可以称之为 umi。
这也是很多新手刚开始接触 umi,存在认知差异上的困惑的原因。没点耐心或者没用过 umi 的就可以大声吐槽这是什么乱七八糟的玩意了。
为了减少这个系列文章的认知差异,我们将最简的不包含任何插件的这部分框架,叫做 umi,后续这个系列的所有关于 umi 的称呼均指代这个内容。
现在我们来创建一个带四个轮子能跑的 umi 吧。它将作为我们接触 umi 插件开发的第一个平台。
1、新建空白文件夹
你可以在任意的文件路径下,执行
$mkdir learn-for-umi-plugin
复制代码
2、初始化 npm 项目
$cd learn-for-umi-plugin
$npm init -y
复制代码
3、安装依赖
$pnpm i umi react react-dom
复制代码
4、新建 src 目录
$mkdir src
复制代码
5、新建 umi 页面
$npx umi g page index
Write: src/pages/index.tsx
Write: src/pages/index.less
复制代码
6、启动 umi
$npx umi dev
info - Umi v4.0.11
ready - App listening at http://localhost:8000
event - [Webpack] Compiled in 593 ms (510 modules)
info - [MFSU] buildDeps since cacheDependency has changed
wait - [Webpack] Compiling...
event - [Webpack] Compiled in 53 ms (496 modules)
event - [MFSU] Compiled in 1136 ms (1137 modules)
info - [MFSU] write cache
info - [MFSU] buildDepsAgain
info - [MFSU] skip buildDeps
复制代码
7、访问页面
访问控制台打印的连接,即可看到当前的 umi 页面
Page index
复制代码
你现在这个项目就是一个最简单的 umi 项目了,还有另一种方式确认你手头上的项目使用了多少插件,就是执行 umi plugin list
$npx umi plugin list
复制代码
如果使用到的插件都是来自 @umijs/preset-umi
和 @umijs/core
那就说明这是一个最简的 umi 项目了。
一般给复现demo的时候,umi 开发人员期望的也是在这个demo的基础上复现。
什么是插件
Umi 的核心就在于它的插件机制。那什么是插件机制呢?如果你有 webpack 基础,那你应该知道 webpack 整个架构很大程度上是基于事件的,每个 webpack 插件基本上都是一组在编译阶段挂钩不同事件的监听器。webpack 在底层使用了一个叫做 tapable
的库来封装“发布-订阅”的实现。
tapable
提供了不同的“钩子”类(SyncBailHook、AsyncParallelHook等)来“钩起”具有一些额外丰富功能(例如拦截或跨侦听器集成)的事件。umi 的插件机制也是基于 tapable
实现的。
如果你听不懂上面的表达,也没有关系,以下我用一些伪代码来让你明白什么是插件,插件是如何运行的。当然,这并不是真正的插件运行机制,我只是为了便于理解做了大量的简化。
class Umi {
constructor(plugins) {
this.plugins = plugins;
}
init() {
console.log('umi core init');
this.plugins.map(i => {
i?.init?.()
})
}
generateFile() {
console.log('umi core generateFile');
this.plugins.map(i => {
i?.generateFile?.()
})
}
build() {
console.log('umi core build');
this.plugins.map(i => {
i?.build?.()
})
}
run() {
this.init();
this.generateFile();
this.build();
}
}
const plugin1 = {
init:()=>{
console.log('[plugin1] init')
},
build:()=>{
console.log('[plugin1] build')
}
}
const plugin2 = {
generateFile:()=>{
console.log('[plugin2] generateFile')
},
build:()=>{
console.log('[plugin2] build')
}
}
const plugins = [plugin1,plugin2]
const umiCore = new Umi(plugins);
umiCore.run();
复制代码
run
里面的内容就是定义好的生命周期,当执行 run 的时候,我们会按顺序执行 init
,generateFile
,build
,并且在内部生命周期函数中,使用 plugins.map
来触发每个插件中定义的钩子函数。
想一想,上面的代码执行之后,会打印什么呢?可以复制上面的代码,在控制台中运行查看结果。
umi core init
[plugin1] init
umi core generateFile
[plugin2] generateFile
umi core build
[plugin1] build
[plugin2] build
复制代码
如果你理解了上面的代码,那加入一个自定义的 EventEmitter
来模拟 tapable
库的“发布-订阅”逻辑。 我们就得到了下面的代码。
class EventEmitter {
constructor() {
this.subscriptions = new Set();
}
emit = (val) => {
for (const subscription of this.subscriptions) {
subscription(val);
}
};
useSubscription = (callback) => {
function subscription(val) {
if (callback) {
callback(val);
}
}
this.subscriptions.add(subscription);
};
}
const initEmitter = new EventEmitter();
const buildEmitter = new EventEmitter();
class Umi {
constructor(plugins) {
plugins.map(i => i());
}
init() {
console.log('umi core init');
initEmitter.emit();
}
build() {
console.log('umi core build');
buildEmitter.emit()
}
run() {
this.init();
this.build();
}
}
const plugin1 = () => {
initEmitter.useSubscription(() => {
console.log('[plugin1] init')
})
buildEmitter.useSubscription(() => {
console.log('[plugin1] build')
})
}
const plugin2 = () => {
buildEmitter.useSubscription(() => {
console.log('[plugin2] build')
})
}
const plugins = [plugin1, plugin2]
const umiCore = new Umi(plugins);
umiCore.run();
复制代码
同样的复制上面的代码,在控制台中运行查看结果。
umi core init
[plugin1] init
umi core build
[plugin1] build
[plugin2] build
复制代码
在这次的生命周期函数中,使用 emit
来“发布”事件。在每个插件中使用 useSubscription
来“订阅”事件。当执行“发布”时,所有的“订阅”都将被响应。
以上为了说明仅仅展示了两个生命周期,在 umi 生态中,有一套更加完整丰富的生命周期,并且全部通过 umi 的 api
暴露给插件。
我将在下一次的更新中和你一起共建一个真正的 umi 插件,如果你对这个内容感兴趣,可以关注这个专栏:Umi 插件开发。如果你觉得这个文章对你有帮助,请点赞评论收藏支持我,并将这个文章分享给更多的朋友,文章的数据是我持续更新的动力。感谢。