diff --git a/packages/kit/src/client/rpc.ts b/packages/kit/src/client/rpc.ts index 6d54ae6..98740f0 100644 --- a/packages/kit/src/client/rpc.ts +++ b/packages/kit/src/client/rpc.ts @@ -12,11 +12,60 @@ function isNumeric(str: string | number | undefined) { return `${+str}` === `${str}` } +interface RpcCacheOptions { + functions: string[] +} + +// @TODO: should be moved to birpc-x? +class RpcCacheManager { + private cacheMap = new Map>() + private options: RpcCacheOptions + + constructor(options: RpcCacheOptions) { + this.options = options + } + + updateOptions(options: Partial) { + this.options = { + ...this.options, + ...options, + } + } + + cached(m: string, a: unknown[]) { + const methodCache = this.cacheMap.get(m) + if (methodCache) { + return methodCache.get(JSON.stringify(a)) + } + return undefined + } + + apply(req: { m: string, a: unknown[] }, res: unknown) { + const methodCache = this.cacheMap.get(req.m) || new Map() + methodCache.set(JSON.stringify(req.a), res) + this.cacheMap.set(req.m, methodCache) + } + + validate(m: string) { + return this.options.functions.includes(m) + } + + invalidate(key?: string) { + if (key) { + this.cacheMap.delete(key) + } + else { + this.cacheMap.clear() + } + } +} + export interface DevToolsRpcClientOptions { connectionMeta?: ConnectionMeta baseURL?: string[] wsOptions?: Partial rpcOptions?: Partial> + cacheOptions?: boolean | Partial } export type DevToolsRpcClient = BirpcReturn @@ -27,12 +76,25 @@ export interface ClientRpcReturn { clientRpc: DevToolsClientRpcHost } +export async function getDevToolsRpcClient( + options: DevToolsRpcClientOptions & { cacheOptions: false }, +): Promise +export async function getDevToolsRpcClient( + options: DevToolsRpcClientOptions & { cacheOptions: true }, +): Promise +export async function getDevToolsRpcClient( + options: DevToolsRpcClientOptions & { cacheOptions: Partial }, +): Promise +export async function getDevToolsRpcClient( + options?: DevToolsRpcClientOptions, +): Promise export async function getDevToolsRpcClient( options: DevToolsRpcClientOptions = {}, ): Promise { const { baseURL = '/.devtools/', rpcOptions = {}, + cacheOptions = false, } = options const urls = Array.isArray(baseURL) ? baseURL : [baseURL] let connectionMeta: ConnectionMeta | undefined = options.connectionMeta @@ -60,6 +122,7 @@ export async function getDevToolsRpcClient( ? `${location.protocol.replace('http', 'ws')}//${location.hostname}:${connectionMeta.websocket}` : connectionMeta.websocket as string + const cacheManager = cacheOptions ? new RpcCacheManager({ functions: [], ...(typeof options.cacheOptions === 'object' ? options.cacheOptions : {}) }) : null const context: DevToolsClientContext = { rpc: undefined!, } @@ -71,7 +134,25 @@ export async function getDevToolsRpcClient( url, ...options.wsOptions, }), - rpcOptions, + rpcOptions: { + ...rpcOptions, + onRequest: async (req, next, resolve) => { + await rpcOptions.onRequest?.(req, next, resolve) + if (cacheOptions && cacheManager?.validate(req.m)) { + const cached = cacheManager.cached(req.m, req.a) + if (cached) { + return resolve(cached) + } + else { + const res = await next(req) + cacheManager?.apply(req, res) + } + } + else { + await next(req) + } + }, + }, }, ) // @ts-expect-error assign to readonly property @@ -81,5 +162,6 @@ export async function getDevToolsRpcClient( connectionMeta, rpc, clientRpc, + ...(cacheOptions ? { cacheManager } : {}), } } diff --git a/packages/vite/src/app/composables/rpc.ts b/packages/vite/src/app/composables/rpc.ts index 09b6f23..96b2e38 100644 --- a/packages/vite/src/app/composables/rpc.ts +++ b/packages/vite/src/app/composables/rpc.ts @@ -23,6 +23,7 @@ export async function connect() { '/.devtools/', runtimeConfig.app.baseURL, ], + cacheOptions: true, connectionMeta: runtimeConfig.app.connection, wsOptions: { onConnected: () => { @@ -44,6 +45,16 @@ export async function connect() { }) rpc.value = result.rpc + + const functions = await rpc.value.$call('vite:core:list-rpc-functions') + + // TODO: add cacheable option to birpc-x and use it here + // @ts-expect-error skip type check + const cacheableFunctions = Object.keys(functions).filter(name => functions[name]?.cacheable) + result.cacheManager.updateOptions({ + functions: [...cacheableFunctions], + }) + connectionState.connected = true } catch (e) {