UNPKG

@vnxjs/service

Version:
341 lines (312 loc) 10.2 kB
import { createDebug, createSwcRegister, NODE_MODULES, recursiveFindNodeModules } from '@vnxjs/helper' import * as helper from '@vnxjs/helper' import { IProjectConfig, PluginItem } from '@vnxjs/vnmf/types/compile' import { EventEmitter } from 'events' import { merge } from 'lodash' import * as path from 'path' import { AsyncSeriesWaterfallHook } from 'tapable' import Config from './Config' import Plugin from './Plugin' import { convertPluginsToObject, mergePlugins, printHelpLog, resolvePresetsOrPlugins } from './utils' import { IS_ADD_HOOK, IS_EVENT_HOOK, IS_MODIFY_HOOK, PluginType } from './utils/constants' import { ICommand, IHook, IPaths, IPlatform, IPlugin, IPluginsObject, IPreset } from './utils/types' interface IKernelOptions { appPath: string presets?: PluginItem[] plugins?: PluginItem[] } export default class Kernel extends EventEmitter { appPath: string isWatch: boolean isProduction: boolean optsPresets: PluginItem[] | void optsPlugins: PluginItem[] | void plugins: Map<string, IPlugin> paths: IPaths extraPlugins: IPluginsObject config: Config initialConfig: IProjectConfig hooks: Map<string, IHook[]> methods: Map<string, ((...args: any[]) => void)[]> commands: Map<string, ICommand> platforms: Map<string, IPlatform> helper: any runOpts: any debugger: any constructor (options: IKernelOptions) { super() this.debugger = process.env.DEBUG === 'Vnmf:Kernel' ? createDebug('Vnmf:Kernel') : function () {} this.appPath = options.appPath || process.cwd() this.optsPresets = options.presets this.optsPlugins = options.plugins this.hooks = new Map() this.methods = new Map() this.commands = new Map() this.platforms = new Map() this.initHelper() this.initConfig() this.initPaths() } initConfig () { this.config = new Config({ appPath: this.appPath }) this.initialConfig = this.config.initialConfig this.debugger('initConfig', this.initialConfig) } initPaths () { this.paths = { appPath: this.appPath, nodeModulesPath: recursiveFindNodeModules(path.join(this.appPath, NODE_MODULES)) } as IPaths if (this.config.isInitSuccess) { Object.assign(this.paths, { configPath: this.config.configPath, sourcePath: path.join(this.appPath, this.initialConfig.sourceRoot as string), outputPath: path.join(this.appPath, this.initialConfig.outputRoot as string) }) } this.debugger(`initPaths:${JSON.stringify(this.paths, null, 2)}`) } initHelper () { this.helper = helper this.debugger('initHelper') } initPresetsAndPlugins () { const initialConfig = this.initialConfig const allConfigPresets = mergePlugins(this.optsPresets || [], initialConfig.presets || [])() const allConfigPlugins = mergePlugins(this.optsPlugins || [], initialConfig.plugins || [])() this.debugger('initPresetsAndPlugins', allConfigPresets, allConfigPlugins) process.env.NODE_ENV !== 'test' && createSwcRegister({ only: [...Object.keys(allConfigPresets), ...Object.keys(allConfigPlugins)] }) this.plugins = new Map() this.extraPlugins = {} this.resolvePresets(allConfigPresets) this.resolvePlugins(allConfigPlugins) } resolvePresets (presets) { const allPresets = resolvePresetsOrPlugins(this.appPath, presets, PluginType.Preset) while (allPresets.length) { this.initPreset(allPresets.shift()!) } } resolvePlugins (plugins) { plugins = merge(this.extraPlugins, plugins) const allPlugins = resolvePresetsOrPlugins(this.appPath, plugins, PluginType.Plugin) while (allPlugins.length) { this.initPlugin(allPlugins.shift()!) } this.extraPlugins = {} } initPreset (preset: IPreset) { this.debugger('initPreset', preset) const { id, path, opts, apply } = preset const pluginCtx = this.initPluginCtx({ id, path, ctx: this }) const { presets, plugins } = apply()(pluginCtx, opts) || {} this.registerPlugin(preset) if (Array.isArray(presets)) { const _presets = resolvePresetsOrPlugins(this.appPath, convertPluginsToObject(presets)(), PluginType.Preset) while (_presets.length) { this.initPreset(_presets.shift()!) } } if (Array.isArray(plugins)) { this.extraPlugins = merge(this.extraPlugins, convertPluginsToObject(plugins)()) } } initPlugin (plugin: IPlugin) { const { id, path, opts, apply } = plugin const pluginCtx = this.initPluginCtx({ id, path, ctx: this }) this.debugger('initPlugin', plugin) this.registerPlugin(plugin) apply()(pluginCtx, opts) this.checkPluginOpts(pluginCtx, opts) } checkPluginOpts (pluginCtx, opts) { if (typeof pluginCtx.optsSchema !== 'function') { return } this.debugger('checkPluginOpts', pluginCtx) const joi = require('joi') const schema = pluginCtx.optsSchema(joi) if (!joi.isSchema(schema)) { throw new Error(`Plugins${pluginCtx.id}_Other Organiser schema There's been a mistake.,Check it out, please.!`) } const { error } = schema.validate(opts) if (error) { error.message = `Plugins${pluginCtx.id}The parameters obtained did not meet the requirements,Check it out, please.!` throw error } } registerPlugin (plugin: IPlugin) { this.debugger('registerPlugin', plugin) if (this.plugins.has(plugin.id)) { throw new Error(`Plugins ${plugin.id} Registered`) } this.plugins.set(plugin.id, plugin) } initPluginCtx ({ id, path, ctx }: { id: string, path: string, ctx: Kernel }) { const pluginCtx = new Plugin({ id, path, ctx }) const internalMethods = ['onReady', 'onStart'] const kernelApis = [ 'appPath', 'plugins', 'platforms', 'paths', 'helper', 'runOpts', 'initialConfig', 'applyPlugins' ] internalMethods.forEach(name => { if (!this.methods.has(name)) { pluginCtx.registerMethod(name) } }) return new Proxy(pluginCtx, { get: (target, name: string) => { if (this.methods.has(name)) { const method = this.methods.get(name) if (Array.isArray(method)) { return (...arg) => { method.forEach(item => { item.apply(this, arg) }) } } return method } if (kernelApis.includes(name)) { return typeof this[name] === 'function' ? this[name].bind(this) : this[name] } return target[name] } }) } async applyPlugins (args: string | { name: string, initialVal?: any, opts?: any }) { let name let initialVal let opts if (typeof args === 'string') { name = args } else { name = args.name initialVal = args.initialVal opts = args.opts } this.debugger('applyPlugins') this.debugger(`applyPlugins:name:${name}`) this.debugger(`applyPlugins:initialVal:${initialVal}`) this.debugger(`applyPlugins:opts:${opts}`) if (typeof name !== 'string') { throw new Error('Call Failed,No correct name entered!') } const hooks = this.hooks.get(name) || [] if (!hooks.length) { return await initialVal } const waterfall = new AsyncSeriesWaterfallHook(['arg']) if (hooks.length) { const resArr: any[] = [] for (const hook of hooks) { waterfall.tapPromise({ name: hook.plugin!, stage: hook.stage || 0, // @ts-ignore before: hook.before }, async arg => { const res = await hook.fn(opts, arg) if (IS_MODIFY_HOOK.test(name) && IS_EVENT_HOOK.test(name)) { return res } if (IS_ADD_HOOK.test(name)) { resArr.push(res) return resArr } return null }) } } return await waterfall.promise(initialVal) } runWithPlatform (platform) { if (!this.platforms.has(platform)) { throw new Error(`No compilation platform exists ${platform}`) } const withNameConfig = this.config.getConfigWithNamed(platform, this.platforms.get(platform)!.useConfigName) return withNameConfig } setRunOpts (opts) { this.runOpts = opts } runHelp (name: string) { const command = this.commands.get(name) const defaultOptionsMap = new Map() defaultOptionsMap.set('-h, --help', 'output usage information') let customOptionsMap = new Map() if (command?.optionsMap) { customOptionsMap = new Map(Object.entries(command?.optionsMap)) } const optionsMap = new Map([...customOptionsMap, ...defaultOptionsMap]) printHelpLog(name, optionsMap, command?.synopsisList ? new Set(command?.synopsisList) : new Set()) } async run (args: string | { name: string, opts?: any }) { let name let opts if (typeof args === 'string') { name = args } else { name = args.name opts = args.opts } this.debugger('command:run') this.debugger(`command:run:name:${name}`) this.debugger('command:runOpts') this.debugger(`command:runOpts:${JSON.stringify(opts, null, 2)}`) this.setRunOpts(opts) this.debugger('initPresetsAndPlugins') this.initPresetsAndPlugins() await this.applyPlugins('onReady') this.debugger('command:onStart') await this.applyPlugins('onStart') if (!this.commands.has(name)) { throw new Error(`${name} Command does not exist`) } if (opts?.isHelp) { return this.runHelp(name) } if (opts?.options?.platform) { opts.config = this.runWithPlatform(opts.options.platform) await this.applyPlugins({ name: 'modifyRunnerOpts', opts: { opts: opts?.config } }) } await this.applyPlugins({ name, opts }) } }