@vnxjs/service
Version:
Vnmf Service
341 lines (312 loc) • 10.2 kB
text/typescript
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
})
}
}