mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-06 03:59:53 +08:00
feat: rename Config to config
This commit is contained in:
parent
c742237d3a
commit
21f4f66504
@ -1,286 +0,0 @@
|
|||||||
import { existsSync } from 'fs';
|
|
||||||
import { extname, join } from 'path';
|
|
||||||
import {
|
|
||||||
chalk,
|
|
||||||
chokidar,
|
|
||||||
compatESModuleRequire,
|
|
||||||
deepmerge,
|
|
||||||
cleanRequireCache,
|
|
||||||
lodash,
|
|
||||||
parseRequireDeps,
|
|
||||||
winPath,
|
|
||||||
getFile
|
|
||||||
} from '@umijs/utils';
|
|
||||||
import assert from 'assert';
|
|
||||||
import joi from 'joi';
|
|
||||||
import { ServiceStage } from '../service/enums';
|
|
||||||
import {
|
|
||||||
getUserConfigWithKey,
|
|
||||||
updateUserConfigWithKey
|
|
||||||
} from './utils/configUtils';
|
|
||||||
import isEqual from './utils/isEqual';
|
|
||||||
import mergeDefault from './utils/mergeDefault';
|
|
||||||
|
|
||||||
const CONFIG_FILES = ['.fes.js', 'config/config.js'];
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// 1. custom config file
|
|
||||||
export default class Config {
|
|
||||||
cwd;
|
|
||||||
|
|
||||||
service;
|
|
||||||
|
|
||||||
config;
|
|
||||||
|
|
||||||
localConfig;
|
|
||||||
|
|
||||||
configFile;
|
|
||||||
|
|
||||||
constructor(opts) {
|
|
||||||
this.cwd = opts.cwd || process.cwd();
|
|
||||||
this.service = opts.service;
|
|
||||||
this.localConfig = opts.localConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDefaultConfig() {
|
|
||||||
const pluginIds = Object.keys(this.service.plugins);
|
|
||||||
|
|
||||||
// collect default config
|
|
||||||
const defaultConfig = pluginIds.reduce((memo, pluginId) => {
|
|
||||||
const { key, config = {} } = this.service.plugins[pluginId];
|
|
||||||
if ('default' in config) memo[key] = config.default;
|
|
||||||
return memo;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return defaultConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfig({ defaultConfig }) {
|
|
||||||
assert(
|
|
||||||
this.service.stage >= ServiceStage.pluginReady,
|
|
||||||
'Config.getConfig() failed, it should not be executed before plugin is ready.'
|
|
||||||
);
|
|
||||||
|
|
||||||
const userConfig = this.getUserConfig();
|
|
||||||
// 用于提示用户哪些 key 是未定义的
|
|
||||||
// TODO: 考虑不排除 false 的 key
|
|
||||||
const userConfigKeys = Object.keys(userConfig).filter(
|
|
||||||
key => userConfig[key] !== false
|
|
||||||
);
|
|
||||||
|
|
||||||
// get config
|
|
||||||
const pluginIds = Object.keys(this.service.plugins);
|
|
||||||
pluginIds.forEach((pluginId) => {
|
|
||||||
const { key, config = {} } = this.service.plugins[pluginId];
|
|
||||||
// recognize as key if have schema config
|
|
||||||
if (!config.schema) return;
|
|
||||||
|
|
||||||
const value = getUserConfigWithKey({
|
|
||||||
key,
|
|
||||||
userConfig
|
|
||||||
});
|
|
||||||
// 不校验 false 的值,此时已禁用插件
|
|
||||||
if (value === false) return;
|
|
||||||
|
|
||||||
// do validate
|
|
||||||
const schema = config.schema(joi);
|
|
||||||
assert(
|
|
||||||
joi.isSchema(schema),
|
|
||||||
`schema return from plugin ${pluginId} is not valid schema.`
|
|
||||||
);
|
|
||||||
const { error } = schema.validate(value);
|
|
||||||
if (error) {
|
|
||||||
const e = new Error(
|
|
||||||
`Validate config "${key}" failed, ${error.message}`
|
|
||||||
);
|
|
||||||
e.stack = error.stack;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove key
|
|
||||||
const index = userConfigKeys.indexOf(key.split('.')[0]);
|
|
||||||
if (index !== -1) {
|
|
||||||
userConfigKeys.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update userConfig with defaultConfig
|
|
||||||
if (key in defaultConfig) {
|
|
||||||
const newValue = mergeDefault({
|
|
||||||
defaultConfig: defaultConfig[key],
|
|
||||||
config: value
|
|
||||||
});
|
|
||||||
updateUserConfigWithKey({
|
|
||||||
key,
|
|
||||||
value: newValue,
|
|
||||||
userConfig
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userConfigKeys.length) {
|
|
||||||
const keys = userConfigKeys.length > 1 ? 'keys' : 'key';
|
|
||||||
throw new Error(
|
|
||||||
`Invalid config ${keys}: ${userConfigKeys.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return userConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserConfig() {
|
|
||||||
const configFile = this.getConfigFile();
|
|
||||||
this.configFile = configFile;
|
|
||||||
// 潜在问题:
|
|
||||||
// .local 和 .env 的配置必须有 configFile 才有效
|
|
||||||
if (configFile) {
|
|
||||||
let envConfigFile;
|
|
||||||
if (process.env.FES_ENV) {
|
|
||||||
const envConfigFileName = this.addAffix(
|
|
||||||
configFile,
|
|
||||||
process.env.FES_ENV
|
|
||||||
);
|
|
||||||
const fileNameWithoutExt = envConfigFileName.replace(
|
|
||||||
extname(envConfigFileName),
|
|
||||||
''
|
|
||||||
);
|
|
||||||
envConfigFile = getFile({
|
|
||||||
base: this.cwd,
|
|
||||||
fileNameWithoutExt,
|
|
||||||
type: 'javascript'
|
|
||||||
}).filename;
|
|
||||||
if (!envConfigFile) {
|
|
||||||
throw new Error(
|
|
||||||
`get user config failed, ${envConfigFile} does not exist, but process.env.FES_ENV is set to ${process.env.FES_ENV}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const files = [
|
|
||||||
configFile,
|
|
||||||
envConfigFile,
|
|
||||||
this.localConfig && this.addAffix(configFile, 'local')
|
|
||||||
]
|
|
||||||
.filter(f => !!f)
|
|
||||||
.map(f => join(this.cwd, f))
|
|
||||||
.filter(f => existsSync(f));
|
|
||||||
|
|
||||||
// clear require cache and set babel register
|
|
||||||
const requireDeps = files.reduce((memo, file) => {
|
|
||||||
memo = memo.concat(parseRequireDeps(file));
|
|
||||||
return memo;
|
|
||||||
}, []);
|
|
||||||
requireDeps.forEach(cleanRequireCache);
|
|
||||||
this.service.babelRegister.setOnlyMap({
|
|
||||||
key: 'config',
|
|
||||||
value: requireDeps
|
|
||||||
});
|
|
||||||
|
|
||||||
// require config and merge
|
|
||||||
return this.mergeConfig(...this.requireConfigs(files));
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
addAffix(file, affix) {
|
|
||||||
const ext = extname(file);
|
|
||||||
return file.replace(new RegExp(`${ext}$`), `.${affix}${ext}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
requireConfigs(configFiles) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
return configFiles.map((f) => compatESModuleRequire(require(f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeConfig(...configs) {
|
|
||||||
let ret = {};
|
|
||||||
for (const config of configs) {
|
|
||||||
// TODO: 精细化处理,比如处理 dotted config key
|
|
||||||
ret = deepmerge(ret, config);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfigFile() {
|
|
||||||
// TODO: support custom config file
|
|
||||||
const configFile = CONFIG_FILES.find(f => existsSync(join(this.cwd, f)));
|
|
||||||
return configFile ? winPath(configFile) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getWatchFilesAndDirectories() {
|
|
||||||
const fesEnv = process.env.FES_ENV;
|
|
||||||
const configFiles = lodash.clone(CONFIG_FILES);
|
|
||||||
CONFIG_FILES.forEach((f) => {
|
|
||||||
if (this.localConfig) configFiles.push(this.addAffix(f, 'local'));
|
|
||||||
if (fesEnv) configFiles.push(this.addAffix(f, fesEnv));
|
|
||||||
});
|
|
||||||
|
|
||||||
const configDir = winPath(join(this.cwd, 'config'));
|
|
||||||
|
|
||||||
const files = configFiles
|
|
||||||
.reduce((memo, f) => {
|
|
||||||
const file = winPath(join(this.cwd, f));
|
|
||||||
if (existsSync(file)) {
|
|
||||||
memo = memo.concat(parseRequireDeps(file));
|
|
||||||
} else {
|
|
||||||
memo.push(file);
|
|
||||||
}
|
|
||||||
return memo;
|
|
||||||
}, [])
|
|
||||||
.filter(f => !f.startsWith(configDir));
|
|
||||||
|
|
||||||
return [configDir].concat(files);
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(opts) {
|
|
||||||
let paths = this.getWatchFilesAndDirectories();
|
|
||||||
let userConfig = opts.userConfig;
|
|
||||||
const watcher = chokidar.watch(paths, {
|
|
||||||
ignoreInitial: true,
|
|
||||||
cwd: this.cwd
|
|
||||||
});
|
|
||||||
watcher.on('all', (event, path) => {
|
|
||||||
console.log(chalk.green(`[${event}] ${path}`));
|
|
||||||
const newPaths = this.getWatchFilesAndDirectories();
|
|
||||||
const diffs = lodash.difference(newPaths, paths);
|
|
||||||
if (diffs.length) {
|
|
||||||
watcher.add(diffs);
|
|
||||||
paths = paths.concat(diffs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUserConfig = this.getUserConfig();
|
|
||||||
const pluginChanged = [];
|
|
||||||
const valueChanged = [];
|
|
||||||
Object.keys(this.service.plugins).forEach((pluginId) => {
|
|
||||||
const { key, config = {} } = this.service.plugins[pluginId];
|
|
||||||
// recognize as key if have schema config
|
|
||||||
if (!config.schema) return;
|
|
||||||
if (!isEqual(newUserConfig[key], userConfig[key])) {
|
|
||||||
const changed = {
|
|
||||||
key,
|
|
||||||
pluginId
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
newUserConfig[key] === false
|
|
||||||
|| userConfig[key] === false
|
|
||||||
) {
|
|
||||||
pluginChanged.push(changed);
|
|
||||||
} else {
|
|
||||||
valueChanged.push(changed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pluginChanged.length || valueChanged.length) {
|
|
||||||
opts.onChange({
|
|
||||||
userConfig: newUserConfig,
|
|
||||||
pluginChanged,
|
|
||||||
valueChanged
|
|
||||||
});
|
|
||||||
}
|
|
||||||
userConfig = newUserConfig;
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
watcher.close();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { lodash } from '@umijs/utils';
|
|
||||||
import set from 'set-value';
|
|
||||||
|
|
||||||
export function updateUserConfigWithKey({
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
userConfig
|
|
||||||
}) {
|
|
||||||
set(userConfig, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUserConfigWithKey({
|
|
||||||
key,
|
|
||||||
userConfig
|
|
||||||
}) {
|
|
||||||
return lodash.get(userConfig, key);
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { lodash } from '@umijs/utils';
|
|
||||||
|
|
||||||
function funcToStr(obj) {
|
|
||||||
if (typeof obj === 'function') return obj.toString();
|
|
||||||
if (lodash.isPlainObject(obj)) {
|
|
||||||
return Object.keys(obj).reduce((memo, key) => {
|
|
||||||
memo[key] = funcToStr(obj[key]);
|
|
||||||
return memo;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (a, b) {
|
|
||||||
return lodash.isEqual(funcToStr(a), funcToStr(b));
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import { deepmerge, lodash } from '@umijs/utils';
|
|
||||||
|
|
||||||
|
|
||||||
export default ({ defaultConfig, config }) => {
|
|
||||||
if (lodash.isPlainObject(defaultConfig) && lodash.isPlainObject(config)) {
|
|
||||||
return deepmerge(defaultConfig, config);
|
|
||||||
}
|
|
||||||
return typeof config !== 'undefined' ? config : defaultConfig;
|
|
||||||
};
|
|
@ -1,154 +0,0 @@
|
|||||||
import assert from 'assert';
|
|
||||||
import * as utils from '@umijs/utils';
|
|
||||||
import { isValidPlugin, pathToObj } from './utils/pluginUtils';
|
|
||||||
import { EnableBy, PluginType, ServiceStage } from './enums';
|
|
||||||
import Logger from '../logger';
|
|
||||||
// TODO
|
|
||||||
// 标准化 logger
|
|
||||||
export default class PluginAPI {
|
|
||||||
constructor(opts) {
|
|
||||||
this.id = opts.id;
|
|
||||||
this.key = opts.key;
|
|
||||||
this.service = opts.service;
|
|
||||||
this.utils = utils;
|
|
||||||
this.logger = new Logger(`fes:plugin:${this.id || this.key}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: reversed keys
|
|
||||||
describe({
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
config,
|
|
||||||
enableBy
|
|
||||||
} = {}) {
|
|
||||||
const { plugins } = this.service;
|
|
||||||
// this.id and this.key is generated automatically
|
|
||||||
// so we need to diff first
|
|
||||||
if (id && this.id !== id) {
|
|
||||||
if (plugins[id]) {
|
|
||||||
const name = plugins[id].isPreset ? 'preset' : 'plugin';
|
|
||||||
throw new Error(
|
|
||||||
`api.describe() failed, ${name} ${id} is already registered by ${plugins[id].path}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
plugins[id] = plugins[this.id];
|
|
||||||
plugins[id].id = id;
|
|
||||||
delete plugins[this.id];
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
if (key && this.key !== key) {
|
|
||||||
this.key = key;
|
|
||||||
plugins[this.id].key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config) {
|
|
||||||
plugins[this.id].config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins[this.id].enableBy = enableBy || EnableBy.register;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(hook) {
|
|
||||||
assert(
|
|
||||||
hook.key && typeof hook.key === 'string',
|
|
||||||
`api.register() failed, hook.key must supplied and should be string, but got ${hook.key}.`,
|
|
||||||
);
|
|
||||||
assert(
|
|
||||||
hook.fn && typeof hook.fn === 'function',
|
|
||||||
`api.register() failed, hook.fn must supplied and should be function, but got ${hook.fn}.`,
|
|
||||||
);
|
|
||||||
this.service.hooksByPluginId[this.id] = (
|
|
||||||
this.service.hooksByPluginId[this.id] || []
|
|
||||||
).concat(hook);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCommand(command) {
|
|
||||||
const { name, alias } = command;
|
|
||||||
assert(
|
|
||||||
!this.service.commands[name],
|
|
||||||
`api.registerCommand() failed, the command ${name} is exists.`,
|
|
||||||
);
|
|
||||||
this.service.commands[name] = command;
|
|
||||||
if (alias) {
|
|
||||||
this.service.commands[alias] = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在 preset 初始化阶段放后面,在插件注册阶段放前面
|
|
||||||
registerPlugins(plugins) {
|
|
||||||
assert(
|
|
||||||
this.service.stage === ServiceStage.initPresets
|
|
||||||
|| this.service.stage === ServiceStage.initPlugins,
|
|
||||||
'api.registerPlugins() failed, it should only be used in registering stage.',
|
|
||||||
);
|
|
||||||
assert(
|
|
||||||
Array.isArray(plugins),
|
|
||||||
'api.registerPlugins() failed, plugins must be Array.',
|
|
||||||
);
|
|
||||||
const extraPlugins = plugins.map(plugin => (isValidPlugin(plugin)
|
|
||||||
? (plugin)
|
|
||||||
: pathToObj({
|
|
||||||
type: PluginType.plugin,
|
|
||||||
path: plugin,
|
|
||||||
cwd: this.service.cwd
|
|
||||||
})));
|
|
||||||
if (this.service.stage === ServiceStage.initPresets) {
|
|
||||||
this.service._extraPlugins.push(...extraPlugins);
|
|
||||||
} else {
|
|
||||||
this.service._extraPlugins.splice(0, 0, ...extraPlugins);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerPresets(presets) {
|
|
||||||
assert(
|
|
||||||
this.service.stage === ServiceStage.initPresets,
|
|
||||||
'api.registerPresets() failed, it should only used in presets.',
|
|
||||||
);
|
|
||||||
assert(
|
|
||||||
Array.isArray(presets),
|
|
||||||
'api.registerPresets() failed, presets must be Array.',
|
|
||||||
);
|
|
||||||
const extraPresets = presets.map(preset => (isValidPlugin(preset)
|
|
||||||
? (preset)
|
|
||||||
: pathToObj({
|
|
||||||
type: PluginType.preset,
|
|
||||||
path: preset,
|
|
||||||
cwd: this.service.cwd
|
|
||||||
})));
|
|
||||||
// 插到最前面,下个 while 循环优先执行
|
|
||||||
this.service._extraPresets.splice(0, 0, ...extraPresets);
|
|
||||||
}
|
|
||||||
|
|
||||||
registerMethod({
|
|
||||||
name,
|
|
||||||
fn,
|
|
||||||
exitsError = true
|
|
||||||
}) {
|
|
||||||
if (this.service.pluginMethods[name]) {
|
|
||||||
if (exitsError) {
|
|
||||||
throw new Error(
|
|
||||||
`api.registerMethod() failed, method ${name} is already exist.`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.service.pluginMethods[name] = fn
|
|
||||||
// 这里不能用 arrow function,this 需指向执行此方法的 PluginAPI
|
|
||||||
// 否则 pluginId 会不会,导致不能正确 skip plugin
|
|
||||||
|| function (hookFn) {
|
|
||||||
const hook = {
|
|
||||||
key: name,
|
|
||||||
...(utils.lodash.isPlainObject(hookFn) ? hookFn : { fn: hookFn })
|
|
||||||
};
|
|
||||||
// @ts-ignore
|
|
||||||
this.register(hook);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
skipPlugins(pluginIds) {
|
|
||||||
pluginIds.forEach((pluginId) => {
|
|
||||||
this.service.skipPluginIds.add(pluginId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user