From 3cd44a889548912e179e696d93d60cf1fe4d19c4 Mon Sep 17 00:00:00 2001 From: the1812 Date: Wed, 16 Mar 2022 21:51:51 +0800 Subject: [PATCH] Add ScopedConsole API (#3105) --- src/client/init.ts | 7 +++ src/core/utils/log.ts | 142 +++++++++++++++++++++++++++++++++++++++++- src/plugins/hook.ts | 4 +- 3 files changed, 150 insertions(+), 3 deletions(-) diff --git a/src/client/init.ts b/src/client/init.ts index 7d09013ab..af900223b 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -42,6 +42,13 @@ export const init = async () => { window.dqa = coreApis.utils.dqa window.none = coreApis.utils.none window.componentsTags = coreApis.componentApis.component.componentsTags + window.console = coreApis.utils.log.useScopedConsole({ + name: 'Bilibili Evolved', + color: '#00A0D8', + }) + // window.console 和 console 是独立的引用 + // eslint-disable-next-line no-global-assign + console = window.console const { loadAllUserComponents } = await import('@/components/component') await promiseLoadTrace('parse user components', loadAllUserComponents) diff --git a/src/core/utils/log.ts b/src/core/utils/log.ts index 1721afce5..66a0348bf 100644 --- a/src/core/utils/log.ts +++ b/src/core/utils/log.ts @@ -1,10 +1,17 @@ +import { getHook } from '@/plugins/hook' +import { getGeneralSettings } from '../settings' + +/** + * 向 console 中输出错误消息, 并弹出 Toast 提示. 如果开启了开发者模式且传入了 Error 对象, 则会输出整个堆栈 + * @param error Error 对象或错误消息 + * @param duration Toast 展示时间 + */ export const logError = async (error: Error | string, duration?: number) => { let finalMessage: string if (typeof error === 'string') { finalMessage = error console.error(finalMessage) } else { - const { getGeneralSettings } = await import('../settings') if (getGeneralSettings().devMode) { finalMessage = error.stack } else { @@ -15,3 +22,136 @@ export const logError = async (error: Error | string, duration?: number) => { const { Toast } = await import('../toast') Toast.error(finalMessage, '错误', duration) } + +/** + * 可添加到 ScopedConsole 的前缀 + */ +export interface ConsoleBadge { + /** 名称 */ + name: string + /** 背景色 */ + color?: string +} +interface ScopedData { + readonly badgeNames: string[] + readonly badgeValues: string[] + readonly original: (...args: any[]) => void +} +const ScopedConsoleSymbol = Symbol('ScopedConsole') +const NamePatchSymbol = Symbol('NamePatch') +const specialPalette = { + default: '#78909C', + warn: '#CC7A00', + error: '#BF6060', + group: '#9575CD', +} +const functionNamePatch = (target: any, names: string[]) => { + names.forEach(name => { + if (!target[name][NamePatchSymbol]) { + target[name][NamePatchSymbol] = name + } + }) +} +/** 创建 ScopedConsole 时触发的 Hook */ +export const ScopedConsoleCreateHook = 'scopedConsole.create' +/** ScopedConsole 支持的函数被调用时触发的 Hook */ +export const ScopedConsoleCallHook = 'scopedConsole.call' +/** + * 创建一个 ScopedConsole, 为输出的日志添加固定的前缀 + * @param consoleBadge 前缀信息 + * @param console 原型对象 + */ +export const useScopedConsole = (consoleBadge: ConsoleBadge, console = window.console) => { + const { before: beforeCreate, after: afterCreate } = getHook(ScopedConsoleCreateHook) + beforeCreate(consoleBadge, console) + let groupCounter = 0 + const prependBadge = ( + target: (...args: any[]) => void, + badge: ConsoleBadge, + firstColor = badge.color, + ) => { + const lastScopedData: ScopedData = target[ScopedConsoleSymbol] + const backgroundColor = (lastScopedData ? badge.color : firstColor) ?? specialPalette.default + const textColor = '#fff' + const currentScopedData: ScopedData = { + badgeNames: [...(lastScopedData?.badgeNames ?? []), `%c${badge.name}`], + badgeValues: [...(lastScopedData?.badgeValues ?? []), `background-color: ${backgroundColor}; color: ${textColor}; padding: 2px 4px; border-radius: 4px; margin-left: ${lastScopedData ? 6 : 0}px`], + original: lastScopedData?.original ?? target, + } + const rootTarget = currentScopedData.original + const patchedLog = function patchedLog(...args: any[]) { + const hookPayload = { + type: rootTarget[NamePatchSymbol], + args, + } + const { before: beforeCall, after: afterCall } = getHook(ScopedConsoleCallHook) + beforeCall(hookPayload) + afterCall(hookPayload) + if (groupCounter === 0) { + return rootTarget.apply(this, [ + currentScopedData.badgeNames.join(''), + ...currentScopedData.badgeValues, + ...args, + ]) + } + return rootTarget.apply(this, args) + } + patchedLog[ScopedConsoleSymbol] = currentScopedData + return patchedLog + } + const prependGroupBadge = ( + target: (...args: any[]) => void, + badge: ConsoleBadge, + firstColor = badge.color, + counter: (num: number) => number = n => n, + ) => { + const patch = prependBadge(target, badge, firstColor) + return function patchedGroup(...args: any[]) { + const returnValue = patch.apply(this, args) + groupCounter = counter(groupCounter) + return returnValue + } + } + + // 为各个函数补充名称, 方便 Hook 拿到被调用的函数类型 + functionNamePatch(console, [ + 'log', + 'info', + 'warn', + 'error', + 'group', + 'groupCollapsed', + 'groupEnd', + 'debug', + ]) + + const scopedConsole = { + ...console, + } + + scopedConsole.log = prependBadge(console.log, consoleBadge) + scopedConsole.info = prependBadge(console.info, consoleBadge) + scopedConsole.warn = prependBadge(console.warn, consoleBadge, specialPalette.warn) + scopedConsole.error = prependBadge(console.error, consoleBadge, specialPalette.error) + + scopedConsole.group = prependGroupBadge( + console.group, consoleBadge, specialPalette.group, n => n + 1, + ) + scopedConsole.groupCollapsed = prependGroupBadge( + console.groupCollapsed, consoleBadge, specialPalette.group, n => n + 1, + ) + scopedConsole.groupEnd = prependGroupBadge( + console.groupEnd, consoleBadge, specialPalette.group, n => n - 1, + ) + scopedConsole.debug = (() => { + const patch = prependBadge(console.debug, consoleBadge) + return function patchedDebug(...args: any[]) { + if (!getGeneralSettings().devMode) { + return undefined + } + return patch.apply(this, args) + } + })() + afterCreate(consoleBadge, scopedConsole) + return scopedConsole +} diff --git a/src/plugins/hook.ts b/src/plugins/hook.ts index 90461d88a..52bfea4c3 100644 --- a/src/plugins/hook.ts +++ b/src/plugins/hook.ts @@ -13,7 +13,7 @@ const pluginHookMap = new Map< >() /** - * 向由`key`指定的目标注入代码 + * 向由 `key` 指定的目标注入代码 * @param key 标识ID * @param provider 代码注入的配置对象 */ @@ -29,7 +29,7 @@ export const addHook = (key: string, provider: PluginHookProvider) => { } /** - * 根据`key`获取已添加的代码注入 + * 根据 `key` 获取已添加的代码注入 * @param key 标识ID * @param fixedArgs 运行代码注入函数时, 传入的固定参数 */