diff --git a/e2e/cases/html/favicon/index.test.ts b/e2e/cases/html/favicon/index.test.ts index 0846e89cbb..858950e70c 100644 --- a/e2e/cases/html/favicon/index.test.ts +++ b/e2e/cases/html/favicon/index.test.ts @@ -1,6 +1,6 @@ import path from 'node:path'; - import { expect, findFile, getFileContent, test } from '@e2e/helper'; +import { outputFile, remove } from 'fs-extra'; test('should emit local favicon to dist path', async ({ build }) => { const rsbuild = await build({ @@ -10,13 +10,12 @@ test('should emit local favicon to dist path', async ({ build }) => { }, }, }); + const files = rsbuild.getDistFiles(); const icon = findFile(files, 'icon.png'); - expect(icon.endsWith('/icon.png')).toBeTruthy(); const html = getFileContent(files, 'index.html'); - expect(html).toContain(''); }); @@ -30,13 +29,12 @@ test('should allow `html.favicon` to be an absolute path', async ({ }, }, }); + const files = rsbuild.getDistFiles(); const icon = findFile(files, 'icon.png'); - expect(icon.endsWith('/icon.png')).toBeTruthy(); const html = getFileContent(files, 'index.html'); - expect(html).toContain(''); }); @@ -48,13 +46,12 @@ test('should add type attribute for SVG favicon', async ({ build }) => { }, }, }); + const files = rsbuild.getDistFiles(); const icon = findFile(files, 'mobile.svg'); - expect(icon.endsWith('/mobile.svg')).toBeTruthy(); const html = getFileContent(files, 'index.html'); - expect(html).toContain( '', ); @@ -88,10 +85,9 @@ test('should allow favicon to be a CDN URL', async ({ build }) => { }, }, }); - const files = rsbuild.getDistFiles(); + const files = rsbuild.getDistFiles(); const html = getFileContent(files, 'index.html'); - expect(html).toContain(''); }); @@ -115,6 +111,7 @@ test('should generate favicon via function correctly', async ({ build }) => { }, }, }); + const files = rsbuild.getDistFiles(); const fooHtml = getFileContent(files, 'foo.html'); @@ -143,13 +140,12 @@ test('should allow to custom favicon dist path with a relative path', async ({ }, }, }); + const files = rsbuild.getDistFiles(); const faviconFile = findFile(files, 'static/favicon/icon.png'); - expect(faviconFile.endsWith('/static/favicon/icon.png')).toBeTruthy(); const html = getFileContent(files, 'index.html'); - expect(html).toContain(''); }); @@ -168,12 +164,37 @@ test('should allow to custom favicon dist path with a relative path starting wit }, }, }); + const files = rsbuild.getDistFiles(); const faviconFile = findFile(files, 'custom/icon.png'); - expect(faviconFile.endsWith('/custom/icon.png')).toBeTruthy(); const html = getFileContent(files, 'index.html'); - expect(html).toContain(''); }); + +for (const filename of ['favicon.ico', 'favicon.png', 'favicon.svg']) { + const publicPath = path.join(__dirname, 'test-temp-public'); + + test(`should resolve ${filename} under public dir by default`, async ({ + build, + }) => { + await remove(publicPath); + await outputFile(path.join(publicPath, filename), ''); + + const rsbuild = await build({ + config: { + server: { + publicDir: [{ name: publicPath }], + }, + }, + }); + + const files = rsbuild.getDistFiles(); + const faviconFile = findFile(files, filename); + expect(faviconFile.endsWith(`/${filename}`)).toBeTruthy(); + + const html = getFileContent(files, 'index.html'); + expect(html).toContain(` ({ name: 'rsbuild:html', setup(api) { + let defaultFavicon: string | undefined; + + const resolveDefaultFavicon = () => { + if (defaultFavicon) { + return defaultFavicon; + } + + const { rootPath } = api.context; + const { publicDir } = api.getNormalizedConfig().server; + const extensions = ['ico', 'png', 'svg']; + const publicDirs = Array.from( + new Set( + publicDir.map(({ name }) => + isAbsolute(name) ? name : path.join(rootPath, name), + ), + ), + ); + + const faviconPaths: string[] = []; + for (const publicDir of publicDirs) { + for (const ext of extensions) { + faviconPaths.push(path.join(publicDir, `favicon.${ext}`)); + } + } + + const faviconPath = findExists(faviconPaths); + if (faviconPath) { + defaultFavicon = faviconPath; + } + return defaultFavicon; + }; + api.modifyBundlerChain( async (chain, { HtmlPlugin, CHAIN_ID, environment }) => { const { config, htmlPaths } = environment; @@ -294,7 +326,8 @@ export const pluginHtml = (context: InternalContext): RsbuildPlugin => ({ pluginOptions.title = getTitle(entryName, config); - const favicon = getFavicon(entryName, config); + const favicon = + getFavicon(entryName, config) || resolveDefaultFavicon(); if (favicon) { extraData.favicon = favicon; }