From 00e8a00cb4aebb4e029b5ff32b8fe1e98f0b0bc5 Mon Sep 17 00:00:00 2001 From: CaIon Date: Mon, 21 Sep 2026 13:35:54 +0800 Subject: [PATCH] fix(web): include provider icons in Windows builds --- web/src/lib/__tests__/lobe-icon.test.tsx | 39 ++++++++++++++++++++++++ web/src/lib/lobe-icon.tsx | 3 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/web/src/lib/__tests__/lobe-icon.test.tsx b/web/src/lib/__tests__/lobe-icon.test.tsx index cc0f09eed..0fdff8e7d 100644 --- a/web/src/lib/__tests__/lobe-icon.test.tsx +++ b/web/src/lib/__tests__/lobe-icon.test.tsx @@ -16,6 +16,10 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ +import { readFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { runInNewContext } from 'node:vm' + import { act, render, screen } from '@testing-library/react' import { describe, expect, it, vi } from 'vitest' @@ -26,6 +30,41 @@ vi.mock('@lobehub/icons/es/Mistral/components/Color.js', () => { }) describe('Lobe icons', () => { + it.each([ + '/build/web/node_modules/@lobehub/icons/es/', + 'D:\\a\\new-api\\web\\node_modules\\@lobehub\\icons\\es\\', + ])('includes icon variants when bundling from %s', (root) => { + // Vitest loads imports without applying Rspack's webpackInclude filter. + const source = readFileSync( + resolve(import.meta.dirname, '../lobe-icon.tsx'), + 'utf8' + ) + const comment = source.match(/\/\*\s*(webpackInclude:[\s\S]*?)\*\//) + expect(comment).not.toBeNull() + const { webpackInclude } = runInNewContext( + `({${comment?.[1]}})`, + {}, + { + timeout: 1000, + } + ) as { webpackInclude: RegExp } + const separator = root.includes('\\') ? '\\' : '/' + const files = [ + 'OpenAI/components/Mono.js', + 'Claude/components/Color.js', + 'Gemini/components/Color.js', + 'Gemma/components/Simple.js', + 'LobeHub/components/Morden.js', + 'OpenAI/index.js', + 'OpenAI/components/Mono.d.ts', + 'OpenAI/components/Unknown.js', + ] + const included = files.filter((file) => + webpackInclude.test(root + file.replaceAll('/', separator)) + ) + expect(included).toEqual(files.slice(0, 5)) + }) + it('loads a named variant with its configured size and accessibility props', async () => { render( getLobeIcon('Claude.Color.size={32}.role="img".aria-label="Claude icon"') diff --git a/web/src/lib/lobe-icon.tsx b/web/src/lib/lobe-icon.tsx index d91e1aca3..37b9047bd 100644 --- a/web/src/lib/lobe-icon.tsx +++ b/web/src/lib/lobe-icon.tsx @@ -161,9 +161,10 @@ export function getLobeIcon( let IconComponent = LAZY_ICONS.get(cacheKey) if (!IconComponent) { // Load the selected SVG variant, avoiding the brand index's Avatar/UI dependencies. + // Rspack applies webpackInclude before normalizing Windows path separators. IconComponent = lazy(() => import( - /* webpackInclude: /\/components\/(Mono|Avatar|Brand|BrandColor|Color|Combine|Text|TextCn|TextColor|Simple|Morden)\.js$/ */ + /* webpackInclude: /[\\/]components[\\/](Mono|Avatar|Brand|BrandColor|Color|Combine|Text|TextCn|TextColor|Simple|Morden)\.js$/ */ `@lobehub/icons/es/${baseKey}/components/${variant}.js` ).catch(() => ({ default: (props: Record) =>