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