这一篇衔接了上一篇的 继承 Service 并执行其中的 run 方法, Service 的代码在 umi/packages/core/src/service/service.ts 中,我们以其中的 run 方法为入口抽丝剥茧,详细介绍。
代码太长,这里放一个链接,方便大家查看,下面从 run 方法中具体执行的事情一一说起。 github.com/umijs/umi/b…
- 获取 环境变量 .env 或者 .env.local,逻辑比较简单,主要有三步
- 读取配置文件内容并解析,
readFileSync & parse - 解析配置文件为json格式,
expand - 在process.env中添加读取到的配置项
process.env[key] = parsed[key]
- 读取配置文件内容并解析,
// loadEnv({ cwd: this.cwd, envFile: '.env' });
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
import { parse } from '../../compiled/dotenv';
import { expand } from '../../compiled/dotenv-expand';
export function loadEnv(opts: { cwd: string; envFile: string }) {
const files = [
join(opts.cwd, opts.envFile),
join(opts.cwd, `${opts.envFile}.local`),
];
for (const file of files) {
if (!existsSync(file)) continue;
const parsed: Record<string, string> = parse(readFileSync(file)) || {};
expand({ parsed, ignoreProcessEnv: true });
for (const key of Object.keys(parsed)) {
process.env[key] = parsed[key];
}
}
}
- 读取命令执行所在的根路径对应的 package.json 内容和路径,代码也比较简单
this.pkg = require(join(this.cwd, 'package.json'));
this.pkgPath = join(this.cwd, 'package.json');
- 实例化用户的基础配置
这里先说一下静态方法的一些特点,后面会用到,静态方法只能通过类调用,不能通过实例调用。
class Foo {
static staticMethod() {
return 'hello';
}
basicMethod() {
return 'basic'
}
}
// 静态方法只能通过类调用
Foo.staticMethod() // 'hello'
foo.staticMethod()
// TypeError: foo.classMethod is not a function
var foo = new Foo();
// 非静态方法,可以通过实例调用
foo.basicMethod() // 'basic'
- 获取 .umirc.ts 中的配置内容,以及其他配置信息
const configManager = new Config({
cwd: this.cwd,
env: this.env,
defaultConfigFiles: this.opts.defaultConfigFiles,
specifiedEnv,
});
this.configManager = configManager;
// 获取配置的关键代码
this.userConfig = configManager.getUserConfig().config;
configManager.getUserConfig().config; 整个函数执行流程如下:
- getUserConfig
- static getConfigFiles 获取 .umirc.ts 、.umirc.test.ts 等文件名
- static getUserConfig 获取 .umirc.ts 里面的内容,用到了 esbuild 解析文件内容
- 删除了非必要代码的配置文件如下,具体请看注释
import esbuild from '@umijs/bundler-utils/compiled/esbuild';
import { chokidar, lodash, register, semver } from '@umijs/utils';
import joi from '@umijs/utils/compiled/@hapi/joi';
import assert from 'assert';
import { existsSync } from 'fs';
import { join } from 'path';
import { diff } from '../../compiled/just-diff';
import {
DEFAULT_CONFIG_FILES,
LOCAL_EXT,
SHORT_ENV,
WATCH_DEBOUNCE_STEP,
} from '../constants';
import { Env } from '../types';
import { addExt, getAbsFiles } from './utils';
interface IOpts {
cwd: string;
env: Env;
specifiedEnv?: string;
defaultConfigFiles?: string[];
}
type ISchema = Record<string, any>;
type IOnChangeTypes = Record<string, string | Function>;
export class Config {
public opts: IOpts;
public mainConfigFile: string | null;
public prevConfig: any;
public files: string[] = [];
constructor(opts: IOpts) {
this.opts = opts;
// 获取配置文件类似,.umirc.ts
this.mainConfigFile = Config.getMainConfigFile(this.opts);
this.prevConfig = null;
}
getUserConfig() {
// .umirc.ts => .umirc.ts
// .umirc.ts => .umirc.test.ts
// .umirc.ts => .umirc.production.ts
const configFiles = Config.getConfigFiles({
mainConfigFile: this.mainConfigFile,
env: this.opts.env,
specifiedEnv: this.opts.specifiedEnv,
});
return Config.getUserConfig({
configFiles: getAbsFiles({
files: configFiles,
cwd: this.opts.cwd,
}),
});
}
// 获取配置文件类似,.umirc.ts,只要找到第一个就不再往下找,默认是 .umirc.ts
static getMainConfigFile(opts: {
cwd: string;
defaultConfigFiles?: string[];
}) {
let mainConfigFile = null;
for (const configFile of opts.defaultConfigFiles || DEFAULT_CONFIG_FILES) {
const absConfigFile = join(opts.cwd, configFile);
if (existsSync(absConfigFile)) {
mainConfigFile = absConfigFile;
break;
}
}
return mainConfigFile;
}
static getConfigFiles(opts: {
mainConfigFile: string | null;
env: Env;
specifiedEnv?: string;
}) {
const ret: string[] = [];
const { mainConfigFile } = opts;
const specifiedEnv = opts.specifiedEnv || '';
if (mainConfigFile) {
const env = SHORT_ENV[opts.env] || opts.env;
ret.push(
...[
mainConfigFile,
specifiedEnv &&
addExt({ file: mainConfigFile, ext: `.${specifiedEnv}` }),
addExt({ file: mainConfigFile, ext: `.${env}` }),
specifiedEnv &&
addExt({
file: mainConfigFile,
ext: `.${env}.${specifiedEnv}`,
}),
].filter(Boolean),
);
if (opts.env === Env.development) {
ret.push(addExt({ file: mainConfigFile, ext: LOCAL_EXT }));
}
}
return ret;
}
// 解析 .umirc.ts 获取 配置信息 json 数据
static getUserConfig(opts: { configFiles: string[] }) {
let config = {};
let files: string[] = [];
for (const configFile of opts.configFiles) {
if (existsSync(configFile)) {
register.register({
implementor: esbuild,
});
register.clearFiles();
try {
config = lodash.merge(config, require(configFile).default);
} catch (e) {
// Error.prototype.cause has been fully supported from node v16.9.0
// Ref https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause#browser_compatibility
if (semver.lt(semver.clean(process.version)!, '16.9.0')) {
throw e;
}
// @ts-ignore
throw new Error(`Parse config file failed: [${configFile}]`, {
cause: e,
});
}
for (const file of register.getFiles()) {
delete require.cache[file];
}
// includes the config File
files.push(...register.getFiles());
register.restore();
} else {
files.push(configFile);
}
}
return {
config,
files,
};
}
}