@jmruthers/pace-core 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -1,1328 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
lastUpdated: 2025-11-18T17:00:00+11:00
|
|
3
|
-
version: 0.5.181
|
|
4
|
-
reviewedBy: documentation-standards-audit
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Performance Best Practices
|
|
8
|
-
|
|
9
|
-
> **📚 Complete Performance Guide** | [← Back to Documentation](./README.md) | [Installation Guide](../getting-started/installation-guide.md)
|
|
10
|
-
|
|
11
|
-
Performance is crucial for user experience. This comprehensive guide provides all performance optimization techniques for `@jmruthers/pace-core` applications, from basic strategies to advanced optimization.
|
|
12
|
-
|
|
13
|
-
## Overview
|
|
14
|
-
|
|
15
|
-
Performance optimization in `@jmruthers/pace-core` covers:
|
|
16
|
-
|
|
17
|
-
- **Component Rendering**: Minimizing unnecessary re-renders
|
|
18
|
-
- **Data Fetching**: Efficient data loading and caching
|
|
19
|
-
- **Bundle Size**: Optimizing package size and tree shaking
|
|
20
|
-
- **Memory Management**: Preventing memory leaks
|
|
21
|
-
- **Network Optimization**: Reducing API calls and payload size
|
|
22
|
-
- **Advanced Techniques**: Code splitting, lazy loading, and optimization tools
|
|
23
|
-
|
|
24
|
-
## Component Performance
|
|
25
|
-
|
|
26
|
-
### 1. Memoization Strategies
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
import { useMemo, useCallback } from 'react';
|
|
30
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
31
|
-
|
|
32
|
-
function OptimizedEventList() {
|
|
33
|
-
const { events, loading } = useEvents();
|
|
34
|
-
|
|
35
|
-
// Memoize expensive calculations
|
|
36
|
-
const sortedEvents = useMemo(() => {
|
|
37
|
-
return events?.sort((a, b) =>
|
|
38
|
-
new Date(a.start_date).getTime() - new Date(b.start_date).getTime()
|
|
39
|
-
) || [];
|
|
40
|
-
}, [events]);
|
|
41
|
-
|
|
42
|
-
// Memoize filtered data
|
|
43
|
-
const upcomingEvents = useMemo(() => {
|
|
44
|
-
const now = new Date();
|
|
45
|
-
return sortedEvents.filter(event =>
|
|
46
|
-
new Date(event.start_date) > now
|
|
47
|
-
);
|
|
48
|
-
}, [sortedEvents]);
|
|
49
|
-
|
|
50
|
-
// Memoize event groups
|
|
51
|
-
const eventsByDate = useMemo(() => {
|
|
52
|
-
return upcomingEvents.reduce((groups, event) => {
|
|
53
|
-
const date = event.start_date.split('T')[0];
|
|
54
|
-
if (!groups[date]) groups[date] = [];
|
|
55
|
-
groups[date].push(event);
|
|
56
|
-
return groups;
|
|
57
|
-
}, {});
|
|
58
|
-
}, [upcomingEvents]);
|
|
59
|
-
|
|
60
|
-
if (loading) return <div>Loading...</div>;
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div>
|
|
64
|
-
{Object.entries(eventsByDate).map(([date, events]) => (
|
|
65
|
-
<div key={date}>
|
|
66
|
-
<h3>{date}</h3>
|
|
67
|
-
{events.map(event => (
|
|
68
|
-
<EventCard key={event.id} event={event} />
|
|
69
|
-
))}
|
|
70
|
-
</div>
|
|
71
|
-
))}
|
|
72
|
-
</div>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### 2. Callback Optimization
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
import { useCallback } from 'react';
|
|
81
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
82
|
-
|
|
83
|
-
function EventManager() {
|
|
84
|
-
const { events, setSelectedEvent, refreshEvents } = useEvents();
|
|
85
|
-
|
|
86
|
-
// Memoize event handlers
|
|
87
|
-
const handleEventSelect = useCallback((event) => {
|
|
88
|
-
setSelectedEvent(event);
|
|
89
|
-
}, [setSelectedEvent]);
|
|
90
|
-
|
|
91
|
-
const handleRefresh = useCallback(() => {
|
|
92
|
-
refreshEvents();
|
|
93
|
-
}, [refreshEvents]);
|
|
94
|
-
|
|
95
|
-
const handleEventDelete = useCallback(async (eventId) => {
|
|
96
|
-
try {
|
|
97
|
-
await deleteEvent(eventId);
|
|
98
|
-
refreshEvents();
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error('Failed to delete event:', error);
|
|
101
|
-
}
|
|
102
|
-
}, [refreshEvents]);
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<div>
|
|
106
|
-
<button onClick={handleRefresh}>Refresh</button>
|
|
107
|
-
{events.map(event => (
|
|
108
|
-
<EventCard
|
|
109
|
-
key={event.id}
|
|
110
|
-
event={event}
|
|
111
|
-
onSelect={handleEventSelect}
|
|
112
|
-
onDelete={handleEventDelete}
|
|
113
|
-
/>
|
|
114
|
-
))}
|
|
115
|
-
</div>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### 3. Component Splitting
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
// Split large components into smaller, focused components
|
|
124
|
-
function EventDashboard() {
|
|
125
|
-
const { events, loading } = useEvents();
|
|
126
|
-
|
|
127
|
-
if (loading) return <LoadingSpinner />;
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<div className="dashboard">
|
|
131
|
-
<EventSummary events={events} />
|
|
132
|
-
<EventCalendar events={events} />
|
|
133
|
-
<EventList events={events} />
|
|
134
|
-
</div>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Separate components for better performance
|
|
139
|
-
const EventSummary = React.memo(({ events }) => {
|
|
140
|
-
const stats = useMemo(() => ({
|
|
141
|
-
total: events.length,
|
|
142
|
-
upcoming: events.filter(e => new Date(e.start_date) > new Date()).length,
|
|
143
|
-
past: events.filter(e => new Date(e.start_date) < new Date()).length,
|
|
144
|
-
}), [events]);
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<div className="summary">
|
|
148
|
-
<div>Total: {stats.total}</div>
|
|
149
|
-
<div>Upcoming: {stats.upcoming}</div>
|
|
150
|
-
<div>Past: {stats.past}</div>
|
|
151
|
-
</div>
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const EventCalendar = React.memo(({ events }) => {
|
|
156
|
-
// Calendar implementation
|
|
157
|
-
return <div className="calendar">...</div>;
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const EventList = React.memo(({ events }) => {
|
|
161
|
-
// List implementation
|
|
162
|
-
return <div className="list">...</div>;
|
|
163
|
-
});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Data Fetching Optimization
|
|
167
|
-
|
|
168
|
-
### 1. Efficient Data Loading
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
172
|
-
|
|
173
|
-
function OptimizedEventLoader() {
|
|
174
|
-
const { events, loading, error, refreshEvents } = useEvents();
|
|
175
|
-
|
|
176
|
-
// Implement pagination for large datasets
|
|
177
|
-
const [page, setPage] = useState(1);
|
|
178
|
-
const [pageSize, setPageSize] = useState(20);
|
|
179
|
-
|
|
180
|
-
const paginatedEvents = useMemo(() => {
|
|
181
|
-
const start = (page - 1) * pageSize;
|
|
182
|
-
const end = start + pageSize;
|
|
183
|
-
return events.slice(start, end);
|
|
184
|
-
}, [events, page, pageSize]);
|
|
185
|
-
|
|
186
|
-
// Implement virtual scrolling for very large lists
|
|
187
|
-
const VirtualizedEventList = useMemo(() => {
|
|
188
|
-
return React.memo(({ events }) => {
|
|
189
|
-
return (
|
|
190
|
-
<div className="virtual-list">
|
|
191
|
-
{events.map((event, index) => (
|
|
192
|
-
<EventRow key={event.id} event={event} index={index} />
|
|
193
|
-
))}
|
|
194
|
-
</div>
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
}, []);
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<div>
|
|
201
|
-
<VirtualizedEventList events={paginatedEvents} />
|
|
202
|
-
<Pagination
|
|
203
|
-
currentPage={page}
|
|
204
|
-
totalPages={Math.ceil(events.length / pageSize)}
|
|
205
|
-
onPageChange={setPage}
|
|
206
|
-
/>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### 2. Caching Strategies
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
import { useMemoizedCallback } from '@jmruthers/pace-core';
|
|
216
|
-
|
|
217
|
-
function CachedDataComponent() {
|
|
218
|
-
const { events, refreshEvents } = useEvents();
|
|
219
|
-
|
|
220
|
-
// Cache expensive operations
|
|
221
|
-
const eventStats = useMemo(() => {
|
|
222
|
-
return events.reduce((stats, event) => {
|
|
223
|
-
const month = new Date(event.start_date).getMonth();
|
|
224
|
-
stats[month] = (stats[month] || 0) + 1;
|
|
225
|
-
return stats;
|
|
226
|
-
}, {});
|
|
227
|
-
}, [events]);
|
|
228
|
-
|
|
229
|
-
// Cache filtered results
|
|
230
|
-
const [filter, setFilter] = useState('');
|
|
231
|
-
const filteredEvents = useMemo(() => {
|
|
232
|
-
if (!filter) return events;
|
|
233
|
-
return events.filter(event =>
|
|
234
|
-
event.name.toLowerCase().includes(filter.toLowerCase())
|
|
235
|
-
);
|
|
236
|
-
}, [events, filter]);
|
|
237
|
-
|
|
238
|
-
// Debounced search
|
|
239
|
-
const debouncedSetFilter = useMemoizedCallback(
|
|
240
|
-
(value) => setFilter(value),
|
|
241
|
-
[],
|
|
242
|
-
300
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
return (
|
|
246
|
-
<div>
|
|
247
|
-
<input
|
|
248
|
-
type="text"
|
|
249
|
-
placeholder="Search events..."
|
|
250
|
-
onChange={(e) => debouncedSetFilter(e.target.value)}
|
|
251
|
-
/>
|
|
252
|
-
<EventList events={filteredEvents} />
|
|
253
|
-
<EventStats stats={eventStats} />
|
|
254
|
-
</div>
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### 3. Background Data Updates
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
import { useEffect, useRef } from 'react';
|
|
263
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
264
|
-
|
|
265
|
-
function AutoRefreshingEventList() {
|
|
266
|
-
const { events, refreshEvents } = useEvents();
|
|
267
|
-
const intervalRef = useRef(null);
|
|
268
|
-
|
|
269
|
-
// Auto-refresh data in background
|
|
270
|
-
useEffect(() => {
|
|
271
|
-
intervalRef.current = setInterval(() => {
|
|
272
|
-
refreshEvents();
|
|
273
|
-
}, 30000); // Refresh every 30 seconds
|
|
274
|
-
|
|
275
|
-
return () => {
|
|
276
|
-
if (intervalRef.current) {
|
|
277
|
-
clearInterval(intervalRef.current);
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
}, [refreshEvents]);
|
|
281
|
-
|
|
282
|
-
// Pause refresh when tab is not visible
|
|
283
|
-
useEffect(() => {
|
|
284
|
-
const handleVisibilityChange = () => {
|
|
285
|
-
if (document.hidden) {
|
|
286
|
-
if (intervalRef.current) {
|
|
287
|
-
clearInterval(intervalRef.current);
|
|
288
|
-
}
|
|
289
|
-
} else {
|
|
290
|
-
intervalRef.current = setInterval(() => {
|
|
291
|
-
refreshEvents();
|
|
292
|
-
}, 30000);
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
297
|
-
return () => {
|
|
298
|
-
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
299
|
-
};
|
|
300
|
-
}, [refreshEvents]);
|
|
301
|
-
|
|
302
|
-
return <EventList events={events} />;
|
|
303
|
-
}
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
## Bundle Size Optimization
|
|
307
|
-
|
|
308
|
-
### 1. Tree Shaking
|
|
309
|
-
|
|
310
|
-
```typescript
|
|
311
|
-
// Import only what you need
|
|
312
|
-
import { Button, DataTable } from '@jmruthers/pace-core';
|
|
313
|
-
// Instead of: import * from '@jmruthers/pace-core';
|
|
314
|
-
|
|
315
|
-
// Use dynamic imports for large components
|
|
316
|
-
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
|
|
317
|
-
|
|
318
|
-
function App() {
|
|
319
|
-
return (
|
|
320
|
-
<Suspense fallback={<div>Loading...</div>}>
|
|
321
|
-
<HeavyComponent />
|
|
322
|
-
</Suspense>
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### 2. Code Splitting
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
// Split routes by feature
|
|
331
|
-
const AdminDashboard = React.lazy(() => import('./AdminDashboard'));
|
|
332
|
-
const UserDashboard = React.lazy(() => import('./UserDashboard'));
|
|
333
|
-
|
|
334
|
-
function App() {
|
|
335
|
-
const { user } = useUnifiedAuth();
|
|
336
|
-
|
|
337
|
-
return (
|
|
338
|
-
<Suspense fallback={<div>Loading dashboard...</div>}>
|
|
339
|
-
{user?.role === 'admin' ? <AdminDashboard /> : <UserDashboard />}
|
|
340
|
-
</Suspense>
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### 3. Component Lazy Loading
|
|
346
|
-
|
|
347
|
-
```typescript
|
|
348
|
-
import { useCan } from '@jmruthers/pace-core/rbac';
|
|
349
|
-
import { useUnifiedAuth } from '@jmruthers/pace-core/providers';
|
|
350
|
-
|
|
351
|
-
// Lazy load components based on user permissions
|
|
352
|
-
function ConditionalComponent({ permission, children }) {
|
|
353
|
-
const { user, selectedOrganisationId } = useUnifiedAuth();
|
|
354
|
-
const { can, isLoading } = useCan(
|
|
355
|
-
user?.id || '',
|
|
356
|
-
{ organisationId: selectedOrganisationId || '', eventId: undefined, appId: undefined },
|
|
357
|
-
permission
|
|
358
|
-
);
|
|
359
|
-
const [shouldLoad, setShouldLoad] = useState(false);
|
|
360
|
-
|
|
361
|
-
useEffect(() => {
|
|
362
|
-
if (!isLoading && can) {
|
|
363
|
-
setShouldLoad(true);
|
|
364
|
-
}
|
|
365
|
-
}, [can, isLoading]);
|
|
366
|
-
|
|
367
|
-
if (!shouldLoad) return null;
|
|
368
|
-
|
|
369
|
-
return <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Usage
|
|
373
|
-
<ConditionalComponent permission="admin:analytics">
|
|
374
|
-
<AnalyticsDashboard />
|
|
375
|
-
</ConditionalComponent>
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
## Memory Management
|
|
379
|
-
|
|
380
|
-
### 1. Cleanup on Unmount
|
|
381
|
-
|
|
382
|
-
```typescript
|
|
383
|
-
import { useEffect, useRef } from 'react';
|
|
384
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
385
|
-
|
|
386
|
-
function EventMonitor() {
|
|
387
|
-
const { events } = useEvents();
|
|
388
|
-
const mountedRef = useRef(true);
|
|
389
|
-
|
|
390
|
-
useEffect(() => {
|
|
391
|
-
return () => {
|
|
392
|
-
mountedRef.current = false;
|
|
393
|
-
};
|
|
394
|
-
}, []);
|
|
395
|
-
|
|
396
|
-
useEffect(() => {
|
|
397
|
-
if (!mountedRef.current) return;
|
|
398
|
-
|
|
399
|
-
const timer = setInterval(() => {
|
|
400
|
-
if (mountedRef.current) {
|
|
401
|
-
// Update logic here
|
|
402
|
-
console.log('Events updated:', events.length);
|
|
403
|
-
}
|
|
404
|
-
}, 5000);
|
|
405
|
-
|
|
406
|
-
return () => {
|
|
407
|
-
clearInterval(timer);
|
|
408
|
-
};
|
|
409
|
-
}, [events]);
|
|
410
|
-
|
|
411
|
-
return <div>Monitoring {events.length} events</div>;
|
|
412
|
-
}
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
### 2. Event Listener Cleanup
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
import { useEffect } from 'react';
|
|
419
|
-
import { useUnifiedAuth } from '@jmruthers/pace-core';
|
|
420
|
-
|
|
421
|
-
function AuthListener() {
|
|
422
|
-
const { user, signOut } = useUnifiedAuth();
|
|
423
|
-
|
|
424
|
-
useEffect(() => {
|
|
425
|
-
const handleStorageChange = (e) => {
|
|
426
|
-
if (e.key === 'supabase.auth.token' && !e.newValue) {
|
|
427
|
-
// Token was removed, sign out
|
|
428
|
-
signOut();
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
window.addEventListener('storage', handleStorageChange);
|
|
433
|
-
return () => {
|
|
434
|
-
window.removeEventListener('storage', handleStorageChange);
|
|
435
|
-
};
|
|
436
|
-
}, [signOut]);
|
|
437
|
-
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### 3. Large List Optimization
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
446
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
447
|
-
|
|
448
|
-
function VirtualizedEventList() {
|
|
449
|
-
const { events } = useEvents();
|
|
450
|
-
const parentRef = useRef();
|
|
451
|
-
|
|
452
|
-
const rowVirtualizer = useVirtualizer({
|
|
453
|
-
count: events.length,
|
|
454
|
-
getScrollElement: () => parentRef.current,
|
|
455
|
-
estimateSize: () => 100,
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
return (
|
|
459
|
-
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
|
|
460
|
-
<div
|
|
461
|
-
style={{
|
|
462
|
-
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
463
|
-
width: '100%',
|
|
464
|
-
position: 'relative',
|
|
465
|
-
}}
|
|
466
|
-
>
|
|
467
|
-
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
|
468
|
-
const event = events[virtualRow.index];
|
|
469
|
-
return (
|
|
470
|
-
<div
|
|
471
|
-
key={event.id}
|
|
472
|
-
style={{
|
|
473
|
-
position: 'absolute',
|
|
474
|
-
top: 0,
|
|
475
|
-
left: 0,
|
|
476
|
-
width: '100%',
|
|
477
|
-
height: `${virtualRow.size}px`,
|
|
478
|
-
transform: `translateY(${virtualRow.start}px)`,
|
|
479
|
-
}}
|
|
480
|
-
>
|
|
481
|
-
<EventCard event={event} />
|
|
482
|
-
</div>
|
|
483
|
-
);
|
|
484
|
-
})}
|
|
485
|
-
</div>
|
|
486
|
-
</div>
|
|
487
|
-
);
|
|
488
|
-
}
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
## Network Optimization
|
|
492
|
-
|
|
493
|
-
### RBAC Performance Optimizations
|
|
494
|
-
|
|
495
|
-
The RBAC system includes built-in performance optimizations that significantly reduce network requests:
|
|
496
|
-
|
|
497
|
-
- **Request Deduplication**: Multiple components checking the same permission share network requests
|
|
498
|
-
- **Enhanced Caching**: Two-tier caching (60s short-term + 5min session cache)
|
|
499
|
-
- **Batched Audit Logging**: Audit events are batched to reduce network requests
|
|
500
|
-
- **Memoization**: Components are memoized to prevent unnecessary re-renders
|
|
501
|
-
|
|
502
|
-
See the [RBAC Performance Guide](../rbac/performance.md) for detailed information.
|
|
503
|
-
|
|
504
|
-
### 1. Request Deduplication
|
|
505
|
-
|
|
506
|
-
```typescript
|
|
507
|
-
import { useMemoizedCallback } from '@jmruthers/pace-core';
|
|
508
|
-
|
|
509
|
-
function OptimizedDataFetcher() {
|
|
510
|
-
const { supabase } = useSupabase();
|
|
511
|
-
const requestCache = useRef(new Map());
|
|
512
|
-
|
|
513
|
-
const fetchEventData = useMemoizedCallback(async (eventId) => {
|
|
514
|
-
// Check cache first
|
|
515
|
-
if (requestCache.current.has(eventId)) {
|
|
516
|
-
return requestCache.current.get(eventId);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Make request
|
|
520
|
-
const { data, error } = await supabase
|
|
521
|
-
.from('events')
|
|
522
|
-
.select('*')
|
|
523
|
-
.eq('id', eventId)
|
|
524
|
-
.single();
|
|
525
|
-
|
|
526
|
-
if (error) throw error;
|
|
527
|
-
|
|
528
|
-
// Cache result
|
|
529
|
-
requestCache.current.set(eventId, data);
|
|
530
|
-
return data;
|
|
531
|
-
}, [supabase]);
|
|
532
|
-
|
|
533
|
-
return (
|
|
534
|
-
<div>
|
|
535
|
-
{eventIds.map(id => (
|
|
536
|
-
<EventDetails key={id} eventId={id} onFetch={fetchEventData} />
|
|
537
|
-
))}
|
|
538
|
-
</div>
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
### 2. Batch Requests
|
|
544
|
-
|
|
545
|
-
```typescript
|
|
546
|
-
import { useSupabase } from '@jmruthers/pace-core';
|
|
547
|
-
|
|
548
|
-
function BatchDataFetcher() {
|
|
549
|
-
const { supabase } = useSupabase();
|
|
550
|
-
const [events, setEvents] = useState([]);
|
|
551
|
-
const [loading, setLoading] = useState(false);
|
|
552
|
-
|
|
553
|
-
const fetchEventsBatch = async (eventIds) => {
|
|
554
|
-
setLoading(true);
|
|
555
|
-
try {
|
|
556
|
-
const { data, error } = await supabase
|
|
557
|
-
.from('events')
|
|
558
|
-
.select('*')
|
|
559
|
-
.in('id', eventIds);
|
|
560
|
-
|
|
561
|
-
if (error) throw error;
|
|
562
|
-
setEvents(data);
|
|
563
|
-
} catch (error) {
|
|
564
|
-
console.error('Failed to fetch events:', error);
|
|
565
|
-
} finally {
|
|
566
|
-
setLoading(false);
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
return (
|
|
571
|
-
<div>
|
|
572
|
-
{loading && <div>Loading...</div>}
|
|
573
|
-
<EventList events={events} />
|
|
574
|
-
</div>
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
### 3. Progressive Loading
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
import { useState, useEffect } from 'react';
|
|
583
|
-
import { useEvents } from '@jmruthers/pace-core';
|
|
584
|
-
|
|
585
|
-
function ProgressiveEventList() {
|
|
586
|
-
const { events } = useEvents();
|
|
587
|
-
const [visibleEvents, setVisibleEvents] = useState([]);
|
|
588
|
-
const [page, setPage] = useState(1);
|
|
589
|
-
const pageSize = 10;
|
|
590
|
-
|
|
591
|
-
useEffect(() => {
|
|
592
|
-
const start = 0;
|
|
593
|
-
const end = page * pageSize;
|
|
594
|
-
setVisibleEvents(events.slice(start, end));
|
|
595
|
-
}, [events, page]);
|
|
596
|
-
|
|
597
|
-
const loadMore = () => {
|
|
598
|
-
setPage(prev => prev + 1);
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
const hasMore = visibleEvents.length < events.length;
|
|
602
|
-
|
|
603
|
-
return (
|
|
604
|
-
<div>
|
|
605
|
-
{visibleEvents.map(event => (
|
|
606
|
-
<EventCard key={event.id} event={event} />
|
|
607
|
-
))}
|
|
608
|
-
{hasMore && (
|
|
609
|
-
<button onClick={loadMore}>
|
|
610
|
-
Load More ({events.length - visibleEvents.length} remaining)
|
|
611
|
-
</button>
|
|
612
|
-
)}
|
|
613
|
-
</div>
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
## Performance Monitoring
|
|
619
|
-
|
|
620
|
-
### 1. Component Performance Tracking
|
|
621
|
-
|
|
622
|
-
```typescript
|
|
623
|
-
import { useComponentPerformance } from '@jmruthers/pace-core';
|
|
624
|
-
|
|
625
|
-
function PerformanceMonitor() {
|
|
626
|
-
const { renderCount, averageRenderTime, lastRenderTime } = useComponentPerformance();
|
|
627
|
-
|
|
628
|
-
useEffect(() => {
|
|
629
|
-
if (averageRenderTime > 16) { // 60fps threshold
|
|
630
|
-
console.warn('Component rendering slowly:', averageRenderTime);
|
|
631
|
-
}
|
|
632
|
-
}, [averageRenderTime]);
|
|
633
|
-
|
|
634
|
-
return (
|
|
635
|
-
<div className="performance-monitor">
|
|
636
|
-
<div>Renders: {renderCount}</div>
|
|
637
|
-
<div>Avg Time: {averageRenderTime.toFixed(2)}ms</div>
|
|
638
|
-
<div>Last Render: {lastRenderTime.toFixed(2)}ms</div>
|
|
639
|
-
</div>
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
### 2. Network Performance
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
import { useUnifiedAuth } from '@jmruthers/pace-core';
|
|
648
|
-
|
|
649
|
-
function NetworkMonitor() {
|
|
650
|
-
const { error } = useUnifiedAuth();
|
|
651
|
-
const [networkStats, setNetworkStats] = useState({
|
|
652
|
-
requests: 0,
|
|
653
|
-
errors: 0,
|
|
654
|
-
avgResponseTime: 0,
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
useEffect(() => {
|
|
658
|
-
const startTime = performance.now();
|
|
659
|
-
|
|
660
|
-
return () => {
|
|
661
|
-
const endTime = performance.now();
|
|
662
|
-
const responseTime = endTime - startTime;
|
|
663
|
-
|
|
664
|
-
setNetworkStats(prev => ({
|
|
665
|
-
...prev,
|
|
666
|
-
requests: prev.requests + 1,
|
|
667
|
-
avgResponseTime: (prev.avgResponseTime + responseTime) / 2,
|
|
668
|
-
}));
|
|
669
|
-
};
|
|
670
|
-
}, []);
|
|
671
|
-
|
|
672
|
-
return (
|
|
673
|
-
<div className="network-monitor">
|
|
674
|
-
<div>Requests: {networkStats.requests}</div>
|
|
675
|
-
<div>Errors: {networkStats.errors}</div>
|
|
676
|
-
<div>Avg Response: {networkStats.avgResponseTime.toFixed(2)}ms</div>
|
|
677
|
-
</div>
|
|
678
|
-
);
|
|
679
|
-
}
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
### 3. Memory Usage Monitoring
|
|
683
|
-
|
|
684
|
-
```typescript
|
|
685
|
-
function MemoryMonitor() {
|
|
686
|
-
const [memoryInfo, setMemoryInfo] = useState(null);
|
|
687
|
-
|
|
688
|
-
useEffect(() => {
|
|
689
|
-
const updateMemoryInfo = () => {
|
|
690
|
-
if ('memory' in performance) {
|
|
691
|
-
setMemoryInfo({
|
|
692
|
-
used: performance.memory.usedJSHeapSize,
|
|
693
|
-
total: performance.memory.totalJSHeapSize,
|
|
694
|
-
limit: performance.memory.jsHeapSizeLimit,
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
const interval = setInterval(updateMemoryInfo, 5000);
|
|
700
|
-
updateMemoryInfo();
|
|
701
|
-
|
|
702
|
-
return () => clearInterval(interval);
|
|
703
|
-
}, []);
|
|
704
|
-
|
|
705
|
-
if (!memoryInfo) return null;
|
|
706
|
-
|
|
707
|
-
const usagePercent = (memoryInfo.used / memoryInfo.limit) * 100;
|
|
708
|
-
|
|
709
|
-
return (
|
|
710
|
-
<div className="memory-monitor">
|
|
711
|
-
<div>Memory Usage: {usagePercent.toFixed(1)}%</div>
|
|
712
|
-
<div>Used: {(memoryInfo.used / 1024 / 1024).toFixed(1)}MB</div>
|
|
713
|
-
<div>Total: {(memoryInfo.total / 1024 / 1024).toFixed(1)}MB</div>
|
|
714
|
-
</div>
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
## Performance Best Practices Checklist
|
|
720
|
-
|
|
721
|
-
### Development Checklist
|
|
722
|
-
|
|
723
|
-
- [ ] Use React.memo for expensive components
|
|
724
|
-
- [ ] Memoize expensive calculations with useMemo
|
|
725
|
-
- [ ] Memoize event handlers with useCallback
|
|
726
|
-
- [ ] Implement proper cleanup in useEffect
|
|
727
|
-
- [ ] Use lazy loading for large components
|
|
728
|
-
- [ ] Implement virtual scrolling for large lists
|
|
729
|
-
- [ ] Cache expensive API calls
|
|
730
|
-
- [ ] Debounce user input
|
|
731
|
-
- [ ] Optimize bundle size with tree shaking
|
|
732
|
-
- [ ] Monitor component render performance
|
|
733
|
-
|
|
734
|
-
### Production Checklist
|
|
735
|
-
|
|
736
|
-
- [ ] Enable production builds
|
|
737
|
-
- [ ] Implement proper error boundaries
|
|
738
|
-
- [ ] Set up performance monitoring
|
|
739
|
-
- [ ] Optimize images and assets
|
|
740
|
-
- [ ] Configure proper caching headers
|
|
741
|
-
- [ ] Implement service workers for offline support
|
|
742
|
-
- [ ] Monitor Core Web Vitals
|
|
743
|
-
- [ ] Set up automated performance testing
|
|
744
|
-
- [ ] Configure CDN for static assets
|
|
745
|
-
- [ ] Implement progressive loading
|
|
746
|
-
|
|
747
|
-
### Monitoring Checklist
|
|
748
|
-
|
|
749
|
-
- [ ] Track component render times
|
|
750
|
-
- [ ] Monitor API response times
|
|
751
|
-
- [ ] Track memory usage
|
|
752
|
-
- [ ] Monitor bundle size
|
|
753
|
-
- [ ] Set up performance alerts
|
|
754
|
-
- [ ] Track user experience metrics
|
|
755
|
-
- [ ] Monitor error rates
|
|
756
|
-
- [ ] Track loading times
|
|
757
|
-
- [ ] Monitor Core Web Vitals
|
|
758
|
-
- [ ] Set up automated performance reports
|
|
759
|
-
|
|
760
|
-
## Performance Tools
|
|
761
|
-
|
|
762
|
-
### 1. React DevTools Profiler
|
|
763
|
-
|
|
764
|
-
```typescript
|
|
765
|
-
import { Profiler } from 'react';
|
|
766
|
-
|
|
767
|
-
function ProfiledComponent() {
|
|
768
|
-
const onRenderCallback = (id, phase, actualDuration) => {
|
|
769
|
-
console.log(`Component ${id} took ${actualDuration}ms to ${phase}`);
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
return (
|
|
773
|
-
<Profiler id="EventList" onRender={onRenderCallback}>
|
|
774
|
-
<EventList />
|
|
775
|
-
</Profiler>
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
### 2. Custom Performance Hooks
|
|
781
|
-
|
|
782
|
-
```typescript
|
|
783
|
-
function usePerformanceMonitor(componentName) {
|
|
784
|
-
const renderCount = useRef(0);
|
|
785
|
-
const startTime = useRef(performance.now());
|
|
786
|
-
|
|
787
|
-
useEffect(() => {
|
|
788
|
-
renderCount.current += 1;
|
|
789
|
-
const renderTime = performance.now() - startTime.current;
|
|
790
|
-
|
|
791
|
-
console.log(`${componentName} rendered in ${renderTime.toFixed(2)}ms`);
|
|
792
|
-
startTime.current = performance.now();
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
```
|
|
796
|
-
|
|
797
|
-
### 3. Performance Budgets
|
|
798
|
-
|
|
799
|
-
```typescript
|
|
800
|
-
// Set performance budgets
|
|
801
|
-
const PERFORMANCE_BUDGETS = {
|
|
802
|
-
firstContentfulPaint: 1500, // 1.5s
|
|
803
|
-
largestContentfulPaint: 2500, // 2.5s
|
|
804
|
-
firstInputDelay: 100, // 100ms
|
|
805
|
-
cumulativeLayoutShift: 0.1, // 0.1
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
function checkPerformanceBudget(metrics) {
|
|
809
|
-
Object.entries(PERFORMANCE_BUDGETS).forEach(([metric, budget]) => {
|
|
810
|
-
if (metrics[metric] > budget) {
|
|
811
|
-
console.warn(`${metric} exceeded budget: ${metrics[metric]} > ${budget}`);
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
## Advanced Performance Techniques
|
|
818
|
-
|
|
819
|
-
### Bundle Optimization
|
|
820
|
-
|
|
821
|
-
#### Tree Shaking
|
|
822
|
-
|
|
823
|
-
**Goal**: Minimize bundle size by eliminating unused code.
|
|
824
|
-
|
|
825
|
-
**Implementation**:
|
|
826
|
-
```typescript
|
|
827
|
-
// ✅ Good - Named exports for tree shaking
|
|
828
|
-
export { Button } from './Button';
|
|
829
|
-
export { Card } from './Card';
|
|
830
|
-
export { DataTable } from './DataTable';
|
|
831
|
-
|
|
832
|
-
// ❌ Bad - Default exports reduce tree shaking
|
|
833
|
-
export default { Button, Card, DataTable };
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
**Verify Tree Shaking**:
|
|
837
|
-
```bash
|
|
838
|
-
# Check bundle size
|
|
839
|
-
npm run build
|
|
840
|
-
npx vite-bundle-visualizer
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
#### Dynamic Imports
|
|
844
|
-
|
|
845
|
-
**Use Case**: Load components only when needed.
|
|
846
|
-
|
|
847
|
-
```tsx
|
|
848
|
-
import React, { Suspense, lazy } from 'react';
|
|
849
|
-
|
|
850
|
-
// Lazy load heavy components
|
|
851
|
-
const DataTable = lazy(() => import('@jmruthers/pace-core').then(m => ({ default: m.DataTable })));
|
|
852
|
-
const ChartEditor = lazy(() => import('./components/ChartEditor'));
|
|
853
|
-
|
|
854
|
-
function App() {
|
|
855
|
-
return (
|
|
856
|
-
<Suspense fallback={<LoadingSpinner />}>
|
|
857
|
-
<DataTable data={data} columns={columns} />
|
|
858
|
-
</Suspense>
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
#### Analysis Tools
|
|
864
|
-
|
|
865
|
-
```bash
|
|
866
|
-
# Install analyzer
|
|
867
|
-
npm install -D vite-bundle-visualizer
|
|
868
|
-
|
|
869
|
-
# Analyze bundle
|
|
870
|
-
npx vite-bundle-visualizer dist/stats.html
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
### Advanced Component Memoization
|
|
874
|
-
|
|
875
|
-
#### React.memo for Expensive Components
|
|
876
|
-
|
|
877
|
-
```tsx
|
|
878
|
-
import React from 'react';
|
|
879
|
-
import { Card } from '@jmruthers/pace-core';
|
|
880
|
-
|
|
881
|
-
interface UserCardProps {
|
|
882
|
-
user: User;
|
|
883
|
-
onEdit: (userId: string) => void;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// Memoize expensive components
|
|
887
|
-
const UserCard = React.memo(({ user, onEdit }: UserCardProps) => {
|
|
888
|
-
return (
|
|
889
|
-
<Card className="p-4">
|
|
890
|
-
<h3>{user.name}</h3>
|
|
891
|
-
<p>{user.email}</p>
|
|
892
|
-
<button onClick={() => onEdit(user.id)}>Edit</button>
|
|
893
|
-
</Card>
|
|
894
|
-
);
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
// Custom comparison function for complex props
|
|
898
|
-
const ComplexComponent = React.memo(
|
|
899
|
-
({ data, config }: { data: any[]; config: any }) => {
|
|
900
|
-
return <DataTable data={data} columns={config.columns} />;
|
|
901
|
-
},
|
|
902
|
-
(prevProps, nextProps) => {
|
|
903
|
-
// Only re-render if data length or config changed
|
|
904
|
-
return (
|
|
905
|
-
prevProps.data.length === nextProps.data.length &&
|
|
906
|
-
JSON.stringify(prevProps.config) === JSON.stringify(nextProps.config)
|
|
907
|
-
);
|
|
908
|
-
}
|
|
909
|
-
);
|
|
910
|
-
```
|
|
911
|
-
|
|
912
|
-
#### useMemo for Expensive Calculations
|
|
913
|
-
|
|
914
|
-
```tsx
|
|
915
|
-
import { useMemo } from 'react';
|
|
916
|
-
import { DataTable } from '@jmruthers/pace-core';
|
|
917
|
-
|
|
918
|
-
function ExpensiveDataProcessor({ rawData }: { rawData: any[] }) {
|
|
919
|
-
// Memoize expensive data processing
|
|
920
|
-
const processedData = useMemo(() => {
|
|
921
|
-
return rawData.map(item => ({
|
|
922
|
-
...item,
|
|
923
|
-
// Expensive calculations
|
|
924
|
-
score: calculateComplexScore(item),
|
|
925
|
-
category: categorizeItem(item),
|
|
926
|
-
processed: true
|
|
927
|
-
}));
|
|
928
|
-
}, [rawData]);
|
|
929
|
-
|
|
930
|
-
// Memoize column configuration
|
|
931
|
-
const columns = useMemo(() => [
|
|
932
|
-
{ accessorKey: 'name', header: 'Name' },
|
|
933
|
-
{ accessorKey: 'score', header: 'Score' },
|
|
934
|
-
{ accessorKey: 'category', header: 'Category' }
|
|
935
|
-
], []);
|
|
936
|
-
|
|
937
|
-
return <DataTable data={processedData} columns={columns} />;
|
|
938
|
-
}
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
### Memory Management
|
|
942
|
-
|
|
943
|
-
#### Preventing Memory Leaks
|
|
944
|
-
|
|
945
|
-
```tsx
|
|
946
|
-
import { useEffect, useRef } from 'react';
|
|
947
|
-
|
|
948
|
-
function DataFetcher() {
|
|
949
|
-
const isMountedRef = useRef(true);
|
|
950
|
-
|
|
951
|
-
useEffect(() => {
|
|
952
|
-
const fetchData = async () => {
|
|
953
|
-
try {
|
|
954
|
-
const data = await api.getData();
|
|
955
|
-
if (isMountedRef.current) {
|
|
956
|
-
setData(data);
|
|
957
|
-
}
|
|
958
|
-
} catch (error) {
|
|
959
|
-
if (isMountedRef.current) {
|
|
960
|
-
setError(error);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
};
|
|
964
|
-
|
|
965
|
-
fetchData();
|
|
966
|
-
|
|
967
|
-
return () => {
|
|
968
|
-
isMountedRef.current = false;
|
|
969
|
-
};
|
|
970
|
-
}, []);
|
|
971
|
-
|
|
972
|
-
// Cleanup subscriptions
|
|
973
|
-
useEffect(() => {
|
|
974
|
-
const subscription = eventBus.subscribe('dataUpdate', handleUpdate);
|
|
975
|
-
|
|
976
|
-
return () => {
|
|
977
|
-
subscription.unsubscribe();
|
|
978
|
-
};
|
|
979
|
-
}, []);
|
|
980
|
-
}
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
#### Efficient State Updates
|
|
984
|
-
|
|
985
|
-
```tsx
|
|
986
|
-
import { useCallback, useReducer } from 'react';
|
|
987
|
-
|
|
988
|
-
// Use reducer for complex state
|
|
989
|
-
function dataReducer(state: any, action: any) {
|
|
990
|
-
switch (action.type) {
|
|
991
|
-
case 'SET_DATA':
|
|
992
|
-
return { ...state, data: action.payload };
|
|
993
|
-
case 'UPDATE_ITEM':
|
|
994
|
-
return {
|
|
995
|
-
...state,
|
|
996
|
-
data: state.data.map(item =>
|
|
997
|
-
item.id === action.id ? { ...item, ...action.updates } : item
|
|
998
|
-
)
|
|
999
|
-
};
|
|
1000
|
-
case 'DELETE_ITEM':
|
|
1001
|
-
return {
|
|
1002
|
-
...state,
|
|
1003
|
-
data: state.data.filter(item => item.id !== action.id)
|
|
1004
|
-
};
|
|
1005
|
-
default:
|
|
1006
|
-
return state;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
function OptimizedDataManager() {
|
|
1011
|
-
const [state, dispatch] = useReducer(dataReducer, { data: [], loading: false });
|
|
1012
|
-
|
|
1013
|
-
// Memoize dispatch functions
|
|
1014
|
-
const updateItem = useCallback((id: string, updates: any) => {
|
|
1015
|
-
dispatch({ type: 'UPDATE_ITEM', id, updates });
|
|
1016
|
-
}, []);
|
|
1017
|
-
|
|
1018
|
-
const deleteItem = useCallback((id: string) => {
|
|
1019
|
-
dispatch({ type: 'DELETE_ITEM', id });
|
|
1020
|
-
}, []);
|
|
1021
|
-
|
|
1022
|
-
return (
|
|
1023
|
-
<DataTable
|
|
1024
|
-
data={state.data}
|
|
1025
|
-
onUpdate={updateItem}
|
|
1026
|
-
onDelete={deleteItem}
|
|
1027
|
-
/>
|
|
1028
|
-
);
|
|
1029
|
-
}
|
|
1030
|
-
```
|
|
1031
|
-
|
|
1032
|
-
### Network Optimization
|
|
1033
|
-
|
|
1034
|
-
#### Request Deduplication
|
|
1035
|
-
|
|
1036
|
-
```tsx
|
|
1037
|
-
import { useMemo } from 'react';
|
|
1038
|
-
|
|
1039
|
-
// Deduplicate identical requests
|
|
1040
|
-
const requestCache = new Map();
|
|
1041
|
-
|
|
1042
|
-
function useOptimizedFetch(url: string, options: any) {
|
|
1043
|
-
const cacheKey = useMemo(() =>
|
|
1044
|
-
`${url}-${JSON.stringify(options)}`,
|
|
1045
|
-
[url, options]
|
|
1046
|
-
);
|
|
1047
|
-
|
|
1048
|
-
const fetchData = useCallback(async () => {
|
|
1049
|
-
if (requestCache.has(cacheKey)) {
|
|
1050
|
-
return requestCache.get(cacheKey);
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
const promise = fetch(url, options).then(res => res.json());
|
|
1054
|
-
requestCache.set(cacheKey, promise);
|
|
1055
|
-
|
|
1056
|
-
// Clear cache after 5 minutes
|
|
1057
|
-
setTimeout(() => {
|
|
1058
|
-
requestCache.delete(cacheKey);
|
|
1059
|
-
}, 5 * 60 * 1000);
|
|
1060
|
-
|
|
1061
|
-
return promise;
|
|
1062
|
-
}, [cacheKey]);
|
|
1063
|
-
|
|
1064
|
-
return fetchData;
|
|
1065
|
-
}
|
|
1066
|
-
```
|
|
1067
|
-
|
|
1068
|
-
#### Batch API Calls
|
|
1069
|
-
|
|
1070
|
-
```tsx
|
|
1071
|
-
import { useCallback } from 'react';
|
|
1072
|
-
|
|
1073
|
-
function useBatchRequests() {
|
|
1074
|
-
const batchRequests = useCallback(async (requests: any[]) => {
|
|
1075
|
-
// Group requests by endpoint
|
|
1076
|
-
const groupedRequests = requests.reduce((groups, request) => {
|
|
1077
|
-
const key = request.endpoint;
|
|
1078
|
-
if (!groups[key]) groups[key] = [];
|
|
1079
|
-
groups[key].push(request);
|
|
1080
|
-
return groups;
|
|
1081
|
-
}, {});
|
|
1082
|
-
|
|
1083
|
-
// Execute batches in parallel
|
|
1084
|
-
const results = await Promise.all(
|
|
1085
|
-
Object.entries(groupedRequests).map(([endpoint, batch]) =>
|
|
1086
|
-
api.batchRequest(endpoint, batch)
|
|
1087
|
-
)
|
|
1088
|
-
);
|
|
1089
|
-
|
|
1090
|
-
return results.flat();
|
|
1091
|
-
}, []);
|
|
1092
|
-
|
|
1093
|
-
return batchRequests;
|
|
1094
|
-
}
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
|
-
### Code Splitting Strategies
|
|
1098
|
-
|
|
1099
|
-
#### Route-Based Splitting
|
|
1100
|
-
|
|
1101
|
-
```tsx
|
|
1102
|
-
import { lazy, Suspense } from 'react';
|
|
1103
|
-
import { Routes, Route } from 'react-router-dom';
|
|
1104
|
-
|
|
1105
|
-
// Lazy load route components
|
|
1106
|
-
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
|
1107
|
-
const Users = lazy(() => import('./pages/Users'));
|
|
1108
|
-
const Settings = lazy(() => import('./pages/Settings'));
|
|
1109
|
-
|
|
1110
|
-
function App() {
|
|
1111
|
-
return (
|
|
1112
|
-
<Suspense fallback={<LoadingSpinner />}>
|
|
1113
|
-
<Routes>
|
|
1114
|
-
<Route path="/" element={<Dashboard />} />
|
|
1115
|
-
<Route path="/users" element={<Users />} />
|
|
1116
|
-
<Route path="/settings" element={<Settings />} />
|
|
1117
|
-
</Routes>
|
|
1118
|
-
</Suspense>
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
```
|
|
1122
|
-
|
|
1123
|
-
#### Feature-Based Splitting
|
|
1124
|
-
|
|
1125
|
-
```tsx
|
|
1126
|
-
import { lazy, Suspense } from 'react';
|
|
1127
|
-
import { useCan } from '@jmruthers/pace-core';
|
|
1128
|
-
|
|
1129
|
-
// Lazy load admin features
|
|
1130
|
-
const AdminPanel = lazy(() => import('./AdminPanel'));
|
|
1131
|
-
const UserManagement = lazy(() => import('./UserManagement'));
|
|
1132
|
-
|
|
1133
|
-
function App() {
|
|
1134
|
-
const { hasPermission } = useCan();
|
|
1135
|
-
|
|
1136
|
-
return (
|
|
1137
|
-
<div>
|
|
1138
|
-
<MainContent />
|
|
1139
|
-
|
|
1140
|
-
{hasPermission('admin:access') && (
|
|
1141
|
-
<Suspense fallback={<div>Loading admin features...</div>}>
|
|
1142
|
-
<AdminPanel />
|
|
1143
|
-
</Suspense>
|
|
1144
|
-
)}
|
|
1145
|
-
|
|
1146
|
-
{hasPermission('users:manage') && (
|
|
1147
|
-
<Suspense fallback={<div>Loading user management...</div>}>
|
|
1148
|
-
<UserManagement />
|
|
1149
|
-
</Suspense>
|
|
1150
|
-
)}
|
|
1151
|
-
</div>
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
```
|
|
1155
|
-
|
|
1156
|
-
### Performance Monitoring
|
|
1157
|
-
|
|
1158
|
-
#### Real-Time Performance Tracking
|
|
1159
|
-
|
|
1160
|
-
```tsx
|
|
1161
|
-
import { useEffect } from 'react';
|
|
1162
|
-
|
|
1163
|
-
function usePerformanceMonitor() {
|
|
1164
|
-
useEffect(() => {
|
|
1165
|
-
// Monitor Core Web Vitals
|
|
1166
|
-
const observer = new PerformanceObserver((list) => {
|
|
1167
|
-
for (const entry of list.getEntries()) {
|
|
1168
|
-
console.log('Performance metric:', {
|
|
1169
|
-
name: entry.name,
|
|
1170
|
-
value: entry.value,
|
|
1171
|
-
startTime: entry.startTime
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
observer.observe({ entryTypes: ['measure', 'navigation'] });
|
|
1177
|
-
|
|
1178
|
-
return () => observer.disconnect();
|
|
1179
|
-
}, []);
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// Usage in components
|
|
1183
|
-
function MyComponent() {
|
|
1184
|
-
usePerformanceMonitor();
|
|
1185
|
-
|
|
1186
|
-
return <div>Component content</div>;
|
|
1187
|
-
}
|
|
1188
|
-
```
|
|
1189
|
-
|
|
1190
|
-
#### Bundle Size Monitoring
|
|
1191
|
-
|
|
1192
|
-
```bash
|
|
1193
|
-
# Add to package.json scripts
|
|
1194
|
-
{
|
|
1195
|
-
"scripts": {
|
|
1196
|
-
"analyze": "npm run build && npx vite-bundle-visualizer",
|
|
1197
|
-
"size-check": "npm run build && npx bundlesize"
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
# Monitor bundle size
|
|
1202
|
-
npm run analyze
|
|
1203
|
-
```
|
|
1204
|
-
|
|
1205
|
-
### Performance Testing
|
|
1206
|
-
|
|
1207
|
-
#### Automated Performance Tests
|
|
1208
|
-
|
|
1209
|
-
```typescript
|
|
1210
|
-
// performance.test.ts
|
|
1211
|
-
import { render, screen } from '@testing-library/react';
|
|
1212
|
-
import { performance } from 'perf_hooks';
|
|
1213
|
-
|
|
1214
|
-
describe('Performance Tests', () => {
|
|
1215
|
-
test('DataTable renders within performance budget', async () => {
|
|
1216
|
-
const start = performance.now();
|
|
1217
|
-
|
|
1218
|
-
render(<DataTable data={largeDataset} columns={columns} />);
|
|
1219
|
-
|
|
1220
|
-
await screen.findByRole('table');
|
|
1221
|
-
|
|
1222
|
-
const end = performance.now();
|
|
1223
|
-
const renderTime = end - start;
|
|
1224
|
-
|
|
1225
|
-
// Should render within 100ms
|
|
1226
|
-
expect(renderTime).toBeLessThan(100);
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
|
-
test('Component re-renders are optimized', () => {
|
|
1230
|
-
let renderCount = 0;
|
|
1231
|
-
|
|
1232
|
-
const TestComponent = () => {
|
|
1233
|
-
renderCount++;
|
|
1234
|
-
return <div>Test</div>;
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
const { rerender } = render(<TestComponent />);
|
|
1238
|
-
|
|
1239
|
-
// Re-render with same props
|
|
1240
|
-
rerender(<TestComponent />);
|
|
1241
|
-
|
|
1242
|
-
// Should not cause unnecessary re-renders
|
|
1243
|
-
expect(renderCount).toBe(1);
|
|
1244
|
-
});
|
|
1245
|
-
});
|
|
1246
|
-
```
|
|
1247
|
-
|
|
1248
|
-
## Performance Checklist
|
|
1249
|
-
|
|
1250
|
-
### Development Phase
|
|
1251
|
-
- [ ] Use React.memo for expensive components
|
|
1252
|
-
- [ ] Implement useCallback for event handlers
|
|
1253
|
-
- [ ] Use useMemo for expensive calculations
|
|
1254
|
-
- [ ] Implement proper cleanup in useEffect
|
|
1255
|
-
- [ ] Use dynamic imports for large components
|
|
1256
|
-
- [ ] Optimize bundle with tree shaking
|
|
1257
|
-
|
|
1258
|
-
### Production Phase
|
|
1259
|
-
- [ ] Enable production optimizations
|
|
1260
|
-
- [ ] Monitor Core Web Vitals
|
|
1261
|
-
- [ ] Implement performance budgets
|
|
1262
|
-
- [ ] Set up performance monitoring
|
|
1263
|
-
- [ ] Test with real data volumes
|
|
1264
|
-
- [ ] Optimize images and assets
|
|
1265
|
-
|
|
1266
|
-
### Monitoring Phase
|
|
1267
|
-
- [ ] Track bundle size over time
|
|
1268
|
-
- [ ] Monitor render performance
|
|
1269
|
-
- [ ] Watch for memory leaks
|
|
1270
|
-
- [ ] Analyze user experience metrics
|
|
1271
|
-
- [ ] Set up performance alerts
|
|
1272
|
-
- [ ] Regular performance audits
|
|
1273
|
-
|
|
1274
|
-
## Related Documentation
|
|
1275
|
-
|
|
1276
|
-
- [Data Tables Performance](../implementation-guides/data-tables.md#performance-optimization) - DataTable-specific optimizations
|
|
1277
|
-
- [Authentication Performance](../implementation-guides/authentication.md#performance) - Auth-related performance
|
|
1278
|
-
- [Best Practices Overview](./README.md) - General best practices
|
|
1279
|
-
- [Troubleshooting Performance Issues](../troubleshooting/README.md#performance-issues) - Performance debugging
|
|
1280
|
-
|
|
1281
|
-
## ♿ Accessibility
|
|
1282
|
-
|
|
1283
|
-
Performance optimizations should maintain accessibility:
|
|
1284
|
-
|
|
1285
|
-
- **Lazy loading doesn't break screen readers** - Ensure lazy-loaded content is accessible
|
|
1286
|
-
- **Code splitting maintains keyboard navigation** - Verify keyboard navigation works after code splitting
|
|
1287
|
-
- **Memoization doesn't affect focus management** - Ensure focus handling remains correct
|
|
1288
|
-
- **Performance optimizations are tested with assistive technologies** - Verify optimizations don't break accessibility
|
|
1289
|
-
- **Loading states are announced** - Screen readers should announce performance-related loading states
|
|
1290
|
-
|
|
1291
|
-
### Accessibility Best Practices
|
|
1292
|
-
|
|
1293
|
-
1. **Test optimizations with screen readers** - Verify performance improvements don't break accessibility
|
|
1294
|
-
2. **Ensure keyboard navigation** - All optimized components should remain keyboard accessible
|
|
1295
|
-
3. **Maintain focus visibility** - Focus indicators should remain visible after optimizations
|
|
1296
|
-
4. **Test with assistive technologies** - Verify performance optimizations work with screen readers
|
|
1297
|
-
5. **Balance performance and accessibility** - Don't sacrifice accessibility for performance gains
|
|
1298
|
-
|
|
1299
|
-
## ⚠️ Edge Cases
|
|
1300
|
-
|
|
1301
|
-
### Performance vs Accessibility Trade-offs
|
|
1302
|
-
|
|
1303
|
-
When performance optimizations impact accessibility:
|
|
1304
|
-
- Find balance between performance and accessibility
|
|
1305
|
-
- Test optimizations with assistive technologies
|
|
1306
|
-
- Ensure lazy loading doesn't break screen reader navigation
|
|
1307
|
-
- Verify code splitting maintains keyboard navigation
|
|
1308
|
-
- Document trade-offs when necessary
|
|
1309
|
-
|
|
1310
|
-
### Over-Optimization Issues
|
|
1311
|
-
|
|
1312
|
-
When over-optimization causes problems:
|
|
1313
|
-
- Monitor for premature optimization
|
|
1314
|
-
- Test optimizations in real-world scenarios
|
|
1315
|
-
- Profile before and after optimizations
|
|
1316
|
-
- Verify optimizations provide measurable benefits
|
|
1317
|
-
- Consider maintainability when optimizing
|
|
1318
|
-
|
|
1319
|
-
### Performance Regression
|
|
1320
|
-
|
|
1321
|
-
When performance degrades after optimizations:
|
|
1322
|
-
- Profile to identify bottlenecks
|
|
1323
|
-
- Test with realistic data volumes
|
|
1324
|
-
- Monitor performance metrics
|
|
1325
|
-
- Rollback optimizations if needed
|
|
1326
|
-
- Document performance regression causes
|
|
1327
|
-
|
|
1328
|
-
For more information about optimizing your application, see the [Security Guide](./security.md) and [Testing Guide](./testing.md).
|