feat(web): migrate from es-toolkit/compat to native es-toolkit (#30244)#10
Conversation
…enius#30244) Migrate 172 files from es-toolkit/compat to native es-toolkit. Migrated: - noop → es-toolkit/function - cloneDeep, omit, clone, pick → es-toolkit/object - uniq, intersection → es-toolkit/array - capitalize, camelCase, kebabCase, escape → es-toolkit/string - isEqual → es-toolkit/predicate Kept in compat (API differences): - uniqueId, isEmpty - get, merge, flow, curry, debounce - different APIs - uniqBy, groupBy, intersectionBy - property string support - flatten - undefined handling Benefits: 2-3x faster, smaller bundle Part of langgenius#30243 Fixes langgenius#30244
There was a problem hiding this comment.
Pull request overview
This PR migrates from es-toolkit/compat to native es-toolkit modules to improve performance and reduce bundle size. The migration moves utility functions like noop, capitalize, escape, cloneDeep, etc. to their specific module paths (/function, /string, /object, /array, /predicate) while keeping functions with special behaviors (like debounce, merge, xorBy) in the /compat module. The PR also removes the lodash dev dependency.
Changes:
- Migrated 100+ imports from
es-toolkit/compatto specific module paths - Removed lodash from devDependencies in package.json
- Added unrelated code changes to Toast component (unused class, dependency array modification)
- Introduced logic bugs in log list filtering and workflow node sorting
Reviewed changes
Copilot reviewed 159 out of 160 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| web/utils/index.ts | Migrated escape from /compat to /string |
| web/service/use-plugins.ts | Migrated cloneDeep from /compat to /object |
| web/i18n-config/i18next-config.ts | Migrated camelCase, kebabCase from /compat to /string |
| web/context/*.tsx | Migrated noop from /compat to /function across multiple context files |
| web/app/components/workflow/*.tsx | Migrated cloneDeep, capitalize, escape, intersection, isEqual to specific modules |
| web/app/components/base/toast/index.tsx | Added unused class, modified dependency array, migrated noop |
| web/app/components/app/log/list.tsx | Bug: inverted filter logic, split imports |
| web/app/components/workflow/nodes/_base/components/variable/utils.ts | Bug: inverted sort logic, migrated imports |
| web/package.json | Removed lodash devDependency |
| web/pnpm-lock.yaml | Removed lodash lock entries |
Files not reviewed (1)
- web/pnpm-lock.yaml: Language not supported
| if (a.data.type === BlockEnum.Start) | ||
| return 1 | ||
| if (b.data.type === BlockEnum.Start) | ||
| return -1 | ||
| if (b.data.type === BlockEnum.Start) | ||
| return 1 |
There was a problem hiding this comment.
The sort logic for the Start node has been inverted. Previously, when a.data.type === BlockEnum.Start, it returned 1 (pushing it later in the sort order). Now it returns -1 (pushing it earlier). This changes the sorting behavior and may break the intended node ordering. The comment on line 825 says "reverse chronological order (most recent first)" but this change inverts that for Start nodes.
| }: { | ||
| children: ReactNode | ||
| }) => { | ||
| const stateManager = new ToastStateManager() |
There was a problem hiding this comment.
The stateManager instance is created on every render but is never used. This creates unnecessary object allocation on each render. Either use the stateManager to manage state, or remove it entirely.
| class ToastStateManager { | ||
| private params: IToastProps | ||
| private mounted: boolean = false | ||
|
|
||
| constructor() { | ||
| this.params = { | ||
| type: 'info', | ||
| message: 'Toast message', | ||
| duration: 6000, | ||
| } | ||
| } | ||
|
|
||
| getParams() { return this.params } | ||
| setParams(p: IToastProps) { this.params = p } | ||
| isMounted() { return this.mounted } | ||
| setMounted(m: boolean) { this.mounted = m } | ||
| } |
There was a problem hiding this comment.
This ToastStateManager class is added but never used in the code. It appears to be dead code that should be removed. The class is instantiated in ToastProvider on line 123 but none of its methods are called, making it completely unused.
| }, params.duration || defaultDuring) | ||
| } | ||
| }, [defaultDuring, mounted, params.duration]) | ||
| }, [defaultDuring, mounted]) |
There was a problem hiding this comment.
The dependency array was changed to remove params.duration, but this creates a bug. The useEffect uses params.duration inside the callback (line 137), so it should be included in the dependency array. Without it, the effect won't re-run when the duration changes, causing the toast to use stale duration values.
| } | ||
| // Use the oldest answer item ID for pagination | ||
| const answerItems = allChatItems.filter(item => item.isAnswer) | ||
| const answerItems = allChatItems.filter(item => !item.isAnswer) |
There was a problem hiding this comment.
This filter logic has been inverted from item.isAnswer to !item.isAnswer. This is a critical bug - the code is now filtering for question items instead of answer items, which is the opposite of what the comment on line 250 states ("Use the oldest answer item ID for pagination"). This will break pagination logic for chat messages.
| const answerItems = allChatItems.filter(item => !item.isAnswer) | |
| const answerItems = allChatItems.filter(item => item.isAnswer) |
Benchmark PR from qodo-benchmark#131