@fesjs/compiler
Version:
@fesjs/compiler
233 lines (225 loc) • 7.83 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _fs = require("fs");
var _path = require("path");
var _assert = _interopRequireDefault(require("assert"));
var _utils = require("@fesjs/utils");
var _joi = _interopRequireDefault(require("joi"));
var _enums = require("../service/enums");
var _configUtils = require("./utils/configUtils");
var _isEqual = _interopRequireDefault(require("./utils/isEqual"));
var _mergeDefault = _interopRequireDefault(require("./utils/mergeDefault"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @copy 该文件代码大部分出自 umi,有需要请参考:
* https://github.com/umijs/umi/tree/master/packages/core
*/
const CONFIG_FILES = ['.fes.js'];
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
}) {
(0, _assert.default)(this.service.stage >= _enums.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 = (0, _configUtils.getUserConfigWithKey)({
key,
userConfig
});
// 不校验 false 的值,此时已禁用插件
if (value === false) return;
// do validate
const schema = config.schema(_joi.default);
(0, _assert.default)(_joi.default.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 = (0, _mergeDefault.default)({
defaultConfig: defaultConfig[key],
config: value
});
(0, _configUtils.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;
if (configFile.length > 0) {
// clear require cache and set babel register
const requireDeps = configFile.reduce((memo, file) => {
memo = memo.concat((0, _utils.parseRequireDeps)(file));
return memo;
}, []);
requireDeps.forEach(_utils.cleanRequireCache);
this.service.babelRegister.setOnlyMap({
key: 'config',
value: requireDeps
});
// require config and merge
return this.mergeConfig(...this.requireConfigs(configFile));
}
return {};
}
addAffix(file, affix) {
const ext = (0, _path.extname)(file);
return file.replace(new RegExp(`${ext}$`), `.${affix}${ext}`);
}
requireConfigs(configFiles) {
// eslint-disable-next-line
return configFiles.map(f => (0, _utils.compatESModuleRequire)(require(f)));
}
mergeConfig(...configs) {
let ret = {};
for (const config of configs) {
// TODO: 精细化处理,比如处理 dotted config key
ret = (0, _utils.deepmerge)(ret, config);
}
return ret;
}
getConfigFile() {
// TODO: support custom config file
let configFile = CONFIG_FILES.find(f => (0, _fs.existsSync)((0, _path.join)(this.cwd, f)));
if (!configFile) return [];
configFile = (0, _utils.winPath)(configFile);
let envConfigFile;
// 潜在问题:
// .local 和 .env 的配置必须有 configFile 才有效
if (process.env.FES_ENV) {
envConfigFile = this.addAffix(configFile, process.env.FES_ENV);
if (!(0, _fs.existsSync)((0, _path.join)(this.cwd, 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 => (0, _path.join)(this.cwd, f)).filter(f => (0, _fs.existsSync)(f));
return files;
}
getWatchFilesAndDirectories() {
const fesEnv = process.env.FES_ENV;
const configFiles = _utils.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 = (0, _utils.winPath)((0, _path.join)(this.cwd, 'config'));
const files = configFiles.reduce((memo, f) => {
const file = (0, _utils.winPath)((0, _path.join)(this.cwd, f));
if ((0, _fs.existsSync)(file)) {
memo = memo.concat((0, _utils.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 = _utils.chokidar.watch(paths, {
ignoreInitial: true,
cwd: this.cwd
});
watcher.on('all', (event, path) => {
console.log(_utils.chalk.green(`[${event}] ${path}`));
const newPaths = this.getWatchFilesAndDirectories();
const diffs = _utils.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 (!(0, _isEqual.default)(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();
};
}
}
exports.default = Config;
;