diff --git a/.size-limit.js b/.size-limit.js index 317dd0887b..9aae47d55f 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -45,7 +45,7 @@ module.exports = [ }, { path: "packages/brick-kit/dist/index.esm.js", - limit: "130 KB", + limit: "131 KB", }, { path: "packages/brick-types/dist/index.esm.js", diff --git a/declarations/global.d.ts b/declarations/global.d.ts index fb33ee3330..896734ef67 100644 --- a/declarations/global.d.ts +++ b/declarations/global.d.ts @@ -86,6 +86,13 @@ interface Window { /** Use Apache SkyWalking Client-side JavaScript exception and tracing library **/ USE_SKYWALKING_ANALYSIS?: boolean; + + STORYBOARD_FUNCTIONS_PERF?: { + name: string; + source: string; + /** 函数执行耗时列表,单位毫秒 */ + durations: number[]; + }[]; } declare const __webpack_public_path__: string; diff --git a/etc/brick-kit.api.md b/etc/brick-kit.api.md index 44779eeee8..a8b243a403 100644 --- a/etc/brick-kit.api.md +++ b/etc/brick-kit.api.md @@ -474,7 +474,7 @@ export abstract class ModalElement extends UpdatingElement { // Warning: (ae-internal-missing-underscore) The name "PartialMicroApp" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) -export type PartialMicroApp = Pick; +export type PartialMicroApp = Pick; // Warning: (ae-forgotten-export) The symbol "TransformOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-internal-missing-underscore) The name "preprocessTransformProperties" should be prefixed with an underscore because the declaration is marked as @internal diff --git a/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.spec.ts b/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.spec.ts index af3567e51a..7c5bd992b9 100644 --- a/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.spec.ts +++ b/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.spec.ts @@ -85,6 +85,11 @@ describe("StoryboardFunctions", () => { ], { id: "my-app", + config: { + settings: { + perfStoryboardFunctions: true, + }, + }, } ); expect(fn.sayHello({ en: "world", zh: "世界" })).toBe( @@ -113,6 +118,8 @@ describe("StoryboardFunctions", () => { expect(fn.checkPermissions("my:action-b")).toBe(false); expect(fn.getUniqueId()).not.toBe("42"); expect(fn.getUniqueId("test-")).not.toBe("test-42"); + + expect(window.STORYBOARD_FUNCTIONS_PERF?.length).toBe(6); }); it("should register no functions", () => { diff --git a/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.ts b/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.ts index 4e5460f1be..b0ca8b3f5a 100644 --- a/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.ts +++ b/packages/brick-kit/src/core/StoryboardFunctionRegistryFactory.ts @@ -55,7 +55,14 @@ export interface FunctionCoverageSettings { } /** @internal */ -export type PartialMicroApp = Pick; +export type PartialMicroApp = Pick< + MicroApp, + "id" | "isBuildPush" | "currentVersion" | "config" +>; + +interface AppConfigSettings { + perfStoryboardFunctions?: boolean; +} /** @internal */ export function StoryboardFunctionRegistryFactory({ @@ -77,6 +84,7 @@ export function StoryboardFunctionRegistryFactory({ }) as ReadonlyStoryboardFunctions; let currentApp: PartialMicroApp; + let needPerf = false; function registerStoryboardFunctions( functions: StoryboardFunction[], @@ -84,6 +92,12 @@ export function StoryboardFunctionRegistryFactory({ ): void { if (app) { currentApp = app; + needPerf = (app?.config?.settings as AppConfigSettings) + ?.perfStoryboardFunctions; + if (needPerf) { + // Clear perf data when initialize functions. + window.STORYBOARD_FUNCTIONS_PERF = []; + } } registeredFunctions.clear(); if (Array.isArray(functions)) { @@ -130,10 +144,19 @@ export function StoryboardFunctionRegistryFactory({ }), !!collectCoverage ), - hooks: collector && { - beforeEvaluate: collector.beforeEvaluate, - beforeCall: collector.beforeCall, - beforeBranch: collector.beforeBranch, + hooks: { + perfCall: needPerf + ? (duration) => { + perf(name, fn.source, duration); + } + : undefined, + ...(collector + ? { + beforeEvaluate: collector.beforeEvaluate, + beforeCall: collector.beforeCall, + beforeBranch: collector.beforeBranch, + } + : null), }, }) as SimpleFunction; fn.processed = true; @@ -154,3 +177,12 @@ export function StoryboardFunctionRegistryFactory({ }, }; } + +function perf(name: string, source: string, duration: number): void { + const list = (window.STORYBOARD_FUNCTIONS_PERF ??= []); + let data = list.find((item) => item.name === name); + if (!data) { + list.push((data = { name, durations: [], source })); + } + data.durations.push(Math.round(duration * 1000)); +} diff --git a/packages/brick-utils/src/scanAppInStoryboard.ts b/packages/brick-utils/src/scanAppInStoryboard.ts index bf527b5616..1d5c527a24 100644 --- a/packages/brick-utils/src/scanAppInStoryboard.ts +++ b/packages/brick-utils/src/scanAppInStoryboard.ts @@ -16,7 +16,7 @@ export function scanAppGetMenuInStoryboard(storyboard: Storyboard): string[] { matchExpressionString: matchAppGetMenu, } ); - // // `APP` is not available in storyboard functions + // `APP` is not available in storyboard functions return Array.from(collection); } diff --git a/packages/cook/src/cook.spec.ts b/packages/cook/src/cook.spec.ts index 597399ae50..752aa2938d 100644 --- a/packages/cook/src/cook.spec.ts +++ b/packages/cook/src/cook.spec.ts @@ -302,12 +302,14 @@ describe("evaluate", () => { const beforeEvaluate = jest.fn(); const beforeCall = jest.fn(); const beforeBranch = jest.fn(); + const perfCall = jest.fn(); const func = cook(funcAst, source, { globalVariables, hooks: { beforeEvaluate, beforeCall, beforeBranch, + perfCall, }, }) as SimpleFunction; @@ -350,6 +352,7 @@ describe("evaluate", () => { }), "if" ); + expect(perfCall).toBeCalledTimes(1); jest.clearAllMocks(); func(0); @@ -386,6 +389,7 @@ describe("evaluate", () => { }), "else" ); + expect(perfCall).toBeCalledTimes(1); }); it("support RegExp", () => { diff --git a/packages/cook/src/cook.ts b/packages/cook/src/cook.ts index d99be869b7..f6c0b23fb0 100644 --- a/packages/cook/src/cook.ts +++ b/packages/cook/src/cook.ts @@ -1,7 +1,6 @@ import { ArrayPattern, ArrowFunctionExpression, - BlockStatement, CallExpression, CatchClause, DoWhileStatement, @@ -84,6 +83,7 @@ export interface CookHooks { beforeEvaluate?(node: EstreeNode): void; beforeCall?(node: EstreeNode): void; beforeBranch?(node: EstreeNode, branch: "if" | "else"): void; + perfCall?(duration: number): void; } /** For next-core internal usage only. */ @@ -1415,9 +1415,10 @@ export function cook( // https://tc39.es/ecma262/#sec-runtime-semantics-instantiatefunctionobject function InstantiateFunctionObject( func: FunctionDeclaration, - scope: EnvironmentRecord + scope: EnvironmentRecord, + isRoot?: boolean ): FunctionObject { - return OrdinaryFunctionCreate(func, scope, true); + return OrdinaryFunctionCreate(func, scope, true, isRoot); } // https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression @@ -1454,11 +1455,21 @@ export function cook( | FunctionExpression | ArrowFunctionExpression, scope: EnvironmentRecord, - isConstructor: boolean + isConstructor: boolean, + isRoot?: boolean ): FunctionObject { const F = function () { + const perf = isRoot && hooks.perfCall; + let start: number; + if (perf) { + start = performance.now(); + } // eslint-disable-next-line prefer-rest-params - return CallFunction(F, arguments); + const result = CallFunction(F, arguments); + if (perf) { + perf(performance.now() - start); + } + return result; } as FunctionObject; Object.defineProperties(F, { [SourceNode]: { @@ -1738,7 +1749,7 @@ export function cook( const [fn] = collectBoundNames(rootAst); // Create an immutable binding for the root function. rootEnv.CreateImmutableBinding(fn, true); - const fo = InstantiateFunctionObject(rootAst, rootEnv); + const fo = InstantiateFunctionObject(rootAst, rootEnv, true); rootEnv.InitializeBinding(fn, fo); return fo; }