mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge pull request #3195 from timongh/change-code-loading
改进代码字符串的加载方式2
This commit is contained in:
commit
e651e33863
@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
addComponentListener,
|
addComponentListener,
|
||||||
|
getComponentSettings,
|
||||||
getGeneralSettings,
|
getGeneralSettings,
|
||||||
isComponentEnabled,
|
isComponentEnabled,
|
||||||
getComponentSettings,
|
|
||||||
} from '@/core/settings'
|
} from '@/core/settings'
|
||||||
import { fullyLoaded, contentLoaded } from '@/core/life-cycle'
|
import { contentLoaded, fullyLoaded } from '@/core/life-cycle'
|
||||||
import { LoadingMode } from '@/core/loading-mode'
|
import { LoadingMode } from '@/core/loading-mode'
|
||||||
import { Widget } from '@/components/widget'
|
import { Widget } from '@/components/widget'
|
||||||
import { ComponentMetadata } from './types'
|
import { ComponentMetadata } from './types'
|
||||||
@ -26,7 +26,10 @@ const loadI18n = async (component: ComponentMetadata) => {
|
|||||||
}
|
}
|
||||||
const { addI18nData } = await import('@/components/i18n/helpers')
|
const { addI18nData } = await import('@/components/i18n/helpers')
|
||||||
Object.entries(component.i18n).forEach(([language, data]) => {
|
Object.entries(component.i18n).forEach(([language, data]) => {
|
||||||
const { map = [], regex = [] } = data
|
const {
|
||||||
|
map = [],
|
||||||
|
regex = [],
|
||||||
|
} = data
|
||||||
addI18nData(language, map, regex)
|
addI18nData(language, map, regex)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -35,14 +38,20 @@ const getComponentWidgetName = (component: ComponentMetadata) => `${component.na
|
|||||||
/** 提取组件中的Widget */
|
/** 提取组件中的Widget */
|
||||||
const loadWidget = async (component: ComponentMetadata) => {
|
const loadWidget = async (component: ComponentMetadata) => {
|
||||||
if (component.widget) {
|
if (component.widget) {
|
||||||
const widget: Widget = { ...component.widget, name: getComponentWidgetName(component) }
|
const widget: Widget = {
|
||||||
|
...component.widget,
|
||||||
|
name: getComponentWidgetName(component),
|
||||||
|
}
|
||||||
const { addData } = await import('@/plugins/data')
|
const { addData } = await import('@/plugins/data')
|
||||||
const { WidgetsPlugin } = await import('./settings-panel')
|
const { WidgetsPlugin } = await import('./settings-panel')
|
||||||
addData(WidgetsPlugin, (widgets: Widget[]) => {
|
addData(WidgetsPlugin, (widgets: Widget[]) => {
|
||||||
if (widgets.find(w => w.name === widget.name)) {
|
if (widgets.find(w => w.name === widget.name)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { urlInclude, urlExclude } = widget
|
const {
|
||||||
|
urlInclude,
|
||||||
|
urlExclude,
|
||||||
|
} = widget
|
||||||
if (component.urlInclude) {
|
if (component.urlInclude) {
|
||||||
if (urlInclude) {
|
if (urlInclude) {
|
||||||
urlInclude.push(...component.urlInclude)
|
urlInclude.push(...component.urlInclude)
|
||||||
@ -66,7 +75,7 @@ const loadedComponents: Record<string, Record<string, any>> = {}
|
|||||||
/** 获取已加载组件的导出内容
|
/** 获取已加载组件的导出内容
|
||||||
* @param name 组件名称
|
* @param name 组件名称
|
||||||
*/
|
*/
|
||||||
export const importComponent = <ComponentExportType> (
|
export const importComponent = <ComponentExportType>(
|
||||||
name: string,
|
name: string,
|
||||||
): Record<string, ComponentExportType> => {
|
): Record<string, ComponentExportType> => {
|
||||||
if (!(name in loadedComponents)) {
|
if (!(name in loadedComponents)) {
|
||||||
@ -137,12 +146,17 @@ export const loadComponent = async (component: ComponentMetadata) => {
|
|||||||
/** 加载所有用户组件的定义 (不运行) */
|
/** 加载所有用户组件的定义 (不运行) */
|
||||||
export const loadAllUserComponents = async () => {
|
export const loadAllUserComponents = async () => {
|
||||||
const { settings } = await import('@/core/settings')
|
const { settings } = await import('@/core/settings')
|
||||||
const { batchParseCode } = await import('@/core/external-input')
|
const {
|
||||||
|
loadFeaturesFromCodes,
|
||||||
|
FeatureKind,
|
||||||
|
} = await import('@/core/external-input/load-features-from-codes')
|
||||||
const loadUserComponent = (component: ComponentMetadata) => {
|
const loadUserComponent = (component: ComponentMetadata) => {
|
||||||
components.push(component)
|
components.push(component)
|
||||||
componentsMap[component.name] = component
|
componentsMap[component.name] = component
|
||||||
}
|
}
|
||||||
const userComponents = await batchParseCode<ComponentMetadata>(
|
const userComponents = await loadFeaturesFromCodes(
|
||||||
|
FeatureKind.Component,
|
||||||
|
Object.keys(settings.userComponents),
|
||||||
Object.values(settings.userComponents).map(it => it.code),
|
Object.values(settings.userComponents).map(it => it.code),
|
||||||
)
|
)
|
||||||
userComponents.forEach(loadUserComponent)
|
userComponents.forEach(loadUserComponent)
|
||||||
@ -153,12 +167,12 @@ export const loadAllComponents = async () => {
|
|||||||
const { loadAllPlugins } = await import('@/plugins/plugin')
|
const { loadAllPlugins } = await import('@/plugins/plugin')
|
||||||
const loadComponents = () => loadAllPlugins(components)
|
const loadComponents = () => loadAllPlugins(components)
|
||||||
.then(() => Promise.allSettled(components.map(loadI18n)))
|
.then(() => Promise.allSettled(components.map(loadI18n)))
|
||||||
.then(() => Promise.allSettled(components.map(loadComponent)))
|
.then(() => Promise.allSettled(components.map(loadComponent))).then(async () => {
|
||||||
.then(async () => {
|
|
||||||
if (generalSettings.devMode) {
|
if (generalSettings.devMode) {
|
||||||
const { componentLoadTime, componentResolveTime } = await import(
|
const {
|
||||||
'@/core/performance/component-trace'
|
componentLoadTime,
|
||||||
)
|
componentResolveTime,
|
||||||
|
} = await import('@/core/performance/component-trace')
|
||||||
const { logStats } = await import('@/core/performance/stats')
|
const { logStats } = await import('@/core/performance/stats')
|
||||||
logStats('components block', componentLoadTime)
|
logStats('components block', componentLoadTime)
|
||||||
logStats('components resolve', componentResolveTime)
|
logStats('components resolve', componentResolveTime)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { pickFile } from './file-picker'
|
import { pickFile } from '@/core/file-picker'
|
||||||
|
|
||||||
/** 外部输入包装类型, 详见`parseExternalInput`的文档 */
|
/** 外部输入包装类型, 详见`parseExternalInput`的文档 */
|
||||||
export type ExternalInput<T> = undefined | string | T
|
export type ExternalInput<T> = undefined | string | T
|
||||||
@ -54,22 +54,3 @@ export const parseExternalInput = async <T>(input: ExternalInput<T>): Promise<T>
|
|||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export const batchParseCode = async<T>(inputs: string[]): Promise<T[]> => {
|
|
||||||
try {
|
|
||||||
const exports = {}
|
|
||||||
const result = inputs.map(input => eval(input) as T)
|
|
||||||
if (Object.values(exports).length > 0) {
|
|
||||||
const { coreApis } = await import('@/core/core-apis')
|
|
||||||
return Object.values(exports).map(value => {
|
|
||||||
if (typeof value === 'function') {
|
|
||||||
return value(coreApis) as T
|
|
||||||
}
|
|
||||||
return value as T
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
src/core/external-input/load-feature-code-all-settled.ts
Normal file
31
src/core/external-input/load-feature-code-all-settled.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {
|
||||||
|
loadFeatureCode, LoadFeatureCodeResult,
|
||||||
|
} from '@/core/external-input/load-feature-code'
|
||||||
|
|
||||||
|
type LdRes<X> = LoadFeatureCodeResult<X>
|
||||||
|
type SettledRes<T> = PromiseSettledResult<T>
|
||||||
|
type FilledRes<T> = PromiseFulfilledResult<T>
|
||||||
|
|
||||||
|
const unwrapSettledResult = <T>(r: SettledRes<T>): T => (r as FilledRes<T>).value
|
||||||
|
|
||||||
|
const mapSettledArray = <T>(arr: SettledRes<T>[]): T[] => (
|
||||||
|
arr.map(unwrapSettledResult)
|
||||||
|
)
|
||||||
|
|
||||||
|
const mapSettleResult = <T>(p: Promise<SettledRes<T>[]>): Promise<T[]> => (
|
||||||
|
p.then(mapSettledArray)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量加载组件或插件的代码字符串,获取其导出 feature
|
||||||
|
*
|
||||||
|
* @param codes 代码字符串数组
|
||||||
|
* @returns 不会失败的 `Promise`。其结果为一个数组,其中每个元素都是代表代码执行结果的对象
|
||||||
|
*/
|
||||||
|
export const loadFeatureCodeAllSettled = <X>(
|
||||||
|
codes: string[],
|
||||||
|
): Promise<LoadFeatureCodeResult<X>[]> => lodash(codes)
|
||||||
|
.map<Promise<LdRes<X>>>(loadFeatureCode)
|
||||||
|
.thru<Promise<SettledRes<LdRes<X>>[]>>(arr => Promise.allSettled(arr))
|
||||||
|
.thru(mapSettleResult)
|
||||||
|
.value()
|
||||||
164
src/core/external-input/load-feature-code-all.ts
Normal file
164
src/core/external-input/load-feature-code-all.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import {
|
||||||
|
loadFeatureCode, LoadFeatureCodeResult, LoadFeatureCodeResultError,
|
||||||
|
LoadFeatureCodeResultOk,
|
||||||
|
} from '@/core/external-input/load-feature-code'
|
||||||
|
import { FeatureBase } from '@/components/types'
|
||||||
|
|
||||||
|
interface ResultInstance {
|
||||||
|
readonly isOk: <X extends FeatureBase>(
|
||||||
|
this: LoadFeatureCodeAllResult<X>
|
||||||
|
) => this is LoadFeatureCodeAllResultOk<X>
|
||||||
|
|
||||||
|
readonly isError: (
|
||||||
|
this: LoadFeatureCodeAllResult<FeatureBase>
|
||||||
|
) => this is LoadFeatureCodeAllResultError
|
||||||
|
|
||||||
|
readonly isNoExport: (
|
||||||
|
this: LoadFeatureCodeAllResult<FeatureBase>
|
||||||
|
) => this is LoadFeatureCodeAllResultNoExport
|
||||||
|
|
||||||
|
readonly isCodeThrew: (
|
||||||
|
this: LoadFeatureCodeAllResult<FeatureBase>
|
||||||
|
) => this is LoadFeatureCodeAllResultCodeThrew
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功从代码中获取 features
|
||||||
|
*
|
||||||
|
* @namespace
|
||||||
|
* @property features 从代码中获取的导出值
|
||||||
|
*/
|
||||||
|
interface LoadFeatureCodeAllResultOk<X extends FeatureBase> extends ResultInstance {
|
||||||
|
readonly tag: 'Ok',
|
||||||
|
readonly features: X[],
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 代码没有导出任何值 */
|
||||||
|
interface LoadFeatureCodeAllResultNoExport extends ResultInstance {
|
||||||
|
readonly tag: 'NoExport',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行代码过程中产生了抛出值。
|
||||||
|
*
|
||||||
|
* @namespace
|
||||||
|
* @property thrown 抛出的值
|
||||||
|
*/
|
||||||
|
interface LoadFeatureCodeAllResultCodeThrew extends ResultInstance {
|
||||||
|
readonly tag: 'CodeThrew',
|
||||||
|
readonly thrown: unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadFeatureCodeAllResultError =
|
||||||
|
LoadFeatureCodeAllResultNoExport
|
||||||
|
| LoadFeatureCodeAllResultCodeThrew
|
||||||
|
type LoadFeatureCodeAllResult<X extends FeatureBase> =
|
||||||
|
LoadFeatureCodeAllResultOk<X>
|
||||||
|
| LoadFeatureCodeAllResultError
|
||||||
|
|
||||||
|
const resultProto: ResultInstance = {
|
||||||
|
isOk() { return this.tag === 'Ok' },
|
||||||
|
isError() { return this.tag !== 'Ok' },
|
||||||
|
isNoExport() { return this.tag === 'NoExport' },
|
||||||
|
isCodeThrew() { return this.tag === 'CodeThrew' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const okResult = <X extends FeatureBase>(
|
||||||
|
features: X[],
|
||||||
|
): LoadFeatureCodeAllResultOk<X> => lodash.create(
|
||||||
|
resultProto,
|
||||||
|
{
|
||||||
|
tag: 'Ok' as const,
|
||||||
|
features,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const noExportResult = lodash.create(resultProto, {
|
||||||
|
tag: 'NoExport' as const,
|
||||||
|
})
|
||||||
|
|
||||||
|
const codeThrewResult = (thrown: unknown): LoadFeatureCodeAllResultCodeThrew => lodash.create(
|
||||||
|
resultProto,
|
||||||
|
{
|
||||||
|
tag: 'CodeThrew' as const,
|
||||||
|
thrown,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
type Task<Ok, Err = never> = Promise<Ok>
|
||||||
|
|
||||||
|
type LdRes<X> = LoadFeatureCodeResult<X>
|
||||||
|
type LdOk<X> = LoadFeatureCodeResultOk<X>
|
||||||
|
type LdErr = LoadFeatureCodeResultError
|
||||||
|
|
||||||
|
type LdAllRes<X> = LoadFeatureCodeAllResult<X>
|
||||||
|
type LdAllOk<X> = LoadFeatureCodeAllResultOk<X>
|
||||||
|
type LdAllErr = LoadFeatureCodeAllResultError
|
||||||
|
|
||||||
|
type LoadCodesTask<X> = Task<LdOk<X>[], [number, LdErr]>
|
||||||
|
|
||||||
|
// covert `Task<LdRes<X>>` to `Task<LdOk<X>, LdErr>`
|
||||||
|
const rejectErrorResult = <X>(t: Task<LdRes<X>>): Task<LdOk<X>, LdErr> => (
|
||||||
|
t.then(r => (r.isOk() ? r : Promise.reject(r)))
|
||||||
|
)
|
||||||
|
|
||||||
|
// load feature code, and return `Task<LdOk<X>, LdErr>`
|
||||||
|
const loadCode = <X>(code: string): Task<LdOk<X>, LdErr> => (
|
||||||
|
rejectErrorResult(loadFeatureCode(code))
|
||||||
|
)
|
||||||
|
|
||||||
|
// covert `Task`'s `Err` type from `T` to `[number, T]`
|
||||||
|
const addIndexToRejected = <N extends number, O, E>(
|
||||||
|
t: Task<O, E>,
|
||||||
|
i: N,
|
||||||
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
|
): Task<O, [N, E]> => t.catch(e => Promise.reject([i, e]))
|
||||||
|
|
||||||
|
// create `LdAllOk` from an array of `LdOk`
|
||||||
|
const createOkResult = <X>(arr: LdOk<X>[]): LdAllOk<X> => (
|
||||||
|
okResult(arr.map(r => r.feature))
|
||||||
|
)
|
||||||
|
|
||||||
|
// create `LdAllErr` from `[number, LdErr]`
|
||||||
|
const createErrResult = (t: [number, LdErr]): LdAllErr => (
|
||||||
|
t[1].isCodeThrew()
|
||||||
|
? codeThrewResult(t[1].thrown)
|
||||||
|
: noExportResult
|
||||||
|
)
|
||||||
|
|
||||||
|
// load all feature codes, and return `LoadCodesTask`
|
||||||
|
const loadCodes = <X>(codes: string[]): LoadCodesTask<X> => lodash(codes)
|
||||||
|
.map<Task<LdOk<X>, LdErr>>(loadCode)
|
||||||
|
.map(addIndexToRejected)
|
||||||
|
.thru<LoadCodesTask<X>>(arr => Promise.all(arr))
|
||||||
|
.value()
|
||||||
|
|
||||||
|
// create a `LdAllRes` wrapped by `Task`
|
||||||
|
const createTaskResult = <X>(t: LoadCodesTask<X>): Task<LdAllRes<X>> => t
|
||||||
|
.then(createOkResult)
|
||||||
|
.catch(createErrResult)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量加载组件或插件的代码字符串,获取其导出 feature
|
||||||
|
*
|
||||||
|
* 只要有一个代码出现了错误,则返回错误。
|
||||||
|
*
|
||||||
|
* @param codes 代码字符串数组
|
||||||
|
* @returns 一个不会失败的 `Promise`,其结果值为 `LoadFeatureCodeAllResult`
|
||||||
|
*/
|
||||||
|
const loadFeatureCodeAll = <X>(codes: string[]): Promise<LoadFeatureCodeAllResult<X>> => (
|
||||||
|
lodash(codes)
|
||||||
|
.thru<LoadCodesTask<X>>(loadCodes)
|
||||||
|
.thru(createTaskResult)
|
||||||
|
.value()
|
||||||
|
)
|
||||||
|
|
||||||
|
export {
|
||||||
|
loadFeatureCodeAll,
|
||||||
|
LoadFeatureCodeAllResult,
|
||||||
|
LoadFeatureCodeAllResultOk,
|
||||||
|
LoadFeatureCodeAllResultError,
|
||||||
|
LoadFeatureCodeAllResultNoExport,
|
||||||
|
LoadFeatureCodeAllResultCodeThrew,
|
||||||
|
}
|
||||||
112
src/core/external-input/load-feature-code.ts
Normal file
112
src/core/external-input/load-feature-code.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { FeatureBase } from '@/components/types'
|
||||||
|
|
||||||
|
interface ResultInstance {
|
||||||
|
readonly isOk: <X extends FeatureBase>(
|
||||||
|
this: LoadFeatureCodeResult<X>
|
||||||
|
) => this is LoadFeatureCodeResultOk<X>
|
||||||
|
|
||||||
|
readonly isError: (
|
||||||
|
this: LoadFeatureCodeResult<FeatureBase>
|
||||||
|
) => this is LoadFeatureCodeResultError
|
||||||
|
|
||||||
|
readonly isNoExport: (
|
||||||
|
this: LoadFeatureCodeResult<FeatureBase>
|
||||||
|
) => this is LoadFeatureCodeResultNoExport
|
||||||
|
|
||||||
|
readonly isCodeThrew: (
|
||||||
|
this: LoadFeatureCodeResult<FeatureBase>
|
||||||
|
) => this is LoadFeatureCodeResultCodeThrew
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功从代码中获取 feature
|
||||||
|
*
|
||||||
|
* @namespace
|
||||||
|
* @property feature 从代码中获取的导出值
|
||||||
|
*/
|
||||||
|
interface LoadFeatureCodeResultOk<X extends FeatureBase> extends ResultInstance {
|
||||||
|
readonly tag: 'Ok',
|
||||||
|
readonly feature: X,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 代码没有导出任何值 */
|
||||||
|
interface LoadFeatureCodeResultNoExport extends ResultInstance {
|
||||||
|
readonly tag: 'NoExport',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行代码过程中产生了抛出值。
|
||||||
|
*
|
||||||
|
* @namespace
|
||||||
|
* @property thrown 抛出的值
|
||||||
|
*/
|
||||||
|
interface LoadFeatureCodeResultCodeThrew extends ResultInstance {
|
||||||
|
readonly tag: 'CodeThrew',
|
||||||
|
readonly thrown: unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadFeatureCodeResultError =
|
||||||
|
LoadFeatureCodeResultNoExport
|
||||||
|
| LoadFeatureCodeResultCodeThrew
|
||||||
|
type LoadFeatureCodeResult<X extends FeatureBase> =
|
||||||
|
LoadFeatureCodeResultOk<X>
|
||||||
|
| LoadFeatureCodeResultError
|
||||||
|
|
||||||
|
const resultProto: ResultInstance = {
|
||||||
|
isOk() { return this.tag === 'Ok' },
|
||||||
|
isError() { return this.tag !== 'Ok' },
|
||||||
|
isNoExport() { return this.tag === 'NoExport' },
|
||||||
|
isCodeThrew() { return this.tag === 'CodeThrew' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const okResult = <X extends FeatureBase>(feature: X): LoadFeatureCodeResultOk<X> => lodash.create(
|
||||||
|
resultProto,
|
||||||
|
{
|
||||||
|
tag: 'Ok' as const,
|
||||||
|
feature,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const noExportResult = lodash.create(resultProto, {
|
||||||
|
tag: 'NoExport' as const,
|
||||||
|
})
|
||||||
|
|
||||||
|
const codeThrewResult = (thrown: unknown): LoadFeatureCodeResultCodeThrew => lodash.create(
|
||||||
|
resultProto,
|
||||||
|
{
|
||||||
|
tag: 'CodeThrew' as const,
|
||||||
|
thrown,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载组件或插件的代码字符串,获取其导出 feature
|
||||||
|
*
|
||||||
|
* @param code 代码字符串
|
||||||
|
* @returns 一个不会失败的 `Promise`,其结果值为 {@link LoadFeatureCodeResult}
|
||||||
|
*/
|
||||||
|
const loadFeatureCode = async <X extends FeatureBase>(
|
||||||
|
code: string,
|
||||||
|
): Promise<LoadFeatureCodeResult<X>> => {
|
||||||
|
// 收集代码导出值
|
||||||
|
const exports = {}
|
||||||
|
try {
|
||||||
|
eval(code)
|
||||||
|
} catch (thrown) {
|
||||||
|
return codeThrewResult(thrown)
|
||||||
|
}
|
||||||
|
const values = Object.values(exports)
|
||||||
|
if (values.length === 0) {
|
||||||
|
return noExportResult
|
||||||
|
}
|
||||||
|
return okResult(values[0] as X)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
loadFeatureCode,
|
||||||
|
LoadFeatureCodeResult,
|
||||||
|
LoadFeatureCodeResultOk,
|
||||||
|
LoadFeatureCodeResultError,
|
||||||
|
LoadFeatureCodeResultNoExport,
|
||||||
|
LoadFeatureCodeResultCodeThrew,
|
||||||
|
}
|
||||||
124
src/core/external-input/load-features-from-codes.ts
Normal file
124
src/core/external-input/load-features-from-codes.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
LoadFeatureCodeResultError, LoadFeatureCodeResultOk,
|
||||||
|
} from '@/core/external-input/load-feature-code'
|
||||||
|
import { Toast } from '@/core/toast'
|
||||||
|
import { useScopedConsole } from '@/core/utils/log'
|
||||||
|
import { ComponentMetadata } from '@/components/types'
|
||||||
|
import { PluginMetadata } from '@/plugins/plugin'
|
||||||
|
import {
|
||||||
|
loadFeatureCodeAllSettled,
|
||||||
|
} from '@/core/external-input/load-feature-code-all-settled'
|
||||||
|
|
||||||
|
const curConsole = useScopedConsole(
|
||||||
|
'@/core/external-input/load-features-from-codes.ts',
|
||||||
|
)
|
||||||
|
|
||||||
|
export enum FeatureKind {
|
||||||
|
Component = 'Component',
|
||||||
|
Plugin = 'Plugin',
|
||||||
|
}
|
||||||
|
|
||||||
|
const logError = (kind: FeatureKind): (
|
||||||
|
featureName: string, err: LoadFeatureCodeResultError) => void => {
|
||||||
|
const prefix = kind === FeatureKind.Component ? 'component' : 'plugin'
|
||||||
|
return (featureName, err) => {
|
||||||
|
if (err.isNoExport()) {
|
||||||
|
curConsole.error(
|
||||||
|
`${prefix} '${featureName}' exports no value, failed to load`,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
curConsole.error(
|
||||||
|
`${prefix} '${featureName}' throws something when importing, failed to load`,
|
||||||
|
{ thrown: err.thrown },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportErrToUser = (
|
||||||
|
featureKind: FeatureKind,
|
||||||
|
errNames: string[],
|
||||||
|
): void => {
|
||||||
|
type ErrInfo = number | string[]
|
||||||
|
|
||||||
|
const emptyErrInfo: () => string[] = () => []
|
||||||
|
|
||||||
|
const accErrInfo = (acc: ErrInfo, featureName: string): ErrInfo => {
|
||||||
|
if (Array.isArray(acc)) {
|
||||||
|
if (acc.length < 3) {
|
||||||
|
acc.push(featureName)
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
return acc + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportErrInfo = (kind: FeatureKind, info: ErrInfo): void => {
|
||||||
|
const kindName = kind === FeatureKind.Component ? '组件' : '插件'
|
||||||
|
if (Array.isArray(info)) {
|
||||||
|
Toast.error(
|
||||||
|
`${kindName} "${info.join('", "')}" 加载失败。请向我们反馈,以解决此问题。`,
|
||||||
|
`${kindName}加载失败`,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Toast.error(
|
||||||
|
`有 ${info} 个${kindName}加载失败,请向我们反馈,以解决此问题。`,
|
||||||
|
`${kindName}加载失败`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errInfo = errNames.reduce(accErrInfo, emptyErrInfo())
|
||||||
|
reportErrInfo(featureKind, errInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FeatureMetadata = ComponentMetadata | PluginMetadata
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量加载组件或插件代码
|
||||||
|
*
|
||||||
|
* 如果遇到错误会向 console 和用户输出错误信息
|
||||||
|
*
|
||||||
|
* `names` 和 `codes` 应该是一一对应的
|
||||||
|
*
|
||||||
|
* @param kind 组件或插件类型
|
||||||
|
* @param names 组件或插件名称
|
||||||
|
* @param codes 组件或插件代码
|
||||||
|
* @return 返回加载成功的组件或插件
|
||||||
|
*/
|
||||||
|
export async function loadFeaturesFromCodes(
|
||||||
|
kind: FeatureKind.Component,
|
||||||
|
names: string[],
|
||||||
|
codes: string[],
|
||||||
|
): Promise<ComponentMetadata[]>
|
||||||
|
export async function loadFeaturesFromCodes(
|
||||||
|
kind: FeatureKind.Plugin,
|
||||||
|
names: string[],
|
||||||
|
codes: string[],
|
||||||
|
): Promise<PluginMetadata[]>
|
||||||
|
export async function loadFeaturesFromCodes(
|
||||||
|
kind: FeatureKind,
|
||||||
|
names: string[],
|
||||||
|
codes: string[],
|
||||||
|
): Promise<FeatureMetadata[]> {
|
||||||
|
const results = await loadFeatureCodeAllSettled<FeatureMetadata>(codes)
|
||||||
|
const [namedOk, namedErr] = lodash(results)
|
||||||
|
.map((r, i) => [names[i], r] as const)
|
||||||
|
.partition(([, r]) => r.isOk())
|
||||||
|
.value()
|
||||||
|
|
||||||
|
// 输出日志
|
||||||
|
lodash.forEach(namedErr, lodash.spread(logError(kind)))
|
||||||
|
|
||||||
|
// 向用户输出错误报告
|
||||||
|
if (namedErr.length > 0) {
|
||||||
|
const errNames = namedErr.map(([name]) => name)
|
||||||
|
reportErrToUser(kind, errNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lodash.map(
|
||||||
|
namedOk,
|
||||||
|
([, r]) => (r as LoadFeatureCodeResultOk<FeatureMetadata>).feature,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { ComponentMetadata, FeatureBase } from '@/components/component'
|
import { ComponentMetadata, FeatureBase } from '@/components/component'
|
||||||
import { deleteValue } from '@/core/utils'
|
import { deleteValue } from '@/core/utils'
|
||||||
import { CoreApis } from '../core/core-apis'
|
import { CoreApis } from '@/core/core-apis'
|
||||||
import { addData, registerData, registerAndGetData } from './data'
|
import { addData, registerAndGetData, registerData } from './data'
|
||||||
import { addHook, getHook } from './hook'
|
import { addHook, getHook } from './hook'
|
||||||
|
|
||||||
/** 插件初始化时的传入参数, 可以解构并调用 */
|
/** 插件初始化时的传入参数, 可以解构并调用 */
|
||||||
@ -23,6 +23,7 @@ export interface PluginMinimalData extends FeatureBase {
|
|||||||
/** 显示名称, 默认同插件名称 */
|
/** 显示名称, 默认同插件名称 */
|
||||||
displayName?: string
|
displayName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PartialRequired<Target, Props extends keyof Target> = Target & {
|
type PartialRequired<Target, Props extends keyof Target> = Target & {
|
||||||
[P in Props]-?: Target[P]
|
[P in Props]-?: Target[P]
|
||||||
}
|
}
|
||||||
@ -48,7 +49,7 @@ export const plugins: PluginMetadata[] = getBuiltInPlugins()
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 安装插件
|
* 安装插件
|
||||||
* @param input 插件数据
|
* @param code 插件代码
|
||||||
*/
|
*/
|
||||||
export const installPlugin = async (code: string) => {
|
export const installPlugin = async (code: string) => {
|
||||||
const { parseExternalInput } = await import('../core/external-input')
|
const { parseExternalInput } = await import('../core/external-input')
|
||||||
@ -95,13 +96,11 @@ export const installPlugin = async (code: string) => {
|
|||||||
*/
|
*/
|
||||||
export const uninstallPlugin = async (nameOrDisplayName: string) => {
|
export const uninstallPlugin = async (nameOrDisplayName: string) => {
|
||||||
const { settings } = await import('@/core/settings')
|
const { settings } = await import('@/core/settings')
|
||||||
const existingPlugin = Object.entries(settings.userPlugins)
|
const existingPlugin = Object.entries(settings.userPlugins).find(
|
||||||
.find(([name, { displayName }]) => {
|
([name, { displayName }]) => (
|
||||||
if (name === nameOrDisplayName || displayName === nameOrDisplayName) {
|
name === nameOrDisplayName || displayName === nameOrDisplayName
|
||||||
return true
|
),
|
||||||
}
|
)
|
||||||
return false
|
|
||||||
})
|
|
||||||
if (!existingPlugin) {
|
if (!existingPlugin) {
|
||||||
throw new Error(`没有找到与名称'${nameOrDisplayName}'相关联的插件`)
|
throw new Error(`没有找到与名称'${nameOrDisplayName}'相关联的插件`)
|
||||||
}
|
}
|
||||||
@ -155,20 +154,33 @@ export const loadPlugin = async (plugin: PluginMetadata) => {
|
|||||||
* @param components 组件列表
|
* @param components 组件列表
|
||||||
*/
|
*/
|
||||||
export const loadAllPlugins = async (components: ComponentMetadata[]) => {
|
export const loadAllPlugins = async (components: ComponentMetadata[]) => {
|
||||||
const { settings, getGeneralSettings } = await import('@/core/settings')
|
const {
|
||||||
const { batchParseCode } = await import('@/core/external-input')
|
settings,
|
||||||
const otherPlugins = components
|
getGeneralSettings,
|
||||||
|
} = await import('@/core/settings')
|
||||||
|
const {
|
||||||
|
loadFeaturesFromCodes,
|
||||||
|
FeatureKind,
|
||||||
|
} = await import('@/core/external-input/load-features-from-codes')
|
||||||
|
const otherPlugins = lodash(components)
|
||||||
.map(extractPluginFromComponent)
|
.map(extractPluginFromComponent)
|
||||||
.filter(p => p !== null)
|
.filter(p => p !== null)
|
||||||
.concat(await batchParseCode<PluginMetadata>(
|
.map(p => p as PluginMetadata)
|
||||||
|
.concat(await loadFeaturesFromCodes(
|
||||||
|
FeatureKind.Plugin,
|
||||||
|
Object.keys(settings.userPlugins),
|
||||||
Object.values(settings.userPlugins).map(p => p.code),
|
Object.values(settings.userPlugins).map(p => p.code),
|
||||||
))
|
))
|
||||||
|
.value()
|
||||||
plugins.push(...otherPlugins)
|
plugins.push(...otherPlugins)
|
||||||
return Promise.allSettled(
|
return Promise.allSettled(
|
||||||
plugins.map(loadPlugin),
|
plugins.map(loadPlugin),
|
||||||
).then(async () => {
|
).then(async () => {
|
||||||
if (getGeneralSettings().devMode) {
|
if (getGeneralSettings().devMode) {
|
||||||
const { pluginLoadTime, pluginResolveTime } = await import('@/core/performance/plugin-trace')
|
const {
|
||||||
|
pluginLoadTime,
|
||||||
|
pluginResolveTime,
|
||||||
|
} = await import('@/core/performance/plugin-trace')
|
||||||
const { logStats } = await import('@/core/performance/stats')
|
const { logStats } = await import('@/core/performance/stats')
|
||||||
logStats('plugins block', pluginLoadTime)
|
logStats('plugins block', pluginLoadTime)
|
||||||
logStats('plugins resolve', pluginResolveTime)
|
logStats('plugins resolve', pluginResolveTime)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user