umi是插件化的企业级前端应用框架。作为前后端分离的的前端部分开发框架,集成了常用的一些开发技术,是一种比较优秀的实践方案。主要用于支撑react的开发,4.x以上开发了支撑其他开发语言的入口。
umi阅读分为5个部分,分别是:
- 启动过程:从umi dev到如何调用umi的核心service
- 实例化过程:Service的实例化过程,以及启动dev之后的编译启动
- 渲染过程:研究umi如何将模块挂接渲染的
- routes渲染过程:从渲染入口研究umi的routes渲染机制
- 插件机制:重点研究umi的插件机制
以下代码以umi的3.5.20版本为例,主要内容以源码+个人解读为主
启动过程
内容:以umi dev命令启动,走通umi编译代码的流程,寻找umi从dev命令开始,进行了那些操作,到最后执行了真正的编译操作。
umi源码是多包管理,核心入口库是umi。
umi提供了umi dev来启动项目,而命令的注册来自bin,找到packages/umi/bin文件夹
const { name, bin } = require('../package.json');
const localCLI = resolveCwd.silent(`${name}/${bin['umi']}`);
if (!process.env.USE_GLOBAL_UMI && localCLI && localCLI !== __filename) {
const debug = require('@umijs/utils').createDebug('umi:cli');
debug('Using local install of umi');
require(localCLI);
} else {
require('../lib/cli');
}
该文件又加载了../lib/cli,该路径是编译后的压缩路径,所以应该找到packages/umi/src/cli.ts
该文件主要是处理命令行参数和对命令做出反应,核心代码:
switch (args._[0]) {
case 'dev':
const child = fork({
scriptPath: require.resolve('./forkedDev'),
});
//...
break;
default:
const name = args._[0];
if (name === 'build') {
process.env.NODE_ENV = 'production';
}
// Init webpack version determination and require hook for build command
initWebpack();
await new Service({
cwd: getCwd(),
pkg: getPkg(process.cwd()),
}).run({
name,
args,
});
break;
}
先看dev命令,加载了forkedDev文件,我们找到packages/umi/src/forkedDev.ts
import { Service } from './ServiceWithBuiltIn';
try {
process.env.NODE_ENV = 'development';
// Init webpack version determination and require hook
initWebpack();
const service = new Service({
cwd: getCwd(),
pkg: getPkg(process.cwd()),
});
await service.run({
name: 'dev',
args,
});
let closed = false;
// kill(2) Ctrl-C
process.once('SIGINT', () => onSignal('SIGINT'));
// kill(3) Ctrl-\
process.once('SIGQUIT', () => onSignal('SIGQUIT'));
// kill(15) default
process.once('SIGTERM', () => onSignal('SIGTERM'));
function onSignal(signal: string) {
if (closed) return;
closed = true;
// 退出时触发插件中的onExit事件
service.applyPlugins({
key: 'onExit',
type: service.ApplyPluginsType.event,
args: {
signal,
},
});
process.exit(0);
}
} catch (e) {
console.error(chalk.red(e.message));
console.error(e.stack);
process.exit(1);
}
主要的代码时初始化了webpack和新建了一个Service实例并执行service.run()。同时监听了结束线程的操作并使用service实例来结束线程。这么看来Service就是核心了。
接下来看看Service对象,继续跳转到./ServiceWithBuiltIn(内置服务)
import { IServiceOpts, Service as CoreService } from '@umijs/core';
class Service extends CoreService {
constructor(opts: IServiceOpts) {
process.env.UMI_VERSION = require('../package').version;
process.env.UMI_DIR = dirname(require.resolve('../package'));
super({
...opts,
presets: [
require.resolve('@umijs/preset-built-in'),
...(opts.presets || []),
],
plugins: [require.resolve('./plugins/umiAlias'), ...(opts.plugins || [])],
});
}
}
Service继承了CoreService,在实例化的同时加载了@umijs/preset-built-inpresets,和./plugins/umiAliasplugins。
plugins:配置额外的 umi 插件。
presets: 同 plugins 配置,用于配置额外的 umi 插件集。
通俗来讲,都是umi提供的插件机制的不同表现而已,可以把两者当成一种来看,初始化都是对应的绝对路径数组
其他重点文件解读
packages/umi/src/defineConfig.ts
import { IConfigFromPlugins } from '@@/core/pluginConfig';
import { IConfig } from '@umijs/types';
// IConfig types is prior to IConfigFromPlugins in the same key.
export function defineConfig(
config: IConfigFromPlugins | IConfig,
): IConfigFromPlugins | IConfig {
return config;
}
该文件定了defineConfig函数,用于去定义一些自定义配置,它直接返回了config对象,并提供了配置参数的类型提示。我们知道config是service需要用到,那Service怎么拿到config的呢,我们后续继续跟踪这个问题。
参考资料: