mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-06 03:59:53 +08:00
feat: rename Service to service
This commit is contained in:
parent
248b0b93a4
commit
c742237d3a
@ -1,32 +0,0 @@
|
|||||||
export const PluginType = {
|
|
||||||
preset: 'preset',
|
|
||||||
plugin: 'plugin'
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ServiceStage = {
|
|
||||||
uninitialized: 0,
|
|
||||||
constructor: 1,
|
|
||||||
init: 2,
|
|
||||||
initPlugins: 3,
|
|
||||||
initHooks: 4,
|
|
||||||
pluginReady: 5,
|
|
||||||
getConfig: 6,
|
|
||||||
getPaths: 7,
|
|
||||||
run: 8
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ConfigChangeType = {
|
|
||||||
reload: 'reload',
|
|
||||||
regenerateTmpFiles: 'regenerateTmpFiles'
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ApplyPluginsType = {
|
|
||||||
add: 'add',
|
|
||||||
modify: 'modify',
|
|
||||||
event: 'event'
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EnableBy = {
|
|
||||||
register: 'register',
|
|
||||||
config: 'config'
|
|
||||||
};
|
|
@ -1,37 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import { existsSync, statSync } from 'fs';
|
|
||||||
import { lodash, winPath } from '@umijs/utils';
|
|
||||||
|
|
||||||
function isDirectoryAndExist(path) {
|
|
||||||
return existsSync(path) && statSync(path).isDirectory();
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeWithWinPath(obj) {
|
|
||||||
return lodash.mapValues(obj, value => winPath(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function getServicePaths({
|
|
||||||
cwd,
|
|
||||||
config,
|
|
||||||
env
|
|
||||||
}) {
|
|
||||||
let absSrcPath = cwd;
|
|
||||||
if (isDirectoryAndExist(join(cwd, 'src'))) {
|
|
||||||
absSrcPath = join(cwd, 'src');
|
|
||||||
}
|
|
||||||
const absPagesPath = config.singular
|
|
||||||
? join(absSrcPath, 'page')
|
|
||||||
: join(absSrcPath, 'pages');
|
|
||||||
|
|
||||||
const tmpDir = ['.fes', env !== 'development' && env]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join('-');
|
|
||||||
return normalizeWithWinPath({
|
|
||||||
cwd,
|
|
||||||
absNodeModulesPath: join(cwd, 'node_modules'),
|
|
||||||
absOutputPath: join(cwd, config.outputPath || './dist'),
|
|
||||||
absSrcPath,
|
|
||||||
absPagesPath,
|
|
||||||
absTmpPath: join(absSrcPath, tmpDir)
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,522 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
import assert from 'assert';
|
|
||||||
import { AsyncSeriesWaterfallHook } from 'tapable';
|
|
||||||
import { existsSync } from 'fs';
|
|
||||||
import { BabelRegister, lodash } from '@umijs/utils';
|
|
||||||
import { resolvePresets, pathToObj, resolvePlugins } from './utils/pluginUtils';
|
|
||||||
import loadDotEnv from './utils/loadDotEnv';
|
|
||||||
import isPromise from './utils/isPromise';
|
|
||||||
import PluginAPI from './pluginAPI';
|
|
||||||
import {
|
|
||||||
ApplyPluginsType,
|
|
||||||
ConfigChangeType,
|
|
||||||
EnableBy,
|
|
||||||
PluginType,
|
|
||||||
ServiceStage
|
|
||||||
} from './enums';
|
|
||||||
import Config from '../config';
|
|
||||||
import { getUserConfigWithKey } from '../config/utils/configUtils';
|
|
||||||
import getPaths from './getPaths';
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// 1. duplicated key
|
|
||||||
// 2. Logger
|
|
||||||
export default class Service extends EventEmitter {
|
|
||||||
cwd;
|
|
||||||
|
|
||||||
pkg;
|
|
||||||
|
|
||||||
skipPluginIds = new Set();
|
|
||||||
|
|
||||||
// lifecycle stage
|
|
||||||
stage = ServiceStage.uninitialized;
|
|
||||||
|
|
||||||
// registered commands
|
|
||||||
commands = {};
|
|
||||||
|
|
||||||
// including plugins
|
|
||||||
plugins = {};
|
|
||||||
|
|
||||||
// plugin methods
|
|
||||||
pluginMethods = {};
|
|
||||||
|
|
||||||
// initial presets and plugins from arguments, config, process.env, and package.json
|
|
||||||
initialPresets = [];
|
|
||||||
|
|
||||||
// initial plugins from arguments, config, process.env, and package.json
|
|
||||||
initialPlugins = [];
|
|
||||||
|
|
||||||
_extraPresets = [];
|
|
||||||
|
|
||||||
_extraPlugins = [];
|
|
||||||
|
|
||||||
// user config
|
|
||||||
userConfig;
|
|
||||||
|
|
||||||
configInstance;
|
|
||||||
|
|
||||||
config = null;
|
|
||||||
|
|
||||||
// babel register
|
|
||||||
babelRegister;
|
|
||||||
|
|
||||||
// hooks
|
|
||||||
hooksByPluginId = {};
|
|
||||||
|
|
||||||
hooks = {};
|
|
||||||
|
|
||||||
// paths
|
|
||||||
paths = {};
|
|
||||||
|
|
||||||
env;
|
|
||||||
|
|
||||||
ApplyPluginsType = ApplyPluginsType;
|
|
||||||
|
|
||||||
EnableBy = EnableBy;
|
|
||||||
|
|
||||||
ConfigChangeType = ConfigChangeType;
|
|
||||||
|
|
||||||
ServiceStage = ServiceStage;
|
|
||||||
|
|
||||||
args;
|
|
||||||
|
|
||||||
constructor(opts) {
|
|
||||||
super();
|
|
||||||
this.cwd = opts.cwd || process.cwd();
|
|
||||||
// repoDir should be the root dir of repo
|
|
||||||
this.pkg = opts.pkg || this.resolvePackage();
|
|
||||||
this.env = opts.env || process.env.NODE_ENV;
|
|
||||||
|
|
||||||
assert(existsSync(this.cwd), `cwd ${this.cwd} does not exist.`);
|
|
||||||
|
|
||||||
// register babel before config parsing
|
|
||||||
this.babelRegister = new BabelRegister();
|
|
||||||
|
|
||||||
// load .env or .local.env
|
|
||||||
this.loadEnv();
|
|
||||||
|
|
||||||
// get user config without validation
|
|
||||||
this.configInstance = new Config({
|
|
||||||
cwd: this.cwd,
|
|
||||||
service: this,
|
|
||||||
localConfig: this.env === 'development'
|
|
||||||
});
|
|
||||||
this.userConfig = this.configInstance.getUserConfig();
|
|
||||||
|
|
||||||
// get paths
|
|
||||||
this.paths = getPaths({
|
|
||||||
cwd: this.cwd,
|
|
||||||
config: this.userConfig,
|
|
||||||
env: this.env
|
|
||||||
});
|
|
||||||
|
|
||||||
// setup initial plugins
|
|
||||||
const baseOpts = {
|
|
||||||
pkg: this.pkg,
|
|
||||||
cwd: this.cwd
|
|
||||||
};
|
|
||||||
this.initialPresets = resolvePresets({
|
|
||||||
...baseOpts,
|
|
||||||
presets: opts.presets || [],
|
|
||||||
userConfigPresets: this.userConfig.presets || []
|
|
||||||
});
|
|
||||||
this.initialPlugins = resolvePlugins({
|
|
||||||
...baseOpts,
|
|
||||||
plugins: opts.plugins || [],
|
|
||||||
userConfigPlugins: this.userConfig.plugins || []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setStage(stage) {
|
|
||||||
this.stage = stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvePackage() {
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
return require(join(this.cwd, "package.json"));
|
|
||||||
} catch (e) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadEnv() {
|
|
||||||
const basePath = join(this.cwd, '.env');
|
|
||||||
const localPath = `${basePath}.local`;
|
|
||||||
loadDotEnv(basePath);
|
|
||||||
loadDotEnv(localPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
this.setStage(ServiceStage.init);
|
|
||||||
await this.initPresetsAndPlugins();
|
|
||||||
|
|
||||||
// hooksByPluginId -> hooks
|
|
||||||
// hooks is mapped with hook key, prepared for applyPlugins()
|
|
||||||
this.setStage(ServiceStage.initHooks);
|
|
||||||
Object.keys(this.hooksByPluginId).forEach((id) => {
|
|
||||||
const hooks = this.hooksByPluginId[id];
|
|
||||||
hooks.forEach((hook) => {
|
|
||||||
const { key } = hook;
|
|
||||||
hook.pluginId = id;
|
|
||||||
this.hooks[key] = (this.hooks[key] || []).concat(hook);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// plugin is totally ready
|
|
||||||
this.setStage(ServiceStage.pluginReady);
|
|
||||||
await this.applyPlugins({
|
|
||||||
key: 'onPluginReady',
|
|
||||||
type: ApplyPluginsType.event
|
|
||||||
});
|
|
||||||
|
|
||||||
// get config, including:
|
|
||||||
// 1. merge default config
|
|
||||||
// 2. validate
|
|
||||||
this.setStage(ServiceStage.getConfig);
|
|
||||||
await this.setConfig();
|
|
||||||
|
|
||||||
// merge paths to keep the this.paths ref
|
|
||||||
this.setStage(ServiceStage.getPaths);
|
|
||||||
// config.outputPath may be modified by plugins
|
|
||||||
if (this.config.outputPath) {
|
|
||||||
this.paths.absOutputPath = join(this.cwd, this.config.outputPath);
|
|
||||||
}
|
|
||||||
const paths = await this.applyPlugins({
|
|
||||||
key: 'modifyPaths',
|
|
||||||
type: ApplyPluginsType.modify,
|
|
||||||
initialValue: this.paths
|
|
||||||
});
|
|
||||||
Object.keys(paths).forEach((key) => {
|
|
||||||
this.paths[key] = paths[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async setConfig() {
|
|
||||||
const defaultConfig = await this.applyPlugins({
|
|
||||||
key: 'modifyDefaultConfig',
|
|
||||||
type: this.ApplyPluginsType.modify,
|
|
||||||
initialValue: await this.configInstance.getDefaultConfig()
|
|
||||||
});
|
|
||||||
this.config = await this.applyPlugins({
|
|
||||||
key: 'modifyConfig',
|
|
||||||
type: this.ApplyPluginsType.modify,
|
|
||||||
initialValue: this.configInstance.getConfig({
|
|
||||||
defaultConfig
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async initPresetsAndPlugins() {
|
|
||||||
this.setStage(ServiceStage.initPresets);
|
|
||||||
this._extraPlugins = [];
|
|
||||||
while (this.initialPresets.length) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
await this.initPreset(this.initialPresets.shift());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setStage(ServiceStage.initPlugins);
|
|
||||||
this._extraPlugins.push(...this.initialPlugins);
|
|
||||||
while (this._extraPlugins.length) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
await this.initPlugin(this._extraPlugins.shift());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginAPI(opts) {
|
|
||||||
const pluginAPI = new PluginAPI(opts);
|
|
||||||
|
|
||||||
// register built-in methods
|
|
||||||
[
|
|
||||||
'onPluginReady',
|
|
||||||
'modifyPaths',
|
|
||||||
'onStart',
|
|
||||||
'modifyDefaultConfig',
|
|
||||||
'modifyConfig'
|
|
||||||
].forEach((name) => {
|
|
||||||
pluginAPI.registerMethod({
|
|
||||||
name,
|
|
||||||
exitsError: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Proxy(pluginAPI, {
|
|
||||||
get: (target, prop) => {
|
|
||||||
// 由于 pluginMethods 需要在 register 阶段可用
|
|
||||||
// 必须通过 proxy 的方式动态获取最新,以实现边注册边使用的效果
|
|
||||||
if (this.pluginMethods[prop]) return this.pluginMethods[prop];
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
'applyPlugins',
|
|
||||||
'ApplyPluginsType',
|
|
||||||
'EnableBy',
|
|
||||||
'ConfigChangeType',
|
|
||||||
'babelRegister',
|
|
||||||
'stage',
|
|
||||||
'ServiceStage',
|
|
||||||
'paths',
|
|
||||||
'cwd',
|
|
||||||
'pkg',
|
|
||||||
'userConfig',
|
|
||||||
'config',
|
|
||||||
'env',
|
|
||||||
'args',
|
|
||||||
'hasPlugins',
|
|
||||||
'hasPresets',
|
|
||||||
'setConfig'
|
|
||||||
].includes(prop)
|
|
||||||
) {
|
|
||||||
return typeof this[prop] === 'function'
|
|
||||||
? this[prop].bind(this)
|
|
||||||
: this[prop];
|
|
||||||
}
|
|
||||||
return target[prop];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyAPI(opts) {
|
|
||||||
let ret = opts.apply()(opts.api);
|
|
||||||
if (isPromise(ret)) {
|
|
||||||
ret = await ret;
|
|
||||||
}
|
|
||||||
return ret || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async initPreset(preset) {
|
|
||||||
const { id, key, apply } = preset;
|
|
||||||
preset.isPreset = true;
|
|
||||||
|
|
||||||
const api = this.getPluginAPI({ id, key, service: this });
|
|
||||||
|
|
||||||
// register before apply
|
|
||||||
this.registerPlugin(preset);
|
|
||||||
const { presets, plugins } = await this.applyAPI({
|
|
||||||
api,
|
|
||||||
apply
|
|
||||||
});
|
|
||||||
|
|
||||||
// register extra presets and plugins
|
|
||||||
if (presets) {
|
|
||||||
assert(
|
|
||||||
Array.isArray(presets),
|
|
||||||
`presets returned from preset ${id} must be Array.`,
|
|
||||||
);
|
|
||||||
// 插到最前面,下个 while 循环优先执行
|
|
||||||
this._extraPresets.splice(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
...presets.map(path => pathToObj({
|
|
||||||
type: PluginType.preset,
|
|
||||||
path,
|
|
||||||
cwd: this.cwd
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 深度优先
|
|
||||||
const extraPresets = lodash.clone(this._extraPresets);
|
|
||||||
this._extraPresets = [];
|
|
||||||
while (extraPresets.length) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
await this.initPreset(extraPresets.shift());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugins) {
|
|
||||||
assert(
|
|
||||||
Array.isArray(plugins),
|
|
||||||
`plugins returned from preset ${id} must be Array.`,
|
|
||||||
);
|
|
||||||
this._extraPlugins.push(
|
|
||||||
...plugins.map(path => pathToObj({
|
|
||||||
type: PluginType.plugin,
|
|
||||||
path,
|
|
||||||
cwd: this.cwd
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async initPlugin(plugin) {
|
|
||||||
const { id, key, apply } = plugin;
|
|
||||||
|
|
||||||
const api = this.getPluginAPI({
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
service: this
|
|
||||||
});
|
|
||||||
|
|
||||||
// register before apply
|
|
||||||
this.registerPlugin(plugin);
|
|
||||||
await this.applyAPI({
|
|
||||||
api,
|
|
||||||
apply
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginOptsWithKey(key) {
|
|
||||||
return getUserConfigWithKey({
|
|
||||||
key,
|
|
||||||
userConfig: this.userConfig
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
registerPlugin(plugin) {
|
|
||||||
this.plugins[plugin.id] = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
isPluginEnable(pluginId) {
|
|
||||||
// api.skipPlugins() 的插件
|
|
||||||
if (this.skipPluginIds.has(pluginId)) return false;
|
|
||||||
|
|
||||||
const { key, enableBy } = this.plugins[pluginId];
|
|
||||||
|
|
||||||
// 手动设置为 false
|
|
||||||
if (this.userConfig[key] === false) return false;
|
|
||||||
|
|
||||||
// 配置开启
|
|
||||||
if (enableBy === this.EnableBy.config && !(key in this.userConfig)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 函数自定义开启
|
|
||||||
if (typeof enableBy === 'function') {
|
|
||||||
return enableBy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册开启
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPresets(presetIds) {
|
|
||||||
return presetIds.every((presetId) => {
|
|
||||||
const preset = this.plugins[presetId];
|
|
||||||
return preset && preset.isPreset && this.isPluginEnable(presetId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPlugins(pluginIds) {
|
|
||||||
return pluginIds.every((pluginId) => {
|
|
||||||
const plugin = this.plugins[pluginId];
|
|
||||||
return plugin && !plugin.isPreset && this.isPluginEnable(pluginId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyPlugins(opts) {
|
|
||||||
const hooks = this.hooks[opts.key] || [];
|
|
||||||
switch (opts.type) {
|
|
||||||
case ApplyPluginsType.add:
|
|
||||||
if ('initialValue' in opts) {
|
|
||||||
assert(
|
|
||||||
Array.isArray(opts.initialValue),
|
|
||||||
'applyPlugins failed, opts.initialValue must be Array if opts.type is add.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const tAdd = new AsyncSeriesWaterfallHook(["memo"]);
|
|
||||||
for (const hook of hooks) {
|
|
||||||
if (!this.isPluginEnable(hook.pluginId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tAdd.tapPromise(
|
|
||||||
{
|
|
||||||
name: hook.pluginId,
|
|
||||||
stage: hook.stage || 0,
|
|
||||||
// @ts-ignore
|
|
||||||
before: hook.before
|
|
||||||
},
|
|
||||||
async (memo) => {
|
|
||||||
const items = await hook.fn(opts.args);
|
|
||||||
return memo.concat(items);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return tAdd.promise(opts.initialValue || []);
|
|
||||||
case ApplyPluginsType.modify:
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const tModify = new AsyncSeriesWaterfallHook(["memo"]);
|
|
||||||
for (const hook of hooks) {
|
|
||||||
if (!this.isPluginEnable(hook.pluginId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tModify.tapPromise(
|
|
||||||
{
|
|
||||||
name: hook.pluginId,
|
|
||||||
stage: hook.stage || 0,
|
|
||||||
// @ts-ignore
|
|
||||||
before: hook.before
|
|
||||||
},
|
|
||||||
async memo => hook.fn(memo, opts.args)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return tModify.promise(opts.initialValue);
|
|
||||||
case ApplyPluginsType.event:
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const tEvent = new AsyncSeriesWaterfallHook(["_"]);
|
|
||||||
for (const hook of hooks) {
|
|
||||||
if (!this.isPluginEnable(hook.pluginId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tEvent.tapPromise(
|
|
||||||
{
|
|
||||||
name: hook.pluginId,
|
|
||||||
stage: hook.stage || 0,
|
|
||||||
// @ts-ignore
|
|
||||||
before: hook.before
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
await hook.fn(opts.args);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return tEvent.promise();
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`applyPlugin failed, type is not defined or is not matched, got ${opts.type}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async run({ name, args = {} }) {
|
|
||||||
args._ = args._ || [];
|
|
||||||
// shift the command itself
|
|
||||||
if (args._[0] === name) args._.shift();
|
|
||||||
|
|
||||||
this.args = args;
|
|
||||||
await this.init();
|
|
||||||
|
|
||||||
this.setStage(ServiceStage.run);
|
|
||||||
await this.applyPlugins({
|
|
||||||
key: 'onStart',
|
|
||||||
type: ApplyPluginsType.event,
|
|
||||||
args: {
|
|
||||||
args
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.runCommand({
|
|
||||||
name,
|
|
||||||
args
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async runCommand({ name, args = {} }) {
|
|
||||||
assert(this.stage >= ServiceStage.init, 'service is not initialized.');
|
|
||||||
|
|
||||||
args._ = args._ || [];
|
|
||||||
// shift the command itself
|
|
||||||
if (args._[0] === name) args._.shift();
|
|
||||||
|
|
||||||
const command = typeof this.commands[name] === 'string'
|
|
||||||
? this.commands[this.commands[name]]
|
|
||||||
: this.commands[name];
|
|
||||||
assert(command, `run command failed, command ${name} does not exists.`);
|
|
||||||
|
|
||||||
const { fn } = command;
|
|
||||||
return fn({
|
|
||||||
args
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
export default function isPromise(obj) {
|
|
||||||
return (
|
|
||||||
!!obj
|
|
||||||
&& (typeof obj === 'object' || typeof obj === 'function')
|
|
||||||
&& typeof obj.then === 'function'
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
import { readFileSync, existsSync } from 'fs';
|
|
||||||
import { parse } from 'dotenv';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* dotenv wrapper
|
|
||||||
* @param envPath string
|
|
||||||
*/
|
|
||||||
export default function loadDotEnv(envPath) {
|
|
||||||
if (existsSync(envPath)) {
|
|
||||||
const parsed = parse(readFileSync(envPath, 'utf-8')) || {};
|
|
||||||
Object.keys(parsed).forEach((key) => {
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
if (!process.env.hasOwnProperty(key)) {
|
|
||||||
process.env[key] = parsed[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,135 +0,0 @@
|
|||||||
import {
|
|
||||||
dirname, join, basename, relative, extname
|
|
||||||
} from 'path';
|
|
||||||
import {
|
|
||||||
compatESModuleRequire,
|
|
||||||
resolve,
|
|
||||||
winPath,
|
|
||||||
pkgUp,
|
|
||||||
lodash
|
|
||||||
} from '@umijs/utils';
|
|
||||||
|
|
||||||
import { PluginType } from '../enums';
|
|
||||||
|
|
||||||
const RE = {
|
|
||||||
[PluginType.plugin]: /^(@webank\/)?fes-plugin-/,
|
|
||||||
[PluginType.preset]: /^(@webank\/)?fes-preset-/
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isPluginOrPreset(type, name) {
|
|
||||||
const hasScope = name.charAt(0) === '@';
|
|
||||||
const re = RE[type];
|
|
||||||
if (hasScope) {
|
|
||||||
return re.test(name.split('/')[1]) || re.test(name);
|
|
||||||
}
|
|
||||||
return re.test(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPluginsOrPresets(type, opts) {
|
|
||||||
const upperCaseType = type.toUpperCase();
|
|
||||||
return [
|
|
||||||
// dependencies
|
|
||||||
// opts
|
|
||||||
...((opts[type === PluginType.preset ? 'presets' : 'plugins']) || []),
|
|
||||||
// env
|
|
||||||
...(process.env[`FES_${upperCaseType}S`] || '').split(',').filter(Boolean),
|
|
||||||
...Object.keys(opts.pkg.devDependencies || {})
|
|
||||||
.concat(Object.keys(opts.pkg.dependencies || {}))
|
|
||||||
.filter(isPluginOrPreset.bind(null, type)),
|
|
||||||
// user config
|
|
||||||
...((opts[
|
|
||||||
type === PluginType.preset ? 'userConfigPresets' : 'userConfigPlugins'
|
|
||||||
]) || [])
|
|
||||||
].map(path => resolve.sync(path, {
|
|
||||||
basedir: opts.cwd,
|
|
||||||
extensions: ['.js', '.ts']
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// e.g.
|
|
||||||
// initial-state -> initialState
|
|
||||||
// webpack.css-loader -> webpack.cssLoader
|
|
||||||
function nameToKey(name) {
|
|
||||||
return name
|
|
||||||
.split('.')
|
|
||||||
.map(part => lodash.camelCase(part))
|
|
||||||
.join('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function pkgNameToKey(pkgName, type) {
|
|
||||||
if (pkgName.charAt(0) === '@' && !pkgName.startsWith('@webank/')) {
|
|
||||||
pkgName = pkgName.split('/')[1];
|
|
||||||
}
|
|
||||||
return nameToKey(pkgName.replace(RE[type], ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pathToObj({ path, type, cwd }) {
|
|
||||||
let pkg = null;
|
|
||||||
let isPkgPlugin = false;
|
|
||||||
const pkgJSONPath = pkgUp.sync({ cwd: path });
|
|
||||||
if (pkgJSONPath) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
pkg = require(pkgJSONPath);
|
|
||||||
isPkgPlugin = winPath(join(dirname(pkgJSONPath), pkg.main || 'index.js'))
|
|
||||||
=== winPath(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
let id;
|
|
||||||
if (isPkgPlugin) {
|
|
||||||
id = pkg.name;
|
|
||||||
} else if (winPath(path).startsWith(winPath(cwd))) {
|
|
||||||
id = `./${winPath(relative(cwd, path))}`;
|
|
||||||
} else if (pkgJSONPath) {
|
|
||||||
id = winPath(join(pkg.name, relative(dirname(pkgJSONPath), path)));
|
|
||||||
} else {
|
|
||||||
id = winPath(path);
|
|
||||||
}
|
|
||||||
id = id.replace('@webank/fes-preset-built-in/lib/plugins', '@@');
|
|
||||||
id = id.replace(/\.js$/, '');
|
|
||||||
|
|
||||||
const key = isPkgPlugin
|
|
||||||
? pkgNameToKey(pkg.name, type)
|
|
||||||
: nameToKey(basename(path, extname(path)));
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
path: winPath(path),
|
|
||||||
apply() {
|
|
||||||
// use function to delay require
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const ret = require(path);
|
|
||||||
// use the default member for es modules
|
|
||||||
return compatESModuleRequire(ret);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Register ${path} failed, since ${e.message}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultConfig: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolvePresets(opts) {
|
|
||||||
const type = PluginType.preset;
|
|
||||||
const presets = [...getPluginsOrPresets(type, opts)];
|
|
||||||
return presets.map(path => pathToObj({
|
|
||||||
type,
|
|
||||||
path,
|
|
||||||
cwd: opts.cwd
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolvePlugins(opts) {
|
|
||||||
const type = PluginType.plugin;
|
|
||||||
const plugins = getPluginsOrPresets(type, opts);
|
|
||||||
return plugins.map(path => pathToObj({
|
|
||||||
path,
|
|
||||||
type,
|
|
||||||
cwd: opts.cwd
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isValidPlugin(plugin) {
|
|
||||||
return plugin.id && plugin.key && plugin.apply;
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user