From 520e711ca63dbc646d96fb038bb3e07ea24cd1e1 Mon Sep 17 00:00:00 2001 From: Keith Williams Date: Tue, 26 Aug 2025 18:57:38 -0300 Subject: [PATCH] perf: optimize analytics app imports to avoid loading entire app store (#23372) * perf: optimize analytics app imports to avoid loading entire app store - Add analytics service generation to app-store-cli build process - Generate analytics.services.generated.ts with only analytics apps (dub) - Update getAnalytics.ts to use AnalyticsServiceMap instead of full appStore - Add NEXT_PUBLIC_IS_E2E to turbo.json globalEnv for generated files - Reduces import footprint from 100+ apps to only analytics apps with AnalyticsService - Follows same pattern as calendar services optimization from PR #22450 Co-Authored-By: keith@cal.com * Reorder cli output * fix: follow getCalendar pattern in getAnalytics and maintain alphabetical order in turbo.json - Remove unnecessary object wrapping in getAnalytics.ts to match getCalendar.ts pattern - Move NEXT_PUBLIC_IS_E2E to correct alphabetical position in turbo.json globalEnv - Address PR feedback from keithwillcode Co-Authored-By: keith@cal.com * chore: update yarn.lock after analytics optimization changes Co-Authored-By: keith@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- packages/app-store-cli/src/build.ts | 38 +++++++++++++++++++ packages/app-store/_utils/getAnalytics.ts | 31 ++++----------- .../app-store/analytics.services.generated.ts | 9 +++++ turbo.json | 2 +- yarn.lock | 26 +++++++++++++ 5 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 packages/app-store/analytics.services.generated.ts diff --git a/packages/app-store-cli/src/build.ts b/packages/app-store-cli/src/build.ts index c64cc8008a..b43a3b819e 100644 --- a/packages/app-store-cli/src/build.ts +++ b/packages/app-store-cli/src/build.ts @@ -378,12 +378,50 @@ function generateFiles() { calendarOutput.push(...calendarServices); } + const analyticsOutput = []; + const analyticsServices = getExportedObject( + "AnalyticsServiceMap", + { + importConfig: { + fileToBeImported: "lib/AnalyticsService.ts", + importName: "default", + }, + lazyImport: true, + }, + (app: App) => { + const hasAnalyticsService = fs.existsSync( + path.join(APP_STORE_PATH, app.path, "lib/AnalyticsService.ts") + ); + return hasAnalyticsService; + } + ); + + const analyticsExportLineIndex = analyticsServices.findIndex((line) => + line.startsWith("export const AnalyticsServiceMap") + ); + if (analyticsExportLineIndex !== -1) { + const exportLine = analyticsServices[analyticsExportLineIndex]; + const objectContent = analyticsServices.slice(analyticsExportLineIndex + 1, -1); + + analyticsOutput.push( + exportLine.replace( + "export const AnalyticsServiceMap = {", + "export const AnalyticsServiceMap = process.env.NEXT_PUBLIC_IS_E2E ? {} : {" + ), + ...objectContent, + "};" + ); + } else { + analyticsOutput.push(...analyticsServices); + } + const banner = `/** This file is autogenerated using the command \`yarn app-store:build --watch\`. Don't modify this file manually. **/ `; const filesToGenerate: [string, string[]][] = [ + ["analytics.services.generated.ts", analyticsOutput], ["apps.metadata.generated.ts", metadataOutput], ["apps.server.generated.ts", serverOutput], ["apps.browser.generated.tsx", browserOutput], diff --git a/packages/app-store/_utils/getAnalytics.ts b/packages/app-store/_utils/getAnalytics.ts index e0a68180f6..3a729406fb 100644 --- a/packages/app-store/_utils/getAnalytics.ts +++ b/packages/app-store/_utils/getAnalytics.ts @@ -1,25 +1,11 @@ import logger from "@calcom/lib/logger"; -import type { AnalyticsService, AnalyticsServiceClass } from "@calcom/types/AnalyticsService"; +import type { AnalyticsService } from "@calcom/types/AnalyticsService"; import type { CredentialPayload } from "@calcom/types/Credential"; -import appStore from ".."; +import { AnalyticsServiceMap } from "../analytics.services.generated"; const log = logger.getSubLogger({ prefix: ["AnalyticsManager"] }); -interface AnalyticsApp { - lib: { - AnalyticsService: AnalyticsServiceClass; - }; -} - -const isAnalyticsService = (x: unknown): x is AnalyticsApp => - !!x && - typeof x === "object" && - "lib" in x && - typeof x.lib === "object" && - !!x.lib && - "AnalyticsService" in x.lib; - export const getAnalyticsService = async ({ credential, }: { @@ -30,22 +16,21 @@ export const getAnalyticsService = async ({ const analyticsName = analyticsType.split("_")[0]; - const analyticsAppImportFn = appStore[analyticsName as keyof typeof appStore]; + const analyticsAppImportFn = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap]; if (!analyticsAppImportFn) { log.warn(`analytics app not implemented`); return null; } - const analyticsApp = await analyticsAppImportFn(); + const analyticsApp = await analyticsAppImportFn; - if (!isAnalyticsService(analyticsApp)) { - log.warn(`Analytics is not implemented`); + const AnalyticsService = analyticsApp.default; + + if (!AnalyticsService || typeof AnalyticsService !== "function") { + log.warn(`analytics of type ${analyticsType} is not implemented`); return null; } - const AnalyticsService = analyticsApp.lib.AnalyticsService; - log.info("Got analyticsApp", AnalyticsService); - return new AnalyticsService(credential); }; diff --git a/packages/app-store/analytics.services.generated.ts b/packages/app-store/analytics.services.generated.ts new file mode 100644 index 0000000000..17e8216bcc --- /dev/null +++ b/packages/app-store/analytics.services.generated.ts @@ -0,0 +1,9 @@ +/** + This file is autogenerated using the command `yarn app-store:build --watch`. + Don't modify this file manually. +**/ +export const AnalyticsServiceMap = process.env.NEXT_PUBLIC_IS_E2E + ? {} + : { + dub: import("./dub/lib/AnalyticsService"), + }; diff --git a/turbo.json b/turbo.json index df3e9fd3c5..cdc4e1a6c9 100644 --- a/turbo.json +++ b/turbo.json @@ -85,7 +85,6 @@ "HUBSPOT_CLIENT_SECRET", "IFFY_API_KEY", "INTEGRATION_TEST_MODE", - "IS_E2E", "INTERCOM_SECRET", "INTERCOM_SECRET", "INSIGHTS_DATABASE_URL", @@ -108,6 +107,7 @@ "NEXT_PUBLIC_FORMBRICKS_HOST_URL", "NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID", "NEXT_PUBLIC_HOSTED_CAL_FEATURES", + "NEXT_PUBLIC_IS_E2E", "NEXT_PUBLIC_MINUTES_TO_BOOK", "NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED", "NEXT_PUBLIC_SENDER_ID", diff --git a/yarn.lock b/yarn.lock index fdbc4dfd91..ef36aed2ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3066,6 +3066,32 @@ __metadata: languageName: unknown linkType: soft +"@calcom/exchange2013calendar@workspace:packages/app-store/exchange2013calendar": + version: 0.0.0-use.local + resolution: "@calcom/exchange2013calendar@workspace:packages/app-store/exchange2013calendar" + dependencies: + "@calcom/lib": "*" + "@calcom/prisma": "*" + "@calcom/types": "*" + "@calcom/ui": "*" + ews-javascript-api: ^0.11.0 + react-hook-form: ^7.43.3 + languageName: unknown + linkType: soft + +"@calcom/exchange2016calendar@workspace:packages/app-store/exchange2016calendar": + version: 0.0.0-use.local + resolution: "@calcom/exchange2016calendar@workspace:packages/app-store/exchange2016calendar" + dependencies: + "@calcom/lib": "*" + "@calcom/prisma": "*" + "@calcom/types": "*" + "@calcom/ui": "*" + ews-javascript-api: ^0.11.0 + react-hook-form: ^7.43.3 + languageName: unknown + linkType: soft + "@calcom/exchangecalendar@workspace:packages/app-store/exchangecalendar": version: 0.0.0-use.local resolution: "@calcom/exchangecalendar@workspace:packages/app-store/exchangecalendar"