umi dev
umi 命令在 scripts/umi文件夹下面,我们先看 umi dev 命令。
先来看一下目录结构,有个大致的印象
run
命令的核心是一个 run 方法
// bin/umi.js
#!/usr/bin/env node
// disable since it's conflicted with typescript cjs + dynamic import
// require('v8-compile-cache');
// patch console for debug
// ref: https://remysharp.com/2014/05/23/where-is-that-console-log
if (process.env.DEBUG_CONSOLE) {
['log', 'warn', 'error'].forEach((method) => {
const old = console[method];
console[method] = function () {
let stack = new Error().stack.split(/\n/);
// Chrome includes a single "Error" line, FF doesn't.
if (stack[0].indexOf('Error') === 0) {
stack = stack.slice(1);
}
const args = [].slice.apply(arguments).concat([stack[1].trim()]);
return old.apply(console, args);
};
});
}
require('../dist/cli/cli')
.run()
.catch((e) => {
console.error(e);
process.exit(1);
});
这个run方法就是 src/cli/cli.ts 中的方法,这个方法最核心的就是 dev()方法,当然还有 new Service.run2() ,此处我们暂时只看 dev() 方法,new Service.run2()方法会在后面章节介绍。
// src/cli/cli.ts
import { logger, printHelp, yParser } from '@umijs/utils';
import { DEV_COMMAND } from '../constants';
import { Service } from '../service/service';
import { dev } from './dev';
import {
checkLocal,
checkVersion as checkNodeVersion,
setNoDeprecation,
setNodeTitle,
} from './node';
interface IOpts {
presets?: string[];
}
export async function run(opts?: IOpts) {
checkNodeVersion();
checkLocal();
setNodeTitle();
setNoDeprecation();
const args = yParser(process.argv.slice(2), {
alias: {
version: ['v'],
help: ['h'],
},
boolean: ['version'],
});
const command = args._[0];
if ([DEV_COMMAND, 'setup'].includes(command)) {
process.env.NODE_ENV = 'development';
} else if (command === 'build') {
process.env.NODE_ENV = 'production';
}
if (opts?.presets) {
process.env.UMI_PRESETS = opts.presets.join(',');
}
if (command === DEV_COMMAND) {
dev();
} else {
try {
await new Service().run2({
name: args._[0],
args,
});
} catch (e: any) {
logger.fatal(e);
printHelp.exit();
process.exit(1);
}
}
}
dev
dev中的核心其实就一个 fork方法,这里的fork其实就是 child_process的fork,fork进来的路径对应的文件是一个自执行函数,我们先看这个fork的核心逻辑,下一步再看具体的执行思路。
// src/cli/dev.ts
import fork from './fork';
const child = fork({
scriptPath: require.resolve('../../bin/forkedDev'),
});
// src/cli/fork.ts
import { fork } from 'child_process';
const child = fork(scriptPath, process.argv.slice(2), { execArgv });
forkDev
通过这个forkDev.ts 文件,我们可以这里实例化了一个服务,执行了服务中注册的run2方法。下一步我们重点看一下 service.run2 方法
// src/cli/forkedDev.ts
import { logger, printHelp, yParser } from '@umijs/utils';
import { DEV_COMMAND, FRAMEWORK_NAME } from '../constants';
import { Service } from '../service/service';
import { setNoDeprecation, setNodeTitle } from './node';
setNodeTitle(`${FRAMEWORK_NAME}-dev`);
setNoDeprecation();
(async () => {
try {
// 获取命令行中输入的参数
const args = yParser(process.argv.slice(2));
// 实例化启动命令的服务
const service = new Service();
// 执行服务中的 run2 方法,
await service.run2({
name: DEV_COMMAND,
args,
});
// ... 省略其他非核心逻辑
})();
service
这个文件两段核心代码是 super() 和 this.run()。其中,super是为了初始化一些基础配置、插件、插件集合。this.run() 是真正开始启动项目的流程。通过下面代码可以看到,Service中并没用 run 方法,但是 Service 继承了 CoreService,在 CoreService 中我们会看到 run 方法,这个我们会在下一篇文章中介绍。
// src/service/service.ts
import { Service as CoreService } from '@umijs/core';
import { existsSync } from 'fs';
import { dirname, join } from 'path';
import { DEFAULT_CONFIG_FILES, FRAMEWORK_NAME } from '../constants';
import { getCwd } from './cwd';
export class Service extends CoreService {
constructor(opts?: any) {
process.env.UMI_DIR = dirname(require.resolve('../../package'));
const cwd = getCwd();
// Why?
// plugin import from umi but don't explicitly depend on it
// and we may also have old umi installed
// ref: https://github.com/umijs/umi/issues/8342#issuecomment-1182654076
require('./requireHook');
super({
...opts,
env: process.env.NODE_ENV,
cwd,
// 这里是用户的配置文件枚举,可以是以下四种中的一种
// '.umirc.ts','.umirc.js','config/config.ts','config/config.js',
defaultConfigFiles: opts?.defaultConfigFiles || DEFAULT_CONFIG_FILES,
frameworkName: FRAMEWORK_NAME,
// 这里是插件集合,默认添加了@umijs/preset-umi插件集合
// 也可以通过命令行配置 例如:umi dev presets=@plugin-xxx
presets: [require.resolve('@umijs/preset-umi'), ...(opts?.presets || [])],
// 这里是用户配置的插件文件,可以是plugin.ts,也可以是 plugin.js。
// 可以简单理解为根目录下有一个 plugin.ts或者 plugin.js文件。
plugins: [
existsSync(join(cwd, 'plugin.ts')) && join(cwd, 'plugin.ts'),
existsSync(join(cwd, 'plugin.js')) && join(cwd, 'plugin.js'),
].filter(Boolean),
});
}
async run2(opts: { name: string; args?: any }) {
let name = opts.name;
if (opts?.args.version || name === 'v') {
name = 'version';
} else if (opts?.args.help || !name || name === 'h') {
name = 'help';
}
// TODO
// initWebpack
return await this.run({ ...opts, name });
}
}
补充:因为dev命令执行的链路非常长,本篇文章只说到的 service,后面我会接着 service 继续做具体说明