refactor(build): drop icons-local outDir sweeping, detect theme from ssr bundle

The stale-file cleanup in emitIconsCSS special-cased one file type for a
general condition: MPA mode never empties outDir, so hashed assets of
every kind accumulate (now a FIXME at the MPA copy step). The base test
harness clears its dist dirs itself, which the mpa/spa parity test
relied on the sweep for. Theme detection reuses the ssr bundle, which
exists in every mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/5407/head
Divyansh Singh 2 weeks ago
parent dac6c723b0
commit 4eb940510b

@ -1,5 +1,5 @@
import { spawnSync } from 'node:child_process' import { spawnSync } from 'node:child_process'
import { readFile } from 'node:fs/promises' import { readFile, rm } from 'node:fs/promises'
import { createServer, type Server } from 'node:http' import { createServer, type Server } from 'node:http'
import type { AddressInfo } from 'node:net' import type { AddressInfo } from 'node:net'
import { extname, join, resolve } from 'node:path' import { extname, join, resolve } from 'node:path'
@ -65,6 +65,8 @@ export async function setup() {
// one process per flavor: the markdown renderer is a module-level // one process per flavor: the markdown renderer is a module-level
// singleton, so in-process builds would leak the first base into the rest // singleton, so in-process builds would leak the first base into the rest
for (const mode of ['plain', 'relative', 'cdn', 'mpa']) { for (const mode of ['plain', 'relative', 'cdn', 'mpa']) {
// mpa builds never empty outDir, so stale assets would survive reruns
await rm(dist(mode), { recursive: true, force: true })
const res = spawnSync(process.execPath, [bin, 'build', 'fixture'], { const res = spawnSync(process.execPath, [bin, 'build', 'fixture'], {
cwd: dir, cwd: dir,
env: { env: {

@ -3,7 +3,6 @@ import fs from 'node:fs'
import { import {
mkdir, mkdir,
readFile, readFile,
readdir,
rm, rm,
symlink, symlink,
unlink, unlink,
@ -165,6 +164,9 @@ async function render(
const clientOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] = const clientOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] =
clientResult?.output || [] clientResult?.output || []
const resultOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] =
(siteConfig.mpa ? serverResult : clientResult)?.output || []
const appChunk = clientOutput.find( const appChunk = clientOutput.find(
(chunk): chunk is Rolldown.OutputChunk => (chunk): chunk is Rolldown.OutputChunk =>
chunk.type === 'chunk' && chunk.type === 'chunk' &&
@ -172,20 +174,12 @@ async function render(
!!chunk.facadeModuleId?.endsWith('.js') !!chunk.facadeModuleId?.endsWith('.js')
) )
// MPA has no client bundle — detect the theme from the bundle that exists const isDefaultTheme = resultOutput.some(
const isDefaultTheme = (
(siteConfig.mpa ? serverResult : clientResult)?.output || []
).some(
(chunk): chunk is Rolldown.OutputChunk => (chunk): chunk is Rolldown.OutputChunk =>
chunk.type === 'chunk' && chunk.type === 'chunk' &&
chunk.moduleIds.some((id) => id.includes('client/theme-default')) chunk.moduleIds.some((id) => id.includes('client/theme-default'))
) )
// ----
const resultOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] =
(siteConfig.mpa ? serverResult : clientResult)?.output || []
const cssChunk = resultOutput.find( const cssChunk = resultOutput.find(
(chunk): chunk is Rolldown.OutputAsset => (chunk): chunk is Rolldown.OutputAsset =>
chunk.type === 'asset' && chunk.fileName.endsWith('.css') chunk.type === 'asset' && chunk.fileName.endsWith('.css')
@ -226,9 +220,7 @@ async function render(
} }
} }
// pre-seeded with icons SSR collection cannot see (client-only renders); // pre-seeded with icons SSR collection cannot see (client-only renders)
// the Array.isArray guard keeps an untyped config's bare string from
// spreading into characters
const include = siteConfig.icons?.include const include = siteConfig.icons?.include
const usedIcons = new Set<string>(Array.isArray(include) ? include : []) const usedIcons = new Set<string>(Array.isArray(include) ? include : [])
@ -275,18 +267,9 @@ async function emitIconsCSS(
config.logger.warn(c.yellow(`(icons) ${warning}`)) config.logger.warn(c.yellow(`(icons) ${warning}`))
} }
// MPA builds never empty outDir, so files from prior builds linger — both
// hashed sheets and the fixed-name file pre-rework versions emitted
const assetsDir = path.join(config.outDir, config.assetsDir) const assetsDir = path.join(config.outDir, config.assetsDir)
const existing = await readdir(assetsDir).catch(() => [] as string[])
await Promise.all([
unlink(path.join(config.outDir, 'vp-icons.css')).catch(() => {}),
...existing
.filter((file) => /^vp-icons\.[0-9a-f]{8}\.css$/.test(file))
.map((file) => unlink(path.join(assetsDir, file)))
])
const placeholder = vpIconsFileName(VP_ICONS_HASH_PLACEHOLDER) const placeholder = vpIconsFileName(VP_ICONS_HASH_PLACEHOLDER)
let hashedName = '' let hashedName = ''
if (css) { if (css) {
hashedName = vpIconsFileName( hashedName = vpIconsFileName(
@ -296,10 +279,6 @@ async function emitIconsCSS(
await writeFile(path.join(assetsDir, hashedName), css) await writeFile(path.join(assetsDir, hashedName), css)
} }
// pages linked the placeholder name before the hash could exist — point
// them at the emitted file, or drop the whole tag when there is none.
// Anchored on the placeholder, not the tag shape, so a transformHtml
// hook reformatting attributes doesn't defeat it
const linkRE = new RegExp( const linkRE = new RegExp(
`[ \\t]*<link\\b[^>]*${VP_ICONS_HASH_PLACEHOLDER}[^>]*>\\n?` `[ \\t]*<link\\b[^>]*${VP_ICONS_HASH_PLACEHOLDER}[^>]*>\\n?`
) )

@ -155,6 +155,10 @@ export async function bundle(
)) as Rolldown.RolldownOutput )) as Rolldown.RolldownOutput
if (config.mpa) { if (config.mpa) {
// FIXME: nothing ever empties outDir in MPA mode (no client build runs
// with emptyOutDir, and buildMPAClient sets emptyOutDir: false), so
// hashed assets of every kind accumulate across rebuilds into a dirty
// output directory
// in MPA mode, we need to copy over the non-js asset files from the // in MPA mode, we need to copy over the non-js asset files from the
// server build since there is no client-side build. // server build since there is no client-side build.
await pMap( await pMap(

Loading…
Cancel
Save