perf: optimize analytics app imports to avoid loading entire app store#23372
Conversation
- 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: [email protected] <[email protected]>
WalkthroughAdds generation of packages/app-store/analytics.services.generated.ts exporting AnalyticsServiceMap, conditionally defined as process.env.NEXT_PUBLIC_IS_E2E ? {} : { ... } and currently containing a ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
turbo.json (1)
465-469: Consider updating outputs to include new generated files for future cachingThe @calcom/app-store-cli#build task currently lists only two outputs. Since this PR adds analytics.services.generated.ts (and calendar.services.generated.ts exists), it’s safer to enumerate them to avoid surprises if caching is enabled later or for tooling that inspects outputs.
Apply this diff to reflect the new generated artifacts:
"@calcom/app-store-cli#build": { "cache": false, "inputs": ["../../app-store/**"], - "outputs": ["../../app-store/apps.server.generated.ts", "../../app-store/apps.browser.generated.tsx"] + "outputs": [ + "../../app-store/apps.server.generated.ts", + "../../app-store/apps.browser.generated.tsx", + "../../app-store/calendar.services.generated.ts", + "../../app-store/analytics.services.generated.ts", + "../../app-store/apps.metadata.generated.ts", + "../../app-store/apps.schemas.generated.ts", + "../../app-store/apps.keys-schemas.generated.ts", + "../../app-store/bookerApps.metadata.generated.ts", + "../../app-store/crm.apps.generated.ts" + ] },packages/app-store-cli/src/build.ts (3)
381-397: Analytics map generation is correct and filter is precise
- Uses the same generator as other maps with lazy dynamic imports.
- Filters apps by existence of lib/AnalyticsService.ts, which is sufficient and avoids false positives.
Minor: the filter duplicates file existence checks already performed in getExportedObject; keeping it is fine for clarity.
If you want to DRY this, you can drop the explicit filter here since getExportedObject already skips missing files.
399-417: E2E guard wrapping logic mirrors calendar — LGTM, but consider extracting a helperThe wrapping that rewrites
export const AnalyticsServiceMap = {to a guarded variant is correct and consistent with CalendarServiceMap.Consider extracting a small helper to avoid duplication across Calendar/Analytics:
+function wrapWithE2EGuard(objectName: string, lines: string[]) { + const idx = lines.findIndex((l) => l.startsWith(`export const ${objectName}`)); + if (idx === -1) return lines; + const exportLine = lines[idx]; + const objectContent = lines.slice(idx + 1, -1); + return [ + exportLine.replace( + `export const ${objectName} = {`, + `export const ${objectName} = process.env.NEXT_PUBLIC_IS_E2E ? {} : {` + ), + ...objectContent, + "};", + ]; +}Then reuse it for both CalendarServiceMap and AnalyticsServiceMap.
385-390: Default exports in generated imports — consider standardizing on named exportsRepository guidelines prefer named exports for better tree-shaking and refactors. The generator currently assumes
defaultfor AnalyticsService. Not blocking, but worth aligning longer-term (while still supporting legacy defaults).Example future-friendly generation:
- importConfig: { - fileToBeImported: "lib/AnalyticsService.ts", - importName: "default", - }, + importConfig: [ + { fileToBeImported: "lib/AnalyticsService.ts", importName: "AnalyticsService" }, + { fileToBeImported: "lib/AnalyticsService.ts", importName: "default" } + ],This tries a named export first, falling back to default if needed.
packages/app-store/_utils/getAnalytics.ts (2)
33-38: Improve log clarity and variable naming
- analyticsAppImportFn is a Promise, not a function. Rename for clarity.
- Include the analyticsName in the “not implemented” warning to aid debugging.
Apply this diff:
- const analyticsAppImportFn = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap]; + const analyticsAppModulePromise = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap]; - if (!analyticsAppImportFn) { - log.warn(`analytics app not implemented`); + if (!analyticsAppModulePromise) { + log.warn(`Analytics app not implemented for "${analyticsName}"`); return null; }
48-52: Optional: reduce log verbosity and attach contextCurrent info log prints the class reference; prefer message with app name to avoid noisy object dumps, especially in prod logs.
- log.info("Got analyticsApp", AnalyticsService); + log.info({ analyticsName }, "Analytics service resolved");
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
packages/app-store-cli/src/build.ts(2 hunks)packages/app-store/_utils/getAnalytics.ts(2 hunks)packages/app-store/analytics.services.generated.ts(1 hunks)turbo.json(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/app-store/analytics.services.generated.tspackages/app-store/_utils/getAnalytics.tspackages/app-store-cli/src/build.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/app-store/analytics.services.generated.tspackages/app-store/_utils/getAnalytics.tspackages/app-store-cli/src/build.ts
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/app-store/analytics.services.generated.tspackages/app-store/_utils/getAnalytics.tspackages/app-store-cli/src/build.ts
🧬 Code graph analysis (1)
packages/app-store/_utils/getAnalytics.ts (1)
packages/app-store/analytics.services.generated.ts (1)
AnalyticsServiceMap(5-9)
🔇 Additional comments (3)
turbo.json (1)
88-90: Expose NEXT_PUBLIC_IS_E2E to all tasks — LGTMAdding NEXT_PUBLIC_IS_E2E to globalEnv is consistent with its use in analytics.services.generated.ts and ensures the guard is available during builds and runtime. No concerns.
packages/app-store/analytics.services.generated.ts (1)
5-9: Guarded, minimal analytics import map — LGTMThe conditional export prevents loading analytics services during E2E (NEXT_PUBLIC_IS_E2E) and only includes known AnalyticsService implementers (currently dub) otherwise. This aligns with the PR’s objective to avoid loading the full app store.
packages/app-store-cli/src/build.ts (1)
432-433: Adding analytics.services.generated.ts to the generation list — LGTMThe file will now always be emitted with the rest of the generated app-store artifacts.
|
Helps towards #23104 |
E2E results are ready! |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
…ical 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: [email protected] <[email protected]>
…s://git-manager.devin.ai/proxy/github.com/calcom/cal.com into devin/analytics-import-optimization-1756236003
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/app-store/_utils/getAnalytics.ts (1)
19-36: Make analytics imports truly lazy, harden error handling, and support both default and named exports
- Current map values are awaited directly, which assumes they’re Promises and also eagerly triggers dynamic imports at module-evaluation time in the generated file. This undermines laziness and can still load modules when this file is imported.
- If the dynamic import rejects, this function will throw instead of returning
null.- Only the
defaultexport is considered; some apps may expose a namedAnalyticsService.Refactor to:
- Treat the map entry as either a Promise or a thunk (() => Promise), calling it if it’s a function.
- Wrap the import in try/catch and return
nullon failure.- Accept either a default export or a named
AnalyticsServiceexport.- Normalize
analyticsNameto lowercase to avoid case/format drift.Apply this diff within this range:
- const analyticsName = analyticsType.split("_")[0]; + const analyticsName = analyticsType.split("_")[0]?.toLowerCase(); - const analyticsAppImportFn = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap]; + const importer = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap]; - if (!analyticsAppImportFn) { - log.warn(`analytics app not implemented`); - return null; - } + if (!importer) { + log.warn(`analytics app "${analyticsName}" not implemented (type="${analyticsType}")`); + return null; + } - const analyticsApp = await analyticsAppImportFn; + let analyticsModule: AnalyticsModule | null = null; + try { + const modulePromise = + typeof importer === "function" + ? (importer as () => Promise<AnalyticsModule>)() + : (importer as Promise<AnalyticsModule>); + analyticsModule = await modulePromise; + } catch (err) { + log.warn(`failed to load analytics module "${analyticsName}": ${(err as Error)?.message}`); + return null; + } - const AnalyticsService = analyticsApp.default; + const AnalyticsServiceCtor = (analyticsModule?.default ?? + (analyticsModule as any)?.AnalyticsService) as AnalyticsServiceCtor | undefined; - if (!AnalyticsService || typeof AnalyticsService !== "function") { - log.warn(`analytics of type ${analyticsType} is not implemented`); - return null; - } + if (typeof AnalyticsServiceCtor !== "function") { + log.warn( + `analytics of type "${analyticsType}" is not implemented (no default export or named "AnalyticsService")` + ); + return null; + } - return new AnalyticsService(credential); + return new AnalyticsServiceCtor(credential);Add these supporting types near the top of the file (outside this range):
type AnalyticsServiceCtor = new (credential: CredentialPayload) => AnalyticsService; type AnalyticsModule = { default?: AnalyticsServiceCtor; AnalyticsService?: AnalyticsServiceCtor };
🧹 Nitpick comments (1)
packages/app-store/_utils/getAnalytics.ts (1)
2-2: Avoid value/type name collision withAnalyticsServiceYou import the interface
AnalyticsServiceas a type and then create a runtime value with the same identifier later. While legal in TS, it’s easy to misread. The diff above renames the runtime value toAnalyticsServiceCtor, which aligns with its semantics (a constructor).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (2)
packages/app-store/_utils/getAnalytics.ts(2 hunks)turbo.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- turbo.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/app-store/_utils/getAnalytics.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/app-store/_utils/getAnalytics.ts
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/app-store/_utils/getAnalytics.ts
🧬 Code graph analysis (1)
packages/app-store/_utils/getAnalytics.ts (2)
packages/app-store/analytics.services.generated.ts (1)
AnalyticsServiceMap(5-9)packages/types/AnalyticsService.d.ts (1)
AnalyticsService(9-11)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Install dependencies / Yarn install & cache
- Add PaymentServiceMap generation to app-store-cli build process - Generate payment.services.generated.ts with lazy imports for 6 payment services - Update handlePayment.ts, deletePayment.ts, handlePaymentRefund.ts to use PaymentServiceMap - Update getConnectedApps.ts and tRPC payment routers to use PaymentServiceMap - Follow same pattern as analytics optimization in PR #23372 - Reduces bundle size by avoiding import of 100+ apps when only payment functionality needed Co-Authored-By: [email protected] <[email protected]>
…23408) * perf: optimize payment app imports to avoid loading entire app store - Add PaymentServiceMap generation to app-store-cli build process - Generate payment.services.generated.ts with lazy imports for 6 payment services - Update handlePayment.ts, deletePayment.ts, handlePaymentRefund.ts to use PaymentServiceMap - Update getConnectedApps.ts and tRPC payment routers to use PaymentServiceMap - Follow same pattern as analytics optimization in PR #23372 - Reduces bundle size by avoiding import of 100+ apps when only payment functionality needed Co-Authored-By: [email protected] <[email protected]> * Update build.ts * fix: update payment service test mocking to work with PaymentServiceMap - Remove obsolete appStoreMock line from bookingScenario.ts since handlePayment now uses PaymentServiceMap - Update setupVitest.ts to import prismaMock from correct PrismockClient instance - Add PaymentServiceMap mock following PR #22450 pattern for calendar services - Ensure MockPaymentService uses consistent externalId across test files - Fix webhook handler to return 200 status by ensuring payment records are found correctly Co-Authored-By: [email protected] <[email protected]> * fix: revert prismaMock import to avoid interfering with other tests' vi.spyOn() calls - Remove global prismaMock import from setupVitest.ts that was causing 'is not a spy' errors - Update MockPaymentService to import prismaMock locally to maintain payment test functionality - Fixes organization and outOfOffice tests while preserving payment service optimization Co-Authored-By: [email protected] <[email protected]> * fix: remove E2E conditional check from payment services map generation - Payment services map now always includes all payment apps regardless of E2E environment - Ensures payment functionality is consistently available across all environments - Addresses CI failures caused by conditional payment service loading Co-Authored-By: [email protected] <[email protected]> * refactor: use direct PaymentService imports instead of .lib structure - Update app-store-cli to import directly from lib/PaymentService.ts files - Modify all payment handlers to access PaymentService directly - Update test mocks to match new direct import structure - Remove .lib property access pattern across payment system - Maintain backward compatibility while improving import efficiency Co-Authored-By: [email protected] <[email protected]> * fix: revert chargeCard booking.id parameter additions - Remove booking.id parameter from chargeCard calls in chargeCard.handler.ts and payments.tsx - Addresses GitHub feedback to investigate chargeCard signature changes in separate PR - Keeps all other direct PaymentService import refactor changes intact Co-Authored-By: [email protected] <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
What does this PR do?
This PR optimizes analytics app imports to avoid loading the entire app store (100+ apps) when only analytics functionality is needed, following the same pattern established in PR #22450 for calendar services.
Problem: The import trace showed that accessing
localhost:3000/api/trpc/i18nwas importing the entire app store throughgetAnalytics.ts, causing unnecessary bundle bloat and performance issues.Solution:
analytics.services.generated.tsfile that only imports analytics apps with AnalyticsService implementationsgetAnalytics.tsto useAnalyticsServiceMapinstead of the fullappStoredubapp which has an AnalyticsService implementationVisual Demo (For contributors especially)
Before: Import trace shows entire app store being loaded:
After: Only analytics-specific apps are imported:
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Critical testing needed:
localhost:3000/api/trpc/i18nand verify the import trace no longer includes the full app storeTest configuration:
getAnalytics.ts(lines 40-41) looks suspicious and may indicate module structure mismatchdub) currently benefits from this optimization - impact may be limitedChecklist
Link to Devin run: https://app.devin.ai/sessions/37c64bafb0ff40ef8ae9f34c7e14be5c
Requested by: @keithwillcode