diff --git a/packages/fes-core/src/Config/index.js b/packages/fes-core/src/Config/index.js deleted file mode 100644 index 7ce22819..00000000 --- a/packages/fes-core/src/Config/index.js +++ /dev/null @@ -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(); - }; - } -} diff --git a/packages/fes-core/src/Config/utils/configUtils.js b/packages/fes-core/src/Config/utils/configUtils.js deleted file mode 100644 index dc2bb1e8..00000000 --- a/packages/fes-core/src/Config/utils/configUtils.js +++ /dev/null @@ -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); -} diff --git a/packages/fes-core/src/Config/utils/isEqual.js b/packages/fes-core/src/Config/utils/isEqual.js deleted file mode 100644 index ae545045..00000000 --- a/packages/fes-core/src/Config/utils/isEqual.js +++ /dev/null @@ -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)); -} diff --git a/packages/fes-core/src/Config/utils/mergeDefault.js b/packages/fes-core/src/Config/utils/mergeDefault.js deleted file mode 100644 index 5b8b2e1e..00000000 --- a/packages/fes-core/src/Config/utils/mergeDefault.js +++ /dev/null @@ -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; -}; diff --git a/packages/fes-core/src/service/PluginAPI.js b/packages/fes-core/src/service/PluginAPI.js deleted file mode 100644 index 64e2efd4..00000000 --- a/packages/fes-core/src/service/PluginAPI.js +++ /dev/null @@ -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); - }); - } -}