@lobehub/lobehub 2.0.0-next.338 → 2.0.0-next.339
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/.gitattributes +35 -0
- package/CHANGELOG.md +44 -0
- package/changelog/v1.json +15 -0
- package/locales/ar/plugin.json +12 -2
- package/locales/ar/providers.json +1 -0
- package/locales/ar/setting.json +77 -1
- package/locales/bg-BG/models.json +5 -10
- package/locales/bg-BG/plugin.json +12 -2
- package/locales/bg-BG/providers.json +1 -0
- package/locales/bg-BG/setting.json +78 -2
- package/locales/de-DE/models.json +51 -9
- package/locales/de-DE/plugin.json +12 -2
- package/locales/de-DE/providers.json +1 -0
- package/locales/de-DE/setting.json +78 -2
- package/locales/en-US/models.json +11 -10
- package/locales/en-US/plugin.json +14 -4
- package/locales/en-US/providers.json +1 -0
- package/locales/en-US/setting.json +78 -2
- package/locales/es-ES/plugin.json +12 -2
- package/locales/es-ES/providers.json +1 -0
- package/locales/es-ES/setting.json +78 -2
- package/locales/fa-IR/plugin.json +12 -2
- package/locales/fa-IR/providers.json +1 -0
- package/locales/fa-IR/setting.json +78 -2
- package/locales/fr-FR/plugin.json +12 -2
- package/locales/fr-FR/providers.json +1 -0
- package/locales/fr-FR/setting.json +78 -2
- package/locales/it-IT/plugin.json +12 -2
- package/locales/it-IT/providers.json +1 -0
- package/locales/it-IT/setting.json +78 -2
- package/locales/ja-JP/plugin.json +12 -2
- package/locales/ja-JP/providers.json +1 -0
- package/locales/ja-JP/setting.json +78 -2
- package/locales/ko-KR/plugin.json +12 -2
- package/locales/ko-KR/providers.json +1 -0
- package/locales/ko-KR/setting.json +78 -2
- package/locales/nl-NL/models.json +4 -9
- package/locales/nl-NL/plugin.json +12 -2
- package/locales/nl-NL/providers.json +1 -0
- package/locales/nl-NL/setting.json +78 -2
- package/locales/pl-PL/plugin.json +12 -2
- package/locales/pl-PL/providers.json +1 -0
- package/locales/pl-PL/setting.json +78 -2
- package/locales/pt-BR/plugin.json +12 -2
- package/locales/pt-BR/providers.json +1 -0
- package/locales/pt-BR/setting.json +78 -2
- package/locales/ru-RU/plugin.json +12 -2
- package/locales/ru-RU/providers.json +1 -0
- package/locales/ru-RU/setting.json +78 -2
- package/locales/tr-TR/plugin.json +12 -2
- package/locales/tr-TR/providers.json +1 -0
- package/locales/tr-TR/setting.json +78 -2
- package/locales/vi-VN/plugin.json +12 -2
- package/locales/vi-VN/providers.json +1 -0
- package/locales/vi-VN/setting.json +77 -1
- package/locales/zh-CN/plugin.json +12 -2
- package/locales/zh-CN/providers.json +1 -0
- package/locales/zh-CN/setting.json +78 -2
- package/locales/zh-TW/plugin.json +12 -2
- package/locales/zh-TW/providers.json +1 -0
- package/locales/zh-TW/setting.json +78 -2
- package/package.json +1 -1
- package/packages/agent-runtime/src/groupOrchestration/GroupOrchestrationSupervisor.ts +2 -0
- package/packages/agent-runtime/src/groupOrchestration/__tests__/GroupOrchestrationSupervisor.test.ts +3 -1
- package/packages/agent-runtime/src/groupOrchestration/types.ts +5 -0
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/klavis.ts +144 -0
- package/packages/const/src/lobehubSkill.ts +34 -0
- package/packages/const/src/recommendedSkill.ts +17 -0
- package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +38 -0
- package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +20 -1
- package/packages/model-runtime/src/core/contextBuilders/google.test.ts +42 -0
- package/packages/model-runtime/src/core/contextBuilders/google.ts +17 -0
- package/scripts/electronWorkflow/modifiers/dynamicToStatic.mts +273 -0
- package/scripts/electronWorkflow/modifiers/index.mts +10 -0
- package/scripts/electronWorkflow/modifiers/nextConfig.mts +1 -0
- package/scripts/electronWorkflow/modifiers/nextDynamicToStatic.mts +233 -0
- package/scripts/electronWorkflow/modifiers/removeSuspense.mts +124 -0
- package/scripts/electronWorkflow/modifiers/routes.mts +14 -2
- package/scripts/electronWorkflow/modifiers/settingsContentToStatic.mts +148 -0
- package/scripts/electronWorkflow/modifiers/wrapChildrenWithClientOnly.mts +73 -0
- package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +131 -0
- package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +34 -27
- package/src/app/[variants]/(main)/settings/features/SettingHeader.tsx +8 -4
- package/src/app/[variants]/(main)/settings/features/SettingsContent.tsx +3 -0
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +6 -0
- package/src/{features/PluginStore/InstalledList/List/Item/Action.tsx → app/[variants]/(main)/settings/skill/features/Actions.tsx} +45 -40
- package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +353 -0
- package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +344 -0
- package/src/app/[variants]/(main)/settings/skill/features/McpSkillItem.tsx +116 -0
- package/src/app/[variants]/(main)/settings/skill/features/SkillList.tsx +244 -0
- package/src/app/[variants]/(main)/settings/skill/index.tsx +35 -0
- package/src/components/Plugins/PluginTag.tsx +23 -35
- package/src/components/client/ClientOnly.tsx +6 -2
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +8 -32
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +8 -30
- package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +48 -59
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +5 -23
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +158 -56
- package/src/features/IntegrationDetailModal/index.tsx +293 -0
- package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/index.tsx +15 -6
- package/src/features/MCP/MCPSettings/McpSettingsModal.tsx +58 -0
- package/src/features/{PluginStore/McpList/Detail/Settings → MCP/MCPSettings}/index.tsx +39 -27
- package/src/features/PluginDetailModal/index.tsx +2 -2
- package/src/features/PluginDevModal/index.tsx +16 -40
- package/src/features/ProfileEditor/AgentTool.tsx +2 -2
- package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/index.tsx +1 -1
- package/src/features/{PluginStore/AddPluginButton.tsx → SkillStore/AddSkillButton.tsx} +3 -3
- package/src/features/SkillStore/CommunityList/Item.tsx +158 -0
- package/src/features/SkillStore/CommunityList/index.tsx +101 -0
- package/src/features/SkillStore/Content.tsx +59 -0
- package/src/features/{PluginStore/PluginEmpty.tsx → SkillStore/Empty.tsx} +8 -8
- package/src/features/SkillStore/LobeHubList/Item.tsx +118 -0
- package/src/features/SkillStore/LobeHubList/index.tsx +187 -0
- package/src/features/SkillStore/LobeHubList/useSkillConnect.ts +239 -0
- package/src/features/SkillStore/Search/index.tsx +43 -0
- package/src/features/{PluginStore → SkillStore}/index.tsx +14 -10
- package/src/features/SkillStore/style.ts +27 -0
- package/src/locales/default/plugin.ts +15 -4
- package/src/locales/default/setting.ts +185 -2
- package/src/services/chat/mecha/agentConfigResolver.test.ts +197 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +44 -17
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +40 -37
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +78 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +50 -16
- package/src/store/global/initialState.ts +1 -0
- package/src/store/tool/slices/lobehubSkillStore/action.test.ts +914 -0
- package/src/store/tool/slices/lobehubSkillStore/selectors.test.ts +548 -0
- package/.cursor/skills/vercel-react-best-practices/AGENTS.md +0 -2410
- package/.cursor/skills/vercel-react-best-practices/SKILL.md +0 -125
- package/.cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/.cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -49
- package/.cursor/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
- package/.cursor/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
- package/.cursor/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -36
- package/.cursor/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
- package/.cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
- package/.cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
- package/.cursor/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
- package/.cursor/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
- package/.cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
- package/.cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -57
- package/.cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
- package/.cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
- package/.cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
- package/.cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
- package/.cursor/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
- package/.cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/.cursor/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
- package/.cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
- package/.cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
- package/.cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/.cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
- package/.cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/.cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
- package/.cursor/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
- package/.cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
- package/.cursor/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
- package/src/features/PluginStore/Content.tsx +0 -54
- package/src/features/PluginStore/InstalledList/Detail/CustomPluginEmptyState.tsx +0 -79
- package/src/features/PluginStore/InstalledList/Detail/index.tsx +0 -21
- package/src/features/PluginStore/InstalledList/List/Item/index.tsx +0 -61
- package/src/features/PluginStore/InstalledList/List/index.tsx +0 -72
- package/src/features/PluginStore/InstalledList/index.tsx +0 -90
- package/src/features/PluginStore/McpList/List/Action.tsx +0 -119
- package/src/features/PluginStore/McpList/List/Item.tsx +0 -83
- package/src/features/PluginStore/McpList/List/index.tsx +0 -93
- package/src/features/PluginStore/McpList/index.tsx +0 -58
- package/src/features/PluginStore/PluginList/Detail/DetailProvider.tsx +0 -19
- package/src/features/PluginStore/PluginList/Detail/EmptyState.tsx +0 -56
- package/src/features/PluginStore/PluginList/Detail/Header.tsx +0 -130
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/Nav.tsx +0 -73
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/Settings.tsx +0 -19
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/Tools.tsx +0 -111
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/index.tsx +0 -24
- package/src/features/PluginStore/PluginList/Detail/Loading.tsx +0 -42
- package/src/features/PluginStore/PluginList/Detail/TagList.tsx +0 -35
- package/src/features/PluginStore/PluginList/Detail/index.tsx +0 -39
- package/src/features/PluginStore/PluginList/Detail/useCategory.tsx +0 -76
- package/src/features/PluginStore/PluginList/List/Action.tsx +0 -78
- package/src/features/PluginStore/PluginList/List/Item.tsx +0 -92
- package/src/features/PluginStore/PluginList/List/index.tsx +0 -94
- package/src/features/PluginStore/PluginList/index.tsx +0 -46
- package/src/features/PluginStore/Search/index.tsx +0 -40
- /package/{.codex/skills → .agents}/vercel-react-best-practices/AGENTS.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/SKILL.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-use-latest.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-api-routes.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-defer-await.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-dependencies.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-parallel.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-conditional.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-preload.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-event-listeners.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-swr-dedup.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-function-results.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-property-access.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-storage.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-combine-iterations.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-early-exit.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-index-maps.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-length-check-first.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-min-max-loop.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-activity.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-dependencies.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-derived-state.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-memo.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-transitions.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-lru.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-react.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-serialization.md +0 -0
- /package/src/{features/PluginStore/InstalledList → app/[variants]/(main)/settings/skill/features}/EditCustomPlugin.tsx +0 -0
- /package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/Loading.tsx +0 -0
- /package/src/features/{PluginStore → SkillStore}/Loading.tsx +0 -0
- /package/src/features/{PluginStore → SkillStore}/VirtuosoLoading.tsx +0 -0
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Preload Based on User Intent
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: reduces perceived latency
|
|
5
|
-
tags: bundle, preload, user-intent, hover
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Preload Based on User Intent
|
|
9
|
-
|
|
10
|
-
Preload heavy bundles before they're needed to reduce perceived latency.
|
|
11
|
-
|
|
12
|
-
**Example (preload on hover/focus):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function EditorButton({ onClick }: { onClick: () => void }) {
|
|
16
|
-
const preload = () => {
|
|
17
|
-
if (typeof window !== 'undefined') {
|
|
18
|
-
void import('./monaco-editor')
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<button
|
|
24
|
-
onMouseEnter={preload}
|
|
25
|
-
onFocus={preload}
|
|
26
|
-
onClick={onClick}
|
|
27
|
-
>
|
|
28
|
-
Open Editor
|
|
29
|
-
</button>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**Example (preload when feature flag is enabled):**
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
function FlagsProvider({ children, flags }: Props) {
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (flags.editorEnabled && typeof window !== 'undefined') {
|
|
40
|
-
void import('./monaco-editor').then(mod => mod.init())
|
|
41
|
-
}
|
|
42
|
-
}, [flags.editorEnabled])
|
|
43
|
-
|
|
44
|
-
return <FlagsContext.Provider value={flags}>
|
|
45
|
-
{children}
|
|
46
|
-
</FlagsContext.Provider>
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Deduplicate Global Event Listeners
|
|
3
|
-
impact: LOW
|
|
4
|
-
impactDescription: single listener for N components
|
|
5
|
-
tags: client, swr, event-listeners, subscription
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Deduplicate Global Event Listeners
|
|
9
|
-
|
|
10
|
-
Use `useSWRSubscription()` to share global event listeners across component instances.
|
|
11
|
-
|
|
12
|
-
**Incorrect (N instances = N listeners):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function useKeyboardShortcut(key: string, callback: () => void) {
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const handler = (e: KeyboardEvent) => {
|
|
18
|
-
if (e.metaKey && e.key === key) {
|
|
19
|
-
callback()
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
window.addEventListener('keydown', handler)
|
|
23
|
-
return () => window.removeEventListener('keydown', handler)
|
|
24
|
-
}, [key, callback])
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.
|
|
29
|
-
|
|
30
|
-
**Correct (N instances = 1 listener):**
|
|
31
|
-
|
|
32
|
-
```tsx
|
|
33
|
-
import useSWRSubscription from 'swr/subscription'
|
|
34
|
-
|
|
35
|
-
// Module-level Map to track callbacks per key
|
|
36
|
-
const keyCallbacks = new Map<string, Set<() => void>>()
|
|
37
|
-
|
|
38
|
-
function useKeyboardShortcut(key: string, callback: () => void) {
|
|
39
|
-
// Register this callback in the Map
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (!keyCallbacks.has(key)) {
|
|
42
|
-
keyCallbacks.set(key, new Set())
|
|
43
|
-
}
|
|
44
|
-
keyCallbacks.get(key)!.add(callback)
|
|
45
|
-
|
|
46
|
-
return () => {
|
|
47
|
-
const set = keyCallbacks.get(key)
|
|
48
|
-
if (set) {
|
|
49
|
-
set.delete(callback)
|
|
50
|
-
if (set.size === 0) {
|
|
51
|
-
keyCallbacks.delete(key)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}, [key, callback])
|
|
56
|
-
|
|
57
|
-
useSWRSubscription('global-keydown', () => {
|
|
58
|
-
const handler = (e: KeyboardEvent) => {
|
|
59
|
-
if (e.metaKey && keyCallbacks.has(e.key)) {
|
|
60
|
-
keyCallbacks.get(e.key)!.forEach(cb => cb())
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
window.addEventListener('keydown', handler)
|
|
64
|
-
return () => window.removeEventListener('keydown', handler)
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function Profile() {
|
|
69
|
-
// Multiple shortcuts will share the same listener
|
|
70
|
-
useKeyboardShortcut('p', () => { /* ... */ })
|
|
71
|
-
useKeyboardShortcut('k', () => { /* ... */ })
|
|
72
|
-
// ...
|
|
73
|
-
}
|
|
74
|
-
```
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Version and Minimize localStorage Data
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: prevents schema conflicts, reduces storage size
|
|
5
|
-
tags: client, localStorage, storage, versioning, data-minimization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Version and Minimize localStorage Data
|
|
9
|
-
|
|
10
|
-
Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.
|
|
11
|
-
|
|
12
|
-
**Incorrect:**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
// No version, stores everything, no error handling
|
|
16
|
-
localStorage.setItem('userConfig', JSON.stringify(fullUserObject))
|
|
17
|
-
const data = localStorage.getItem('userConfig')
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Correct:**
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
const VERSION = 'v2'
|
|
24
|
-
|
|
25
|
-
function saveConfig(config: { theme: string; language: string }) {
|
|
26
|
-
try {
|
|
27
|
-
localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))
|
|
28
|
-
} catch {
|
|
29
|
-
// Throws in incognito/private browsing, quota exceeded, or disabled
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function loadConfig() {
|
|
34
|
-
try {
|
|
35
|
-
const data = localStorage.getItem(`userConfig:${VERSION}`)
|
|
36
|
-
return data ? JSON.parse(data) : null
|
|
37
|
-
} catch {
|
|
38
|
-
return null
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Migration from v1 to v2
|
|
43
|
-
function migrate() {
|
|
44
|
-
try {
|
|
45
|
-
const v1 = localStorage.getItem('userConfig:v1')
|
|
46
|
-
if (v1) {
|
|
47
|
-
const old = JSON.parse(v1)
|
|
48
|
-
saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })
|
|
49
|
-
localStorage.removeItem('userConfig:v1')
|
|
50
|
-
}
|
|
51
|
-
} catch {}
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
**Store minimal fields from server responses:**
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
// User object has 20+ fields, only store what UI needs
|
|
59
|
-
function cachePrefs(user: FullUser) {
|
|
60
|
-
try {
|
|
61
|
-
localStorage.setItem('prefs:v1', JSON.stringify({
|
|
62
|
-
theme: user.preferences.theme,
|
|
63
|
-
notifications: user.preferences.notifications
|
|
64
|
-
}))
|
|
65
|
-
} catch {}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.
|
|
70
|
-
|
|
71
|
-
**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Use Passive Event Listeners for Scrolling Performance
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: eliminates scroll delay caused by event listeners
|
|
5
|
-
tags: client, event-listeners, scrolling, performance, touch, wheel
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Use Passive Event Listeners for Scrolling Performance
|
|
9
|
-
|
|
10
|
-
Add `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.
|
|
11
|
-
|
|
12
|
-
**Incorrect:**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
|
|
17
|
-
const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
|
|
18
|
-
|
|
19
|
-
document.addEventListener('touchstart', handleTouch)
|
|
20
|
-
document.addEventListener('wheel', handleWheel)
|
|
21
|
-
|
|
22
|
-
return () => {
|
|
23
|
-
document.removeEventListener('touchstart', handleTouch)
|
|
24
|
-
document.removeEventListener('wheel', handleWheel)
|
|
25
|
-
}
|
|
26
|
-
}, [])
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
**Correct:**
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
|
|
34
|
-
const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
|
|
35
|
-
|
|
36
|
-
document.addEventListener('touchstart', handleTouch, { passive: true })
|
|
37
|
-
document.addEventListener('wheel', handleWheel, { passive: true })
|
|
38
|
-
|
|
39
|
-
return () => {
|
|
40
|
-
document.removeEventListener('touchstart', handleTouch)
|
|
41
|
-
document.removeEventListener('wheel', handleWheel)
|
|
42
|
-
}
|
|
43
|
-
}, [])
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.
|
|
47
|
-
|
|
48
|
-
**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Use SWR for Automatic Deduplication
|
|
3
|
-
impact: MEDIUM-HIGH
|
|
4
|
-
impactDescription: automatic deduplication
|
|
5
|
-
tags: client, swr, deduplication, data-fetching
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Use SWR for Automatic Deduplication
|
|
9
|
-
|
|
10
|
-
SWR enables request deduplication, caching, and revalidation across component instances.
|
|
11
|
-
|
|
12
|
-
**Incorrect (no deduplication, each instance fetches):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function UserList() {
|
|
16
|
-
const [users, setUsers] = useState([])
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
fetch('/api/users')
|
|
19
|
-
.then(r => r.json())
|
|
20
|
-
.then(setUsers)
|
|
21
|
-
}, [])
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Correct (multiple instances share one request):**
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
import useSWR from 'swr'
|
|
29
|
-
|
|
30
|
-
function UserList() {
|
|
31
|
-
const { data: users } = useSWR('/api/users', fetcher)
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**For immutable data:**
|
|
36
|
-
|
|
37
|
-
```tsx
|
|
38
|
-
import { useImmutableSWR } from '@/lib/swr'
|
|
39
|
-
|
|
40
|
-
function StaticContent() {
|
|
41
|
-
const { data } = useImmutableSWR('/api/config', fetcher)
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**For mutations:**
|
|
46
|
-
|
|
47
|
-
```tsx
|
|
48
|
-
import { useSWRMutation } from 'swr/mutation'
|
|
49
|
-
|
|
50
|
-
function UpdateButton() {
|
|
51
|
-
const { trigger } = useSWRMutation('/api/user', updateUser)
|
|
52
|
-
return <button onClick={() => trigger()}>Update</button>
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Reference: [https://swr.vercel.app](https://swr.vercel.app)
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Batch DOM CSS Changes
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: reduces reflows/repaints
|
|
5
|
-
tags: javascript, dom, css, performance, reflow
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Batch DOM CSS Changes
|
|
9
|
-
|
|
10
|
-
Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.
|
|
11
|
-
|
|
12
|
-
**Incorrect (interleaved reads and writes force reflows):**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
function updateElementStyles(element: HTMLElement) {
|
|
16
|
-
element.style.width = '100px'
|
|
17
|
-
const width = element.offsetWidth // Forces reflow
|
|
18
|
-
element.style.height = '200px'
|
|
19
|
-
const height = element.offsetHeight // Forces another reflow
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Correct (batch writes, then read once):**
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
function updateElementStyles(element: HTMLElement) {
|
|
27
|
-
// Batch all writes together
|
|
28
|
-
element.style.width = '100px'
|
|
29
|
-
element.style.height = '200px'
|
|
30
|
-
element.style.backgroundColor = 'blue'
|
|
31
|
-
element.style.border = '1px solid black'
|
|
32
|
-
|
|
33
|
-
// Read after all writes are done (single reflow)
|
|
34
|
-
const { width, height } = element.getBoundingClientRect()
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Better: use CSS classes**
|
|
39
|
-
|
|
40
|
-
```css
|
|
41
|
-
.highlighted-box {
|
|
42
|
-
width: 100px;
|
|
43
|
-
height: 200px;
|
|
44
|
-
background-color: blue;
|
|
45
|
-
border: 1px solid black;
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
function updateElementStyles(element: HTMLElement) {
|
|
51
|
-
element.classList.add('highlighted-box')
|
|
52
|
-
|
|
53
|
-
const { width, height } = element.getBoundingClientRect()
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Prefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Cache Repeated Function Calls
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: avoid redundant computation
|
|
5
|
-
tags: javascript, cache, memoization, performance
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Cache Repeated Function Calls
|
|
9
|
-
|
|
10
|
-
Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
|
|
11
|
-
|
|
12
|
-
**Incorrect (redundant computation):**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
function ProjectList({ projects }: { projects: Project[] }) {
|
|
16
|
-
return (
|
|
17
|
-
<div>
|
|
18
|
-
{projects.map(project => {
|
|
19
|
-
// slugify() called 100+ times for same project names
|
|
20
|
-
const slug = slugify(project.name)
|
|
21
|
-
|
|
22
|
-
return <ProjectCard key={project.id} slug={slug} />
|
|
23
|
-
})}
|
|
24
|
-
</div>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
**Correct (cached results):**
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
// Module-level cache
|
|
33
|
-
const slugifyCache = new Map<string, string>()
|
|
34
|
-
|
|
35
|
-
function cachedSlugify(text: string): string {
|
|
36
|
-
if (slugifyCache.has(text)) {
|
|
37
|
-
return slugifyCache.get(text)!
|
|
38
|
-
}
|
|
39
|
-
const result = slugify(text)
|
|
40
|
-
slugifyCache.set(text, result)
|
|
41
|
-
return result
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function ProjectList({ projects }: { projects: Project[] }) {
|
|
45
|
-
return (
|
|
46
|
-
<div>
|
|
47
|
-
{projects.map(project => {
|
|
48
|
-
// Computed only once per unique project name
|
|
49
|
-
const slug = cachedSlugify(project.name)
|
|
50
|
-
|
|
51
|
-
return <ProjectCard key={project.id} slug={slug} />
|
|
52
|
-
})}
|
|
53
|
-
</div>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**Simpler pattern for single-value functions:**
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
let isLoggedInCache: boolean | null = null
|
|
62
|
-
|
|
63
|
-
function isLoggedIn(): boolean {
|
|
64
|
-
if (isLoggedInCache !== null) {
|
|
65
|
-
return isLoggedInCache
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
isLoggedInCache = document.cookie.includes('auth=')
|
|
69
|
-
return isLoggedInCache
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Clear cache when auth changes
|
|
73
|
-
function onAuthChange() {
|
|
74
|
-
isLoggedInCache = null
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
|
|
79
|
-
|
|
80
|
-
Reference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Cache Property Access in Loops
|
|
3
|
-
impact: LOW-MEDIUM
|
|
4
|
-
impactDescription: reduces lookups
|
|
5
|
-
tags: javascript, loops, optimization, caching
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Cache Property Access in Loops
|
|
9
|
-
|
|
10
|
-
Cache object property lookups in hot paths.
|
|
11
|
-
|
|
12
|
-
**Incorrect (3 lookups × N iterations):**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
for (let i = 0; i < arr.length; i++) {
|
|
16
|
-
process(obj.config.settings.value)
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Correct (1 lookup total):**
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
const value = obj.config.settings.value
|
|
24
|
-
const len = arr.length
|
|
25
|
-
for (let i = 0; i < len; i++) {
|
|
26
|
-
process(value)
|
|
27
|
-
}
|
|
28
|
-
```
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Cache Storage API Calls
|
|
3
|
-
impact: LOW-MEDIUM
|
|
4
|
-
impactDescription: reduces expensive I/O
|
|
5
|
-
tags: javascript, localStorage, storage, caching, performance
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Cache Storage API Calls
|
|
9
|
-
|
|
10
|
-
`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
|
|
11
|
-
|
|
12
|
-
**Incorrect (reads storage on every call):**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
function getTheme() {
|
|
16
|
-
return localStorage.getItem('theme') ?? 'light'
|
|
17
|
-
}
|
|
18
|
-
// Called 10 times = 10 storage reads
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
**Correct (Map cache):**
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
const storageCache = new Map<string, string | null>()
|
|
25
|
-
|
|
26
|
-
function getLocalStorage(key: string) {
|
|
27
|
-
if (!storageCache.has(key)) {
|
|
28
|
-
storageCache.set(key, localStorage.getItem(key))
|
|
29
|
-
}
|
|
30
|
-
return storageCache.get(key)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function setLocalStorage(key: string, value: string) {
|
|
34
|
-
localStorage.setItem(key, value)
|
|
35
|
-
storageCache.set(key, value) // keep cache in sync
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
|
|
40
|
-
|
|
41
|
-
**Cookie caching:**
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
let cookieCache: Record<string, string> | null = null
|
|
45
|
-
|
|
46
|
-
function getCookie(name: string) {
|
|
47
|
-
if (!cookieCache) {
|
|
48
|
-
cookieCache = Object.fromEntries(
|
|
49
|
-
document.cookie.split('; ').map(c => c.split('='))
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
return cookieCache[name]
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Important (invalidate on external changes):**
|
|
57
|
-
|
|
58
|
-
If storage can change externally (another tab, server-set cookies), invalidate cache:
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
window.addEventListener('storage', (e) => {
|
|
62
|
-
if (e.key) storageCache.delete(e.key)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
document.addEventListener('visibilitychange', () => {
|
|
66
|
-
if (document.visibilityState === 'visible') {
|
|
67
|
-
storageCache.clear()
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
```
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Combine Multiple Array Iterations
|
|
3
|
-
impact: LOW-MEDIUM
|
|
4
|
-
impactDescription: reduces iterations
|
|
5
|
-
tags: javascript, arrays, loops, performance
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Combine Multiple Array Iterations
|
|
9
|
-
|
|
10
|
-
Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
|
|
11
|
-
|
|
12
|
-
**Incorrect (3 iterations):**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
const admins = users.filter(u => u.isAdmin)
|
|
16
|
-
const testers = users.filter(u => u.isTester)
|
|
17
|
-
const inactive = users.filter(u => !u.isActive)
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Correct (1 iteration):**
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
const admins: User[] = []
|
|
24
|
-
const testers: User[] = []
|
|
25
|
-
const inactive: User[] = []
|
|
26
|
-
|
|
27
|
-
for (const user of users) {
|
|
28
|
-
if (user.isAdmin) admins.push(user)
|
|
29
|
-
if (user.isTester) testers.push(user)
|
|
30
|
-
if (!user.isActive) inactive.push(user)
|
|
31
|
-
}
|
|
32
|
-
```
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Early Return from Functions
|
|
3
|
-
impact: LOW-MEDIUM
|
|
4
|
-
impactDescription: avoids unnecessary computation
|
|
5
|
-
tags: javascript, functions, optimization, early-return
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Early Return from Functions
|
|
9
|
-
|
|
10
|
-
Return early when result is determined to skip unnecessary processing.
|
|
11
|
-
|
|
12
|
-
**Incorrect (processes all items even after finding answer):**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
function validateUsers(users: User[]) {
|
|
16
|
-
let hasError = false
|
|
17
|
-
let errorMessage = ''
|
|
18
|
-
|
|
19
|
-
for (const user of users) {
|
|
20
|
-
if (!user.email) {
|
|
21
|
-
hasError = true
|
|
22
|
-
errorMessage = 'Email required'
|
|
23
|
-
}
|
|
24
|
-
if (!user.name) {
|
|
25
|
-
hasError = true
|
|
26
|
-
errorMessage = 'Name required'
|
|
27
|
-
}
|
|
28
|
-
// Continues checking all users even after error found
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return hasError ? { valid: false, error: errorMessage } : { valid: true }
|
|
32
|
-
}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Correct (returns immediately on first error):**
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
function validateUsers(users: User[]) {
|
|
39
|
-
for (const user of users) {
|
|
40
|
-
if (!user.email) {
|
|
41
|
-
return { valid: false, error: 'Email required' }
|
|
42
|
-
}
|
|
43
|
-
if (!user.name) {
|
|
44
|
-
return { valid: false, error: 'Name required' }
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return { valid: true }
|
|
49
|
-
}
|
|
50
|
-
```
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Hoist RegExp Creation
|
|
3
|
-
impact: LOW-MEDIUM
|
|
4
|
-
impactDescription: avoids recreation
|
|
5
|
-
tags: javascript, regexp, optimization, memoization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Hoist RegExp Creation
|
|
9
|
-
|
|
10
|
-
Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.
|
|
11
|
-
|
|
12
|
-
**Incorrect (new RegExp every render):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function Highlighter({ text, query }: Props) {
|
|
16
|
-
const regex = new RegExp(`(${query})`, 'gi')
|
|
17
|
-
const parts = text.split(regex)
|
|
18
|
-
return <>{parts.map((part, i) => ...)}</>
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**Correct (memoize or hoist):**
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
26
|
-
|
|
27
|
-
function Highlighter({ text, query }: Props) {
|
|
28
|
-
const regex = useMemo(
|
|
29
|
-
() => new RegExp(`(${escapeRegex(query)})`, 'gi'),
|
|
30
|
-
[query]
|
|
31
|
-
)
|
|
32
|
-
const parts = text.split(regex)
|
|
33
|
-
return <>{parts.map((part, i) => ...)}</>
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
**Warning (global regex has mutable state):**
|
|
38
|
-
|
|
39
|
-
Global regex (`/g`) has mutable `lastIndex` state:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
const regex = /foo/g
|
|
43
|
-
regex.test('foo') // true, lastIndex = 3
|
|
44
|
-
regex.test('foo') // false, lastIndex = 0
|
|
45
|
-
```
|