前言
当开发前端应用(PC、H5、SSR 等)时,会怎么做?
从头开始初始化,拷贝 webpack 配置、安装一堆 babel 插件、手写一系列 ESLint 规则等,这些流程下来仅仅只能将 Hello World 项目运行起来;然后你开始将初始化操作封装成 脚手架,一键生成新项目。
但此时 A 应用中有构建最佳实践,想下沉为基础能力;
又比如 B 应用实现了一套插件机制,可以让应用可插拔式使用内部功能/技术栈;
...
此时,怎样让前端基础能力被上层框架直接使用,而不是每个前端框架都去开发一套 构建系统、AST、插件机制、目录约束、路由、渲染、错误处理等?
蚂蚁内部的实践是:基于UmiJS或 @umijs/core 封装适用特定业务场景的上层业务框架。
UmiJS 或 @umijs/core
UmiJS 定位是插件化的企业级前端应用框架,广义的 UmiJS = 内核 + @umijs/preset-built-in (内置插件集) + @umijs/preset-react(React 技术栈插件集),整个包大小约 100M。一图胜千言,架构如下图:
也许有同学会问,为什么 UmiJS 要加 runtime、core、bundler 等抽象层(Adaptor)?
因为前端库/工具迭代频繁,短短几年经历好几代打包工具(rollup、webpack)、好几个前端 UI 框架(React、Vue),对前端不变地是我们依旧需要 bundler / compiler、runtime。UmiJS 通过对配置强管控,降低框架对底层依赖配置耦合。
关于 @umijs/core 文档较少,core 中定义了前端可插拔式框架所需的最基本功能,也就是除了 core 内核外,umi 其它功能均由插件组成(包括 dev、build 等)Service(核心 Service 类)
Config(配置类):用户配置获取
PluginAPI(插件基础 API):插件开发中的 api.* 实现来源于此钩子方法执行底层为 tapable,支持通过 before 和 step 调整顺序
Route(前端路由类)
Html(Html 类):定义了一系列操作 html 的工具方法,通过模板生成 html 文件。
如何定制?
如果你所在当前业务线需要开发一套可插拔前端框架 largefish,同时需要针对业务线开发定制插件集(例如 @umijs/preset-largefish)
基于 umi
// packages/largefish
// 额外的 umi 插件集process.env.UMI_PRESETS = require.resolve('@umijs/preset-largefish');
// 执行 umi 命令const child = fork(require.resolve('umi/bin/umi'), process.argv.slice(2), {
stdio: 'inherit',
});
fork 一份 umi 进程,然后加上 largefish 插件集,然后我们就可以愉快地用
$ largefish dev
// => dev 开发
$ largefish build
// => build 构建
同时在 @umijs/preset-largefish 里,我们可以将插件集功能拆分:
// packages/preset-largefishexport default {
plugins: [
// 埋点 './tracert',
// 监控 './monitor',
// 登录鉴权 './auth',
// 部署平台相关 './deploy',
// ... ]
}
这样你的框架 largefish 就具备了业务线插件功能,如果我们的框架 bigfish 需要用到你业务线的登录鉴权,我可以直接在 bigfish 中引用你的 ./auth 插件,开发者可无缝接入。
基于 @umijs/core
基于 @umijs/core 可快速构建出一个简易的可插拔前端框架 (mediumfish),并且基于简易框架的插件可同时兼容 UmiJS 体系,有一套独立的插件集 @umijs/preset-mediumfish。
import { dirname, join } from 'path';
import { IServiceOpts, Service as CoreService } from '@umijs/core';
class Service extends CoreService {
constructor(opts: IServiceOpts) {
// umi 内核版本 process.env.UMI_VERSION = require('../package').version;
super({
...opts,
presets: [
// 定制插件集,可自由发挥封装业务线专属的插件集 require.resolve('@umijs/preset-mediumfish'),
...(opts.presets || []),
],
plugins: [...(opts.plugins || [])],
});
}
}
(async () => {
await new Service({ cwd: process.cwd() }).run({
// command 注册 name,
// command 参数 args,
});
})()
因为用了 @umijs/core,不包含 dev、build 等一系列功能,不过你可以基于此插件集进行扩展与定制:
// packages/preset-mediumfish
export default {
presets: [
// dev 开发 './dev',
// 构建 './build'
// ... ]
}
为了让底层构建工具解耦,我们提供了 @umijs/bundler-webpack,可直接复用 umi 的构建能力。选 umi 还是 @umijs/core 进行封装呢?取决于业务形态,如果你的业务偏中后台,可以基于 umi 进行封装;其他情况可选择 @umijs/core
框架封装实践
Bigfish
Bigfish 是基于 umi 研发的蚂蚁集团前端开发框架,实际上 Bigfish = umi + @alipay/umi-preset-bigfish(蚂蚁内部插件集,包括埋点、监控、权限等一系列功能)
内部 Bigfish 框架与 Umi 版本之间怎样协同的呢?
这点还是有不少实践的,首先,出于内部框架稳定性考虑,我们在 Bigfish 里锁定 umi 的 minor 版本:
// bigfish/package.json
{
"dependencies": {
"umi": "~3.2.8"
}
}
// umi-preset-bigfish/package.json
{
"dependencies": {
"@umijs/preset-react": "~1.6.0"
}
}
这样指定的好处有两点:享受 umi bugfix 后给 bigfish 带来的好处
umi 的不兼容改动(Break Change) 升级可以在 Bigfish 兼容后再使用。
dumi
dumi 是基于 Umi 为组件开发场景而生的文档工具,dumi = umi + @umijs/preset-dumi(组件开发、文档插件集)。因为 dumi 可以利于 umi 的构建能力,preset-dumi 加上对 markdown 解析、展示功能,快速构建出一套可插拔的组件研发及文档展示框架。**
alita
alita 是社区同学 xiaohuoni 基于 Umi 开发的移动端研发框架,alita = umi + @alitajs/umi-presets-alita(移动端插件集)。详见
参考
希望以本文,了解到 Umi 3 的一些基本组成、架构等。同时欢迎对前端基础技术感兴趣的同学,一同交流,宜鑫,蚂蚁集团-体验技术部。