feat: 添加插件集

This commit is contained in:
bac-joker 2020-12-19 15:59:38 +08:00
parent 76078684a4
commit 442daebe6b
66 changed files with 230 additions and 84 deletions

View File

@ -8,7 +8,7 @@ const headPkgs = [
"fes-runtime",
"fes-core",
"fes",
"fes-plugin-built-in",
"fes-preset-built-in",
"fes-plugin-request",
"fes-plugin-access",
"fes-plugin-model",

View File

@ -5,13 +5,13 @@ import Logger from './logger';
import Service from './service';
import PluginAPI from './service/pluginAPI';
import { PluginType } from './service/enums';
import { isPlugin } from './service/utils/pluginUtils';
import { isPluginOrPreset } from './service/utils/pluginUtils';
export {
Config,
Service,
PluginAPI,
isPlugin,
isPluginOrPreset,
PluginType,
Logger
};

View File

@ -3,8 +3,8 @@ import { EventEmitter } from 'events';
import assert from 'assert';
import { AsyncSeriesWaterfallHook } from 'tapable';
import { existsSync } from 'fs';
import { BabelRegister } from '@umijs/utils';
import { resolvePlugins } from './utils/pluginUtils';
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';
@ -12,6 +12,7 @@ import {
ApplyPluginsType,
ConfigChangeType,
EnableBy,
PluginType,
ServiceStage
} from './enums';
import Config from '../config';
@ -21,7 +22,6 @@ import getPaths from './getPaths';
// TODO
// 1. duplicated key
// 2. Logger
// 3. 支持插件集?
export default class Service extends EventEmitter {
cwd;
@ -41,9 +41,14 @@ export default class Service extends EventEmitter {
// 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
@ -111,6 +116,11 @@ export default class Service extends EventEmitter {
pkg: this.pkg,
cwd: this.cwd
};
this.initialPresets = resolvePresets({
...baseOpts,
presets: opts.presets || [],
userConfigPresets: this.userConfig.presets || []
});
this.initialPlugins = resolvePlugins({
...baseOpts,
plugins: opts.plugins || [],
@ -140,8 +150,7 @@ export default class Service extends EventEmitter {
async init() {
this.setStage(ServiceStage.init);
// we should have the final hooksByPluginId which is added with api.register()
await this.initPlugins();
await this.initPresetsAndPlugins();
// hooksByPluginId -> hooks
// hooks is mapped with hook key, prepared for applyPlugins()
@ -199,8 +208,14 @@ export default class Service extends EventEmitter {
});
}
async initPlugins() {
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) {
@ -248,6 +263,7 @@ export default class Service extends EventEmitter {
'env',
'args',
'hasPlugins',
'hasPresets',
'setConfig'
].includes(prop)
) {
@ -268,6 +284,61 @@ export default class Service extends EventEmitter {
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;
@ -319,10 +390,17 @@ export default class Service extends EventEmitter {
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 && this.isPluginEnable(pluginId);
return plugin && !plugin.isPreset && this.isPluginEnable(pluginId);
});
}

View File

@ -99,6 +99,26 @@ export default class PluginAPI {
}
}
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,

View File

@ -9,27 +9,37 @@ import {
lodash
} from '@umijs/utils';
import { PluginType } from '../enums';
const RE = {
plugin: /^(@webank\/)?fes-plugin-/
[PluginType.plugin]: /^(@webank\/)?fes-plugin-/,
[PluginType.preset]: /^(@webank\/)?fes-preset-/
};
export function isPlugin(name) {
export function isPluginOrPreset(type, name) {
const hasScope = name.charAt(0) === '@';
const re = RE.plugin;
const re = RE[type];
if (hasScope) {
return re.test(name.split('/')[1]) || re.test(name);
}
return re.test(name);
}
export function getPlugins(opts) {
export function getPluginsOrPresets(type, opts) {
const upperCaseType = type.toUpperCase();
return [
// dependencies
...opts.plugins,
// 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(isPlugin.bind(null)),
...opts.userConfigPlugins
.filter(isPluginOrPreset.bind(null, type)),
// user config
...((opts[
type === PluginType.preset ? 'userConfigPresets' : 'userConfigPlugins'
]) || [])
].map(path => resolve.sync(path, {
basedir: opts.cwd,
extensions: ['.js', '.ts']
@ -46,14 +56,14 @@ function nameToKey(name) {
.join('.');
}
function pkgNameToKey(pkgName) {
function pkgNameToKey(pkgName, type) {
if (pkgName.charAt(0) === '@' && !pkgName.startsWith('@webank/')) {
pkgName = pkgName.split('/')[1];
}
return nameToKey(pkgName.replace(RE.plugin, ''));
return nameToKey(pkgName.replace(RE[type], ''));
}
export function pathToObj({ path, cwd }) {
export function pathToObj({ path, type, cwd }) {
let pkg = null;
let isPkgPlugin = false;
const pkgJSONPath = pkgUp.sync({ cwd: path });
@ -74,11 +84,11 @@ export function pathToObj({ path, cwd }) {
} else {
id = winPath(path);
}
id = id.replace('@webank/fes-plugin-built-in/lib/plugins', '@@');
id = id.replace('@webank/fes-preset-built-in/lib/plugins', '@@');
id = id.replace(/\.js$/, '');
const key = isPkgPlugin
? pkgNameToKey(pkg.name)
? pkgNameToKey(pkg.name, type)
: nameToKey(basename(path, extname(path)));
return {
@ -100,10 +110,22 @@ export function pathToObj({ path, cwd }) {
};
}
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 plugins = getPlugins(opts);
const type = PluginType.plugin;
const plugins = getPluginsOrPresets(type, opts);
return plugins.map(path => pathToObj({
path,
type,
cwd: opts.cwd
}));
}

View File

@ -1,53 +0,0 @@
// TODO 拆成独立的包作为内置插件包
export default [
// register methods
require.resolve('./plugins/registerMethods'),
// misc
require.resolve('./plugins/misc/route'),
// generate files
require.resolve('./plugins/generateFiles/core/plugin'),
require.resolve('./plugins/generateFiles/core/exports/coreExports'),
require.resolve('./plugins/generateFiles/core/exports/pluginExports'),
require.resolve('./plugins/generateFiles/fes'),
// bundle configs
require.resolve('./plugins/features/alias'),
require.resolve('./plugins/features/analyze'),
require.resolve('./plugins/features/autoprefixer'),
require.resolve('./plugins/features/base'),
require.resolve('./plugins/features/chainWebpack'),
require.resolve('./plugins/features/chunks'),
require.resolve('./plugins/features/cssLoader'),
require.resolve('./plugins/features/cssnano'),
require.resolve('./plugins/features/copy'),
require.resolve('./plugins/features/define'),
require.resolve('./plugins/features/devServer'),
require.resolve('./plugins/features/devtool'),
require.resolve('./plugins/features/externals'),
require.resolve('./plugins/features/extraBabelPlugins'),
require.resolve('./plugins/features/extraBabelPresets'),
require.resolve('./plugins/features/extraPostCSSPlugins'),
require.resolve('./plugins/features/hash'),
require.resolve('./plugins/features/html'),
require.resolve('./plugins/features/inlineLimit'),
require.resolve('./plugins/features/lessLoader'),
require.resolve('./plugins/features/mountElementId'),
require.resolve('./plugins/features/nodeModulesTransform'),
require.resolve('./plugins/features/outputPath'),
require.resolve('./plugins/features/plugins'),
require.resolve('./plugins/features/postcssLoader'),
require.resolve('./plugins/features/proxy'),
require.resolve('./plugins/features/publicPath'),
require.resolve('./plugins/features/styleLoader'),
require.resolve('./plugins/features/targets'),
require.resolve('./plugins/features/terserOptions'),
require.resolve('./plugins/features/theme'),
require.resolve('./plugins/features/vueLoader'),
// commands
require.resolve('./plugins/commands/build'),
require.resolve('./plugins/commands/dev')
];

View File

@ -1,7 +1,7 @@
{
"name": "@webank/fes-plugin-built-in",
"name": "@webank/fes-preset-built-in",
"version": "2.0.0",
"description": "@webank/fes-plugin-built-in",
"description": "@webank/fes-preset-built-in",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [

View File

@ -0,0 +1,55 @@
export default function () {
return {
plugins: [
// register methods
require.resolve('./plugins/registerMethods'),
// misc
require.resolve('./plugins/misc/route'),
// generate files
require.resolve('./plugins/generateFiles/core/plugin'),
require.resolve('./plugins/generateFiles/core/exports/coreExports'),
require.resolve('./plugins/generateFiles/core/exports/pluginExports'),
require.resolve('./plugins/generateFiles/fes'),
// bundle configs
require.resolve('./plugins/features/alias'),
require.resolve('./plugins/features/analyze'),
require.resolve('./plugins/features/autoprefixer'),
require.resolve('./plugins/features/base'),
require.resolve('./plugins/features/chainWebpack'),
require.resolve('./plugins/features/chunks'),
require.resolve('./plugins/features/cssLoader'),
require.resolve('./plugins/features/cssnano'),
require.resolve('./plugins/features/copy'),
require.resolve('./plugins/features/define'),
require.resolve('./plugins/features/devServer'),
require.resolve('./plugins/features/devtool'),
require.resolve('./plugins/features/externals'),
require.resolve('./plugins/features/extraBabelPlugins'),
require.resolve('./plugins/features/extraBabelPresets'),
require.resolve('./plugins/features/extraPostCSSPlugins'),
require.resolve('./plugins/features/hash'),
require.resolve('./plugins/features/html'),
require.resolve('./plugins/features/inlineLimit'),
require.resolve('./plugins/features/lessLoader'),
require.resolve('./plugins/features/mountElementId'),
require.resolve('./plugins/features/nodeModulesTransform'),
require.resolve('./plugins/features/outputPath'),
require.resolve('./plugins/features/plugins'),
require.resolve('./plugins/features/postcssLoader'),
require.resolve('./plugins/features/proxy'),
require.resolve('./plugins/features/publicPath'),
require.resolve('./plugins/features/styleLoader'),
require.resolve('./plugins/features/targets'),
require.resolve('./plugins/features/terserOptions'),
require.resolve('./plugins/features/theme'),
require.resolve('./plugins/features/vueLoader'),
// commands
require.resolve('./plugins/commands/build'),
require.resolve('./plugins/commands/dev')
]
};
}

View File

@ -33,6 +33,7 @@ export default (api) => {
description: 'start a dev server for development',
async fn({ args = {} }) {
const defaultPort = process.env.PORT || args.port || api.config.devServer?.port;
console.log(api.config.devServer);
port = await portfinder.getPortPromise({
port: defaultPort ? parseInt(String(defaultPort), 10) : 8000
});

View File

@ -1,14 +1,15 @@
import { join } from 'path';
import { chokidar, winPath, lodash } from '@umijs/utils';
import { existsSync, readFileSync } from 'fs';
import { isPlugin, PluginType } from '@webank/fes-core';
import { isPluginOrPreset, PluginType } from '@webank/fes-core';
function getPlugins(opts) {
return Object.keys({
...opts.pkg.dependencies,
...opts.pkg.devDependencies
}).filter(name => (
isPlugin(PluginType.plugin, name)
isPluginOrPreset(PluginType.plugin, name)
|| isPluginOrPreset(PluginType.preset, name)
));
}

View File

@ -185,7 +185,7 @@ const getRoutesJSON = function ({ routes, config }) {
// eslint-disable-next-line
return (
/^\((.+)?\)(\s+)?=>/.test(component)
|| /^function([^\(]+)?\(([^\)]+)?\)([^{]+)?{/.test(component)
|| /^function([^(]+)?\(([^)]+)?\)([^{]+)?{/.test(component)
);
}

View File

@ -11,5 +11,8 @@ export default {
menus: [{
path: '/'
}]
},
devServer: {
port: 8080
}
};

View File

@ -30,7 +30,7 @@
"strong"
],
"dependencies": {
"@webank/fes-plugin-built-in": "^2.0.0",
"@webank/fes-preset-built-in": "^2.0.0",
"@webank/fes-core": "^2.0.0",
"@webank/fes-runtime": "^2.0.0",
"@umijs/utils": "3.2.24",

View File

@ -1,6 +1,5 @@
import { dirname } from 'path';
import { Service as CoreService } from '@webank/fes-core';
import innerPlugins from '@webank/fes-plugin-built-in';
class Service extends CoreService {
constructor(opts) {
@ -9,7 +8,11 @@ class Service extends CoreService {
super({
...opts,
plugins: [...innerPlugins, ...(opts.plugins || [])]
presets: [
require.resolve('@webank/fes-preset-built-in'),
...(opts.presets || [])
],
plugins: [...(opts.plugins || [])]
});
}
}

16
vetur.config.js Normal file
View File

@ -0,0 +1,16 @@
// vetur.config.js
/** @type {import('vls').VeturConfig} */
module.exports = {
// **optional** default: `{}`
// override vscode settings
// Notice: It only affects the settings used by Vetur.
settings: {
'vetur.useWorkspaceDependencies': true,
'vetur.experimental.templateInterpolationService': true
},
// **optional** default: `[{ root: './' }]`
// support monorepos
projects: [
'./packages/fes-template' // shorthand for only root
]
};