refactor(client): move web rendering into a dynamic plugin

This commit is contained in:
imccyu
2026-08-18 01:34:21 +08:00
parent 3e4ad10d05
commit f37bc082c5
98 changed files with 1675 additions and 1049 deletions
+54 -6
View File
@@ -1,6 +1,6 @@
/**
* CSS Modules enter client bundles through virtual modules, so the loader must
* explicitly register the underlying stylesheet as a watch dependency.
* Stylesheets enter client bundles through virtual modules, so the loader must
* register their physical files as watch dependencies.
*/
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
@@ -14,7 +14,7 @@ interface CssPlugin {
load?: (this: { addWatchFile(id: string): void }, id: string) => Promise<string | null>
}
function cssPlugin(): CssPlugin {
function cssPlugin(name: 'dsh-css-modules-inline' | 'dsh-css-global-inline' | 'dsh-css-text-inline'): CssPlugin {
const configs = clientBundle(
'@deepseek-ai/dsh-client-test',
['lib/types/index.js', 'lib/types/invariant.js'],
@@ -22,8 +22,8 @@ function cssPlugin(): CssPlugin {
const client = configs.find(config => config.platform === 'browser')
if (client === undefined) throw new Error('client config missing')
const plugins = (client as { plugins: CssPlugin[] }).plugins
const plugin = plugins.find(candidate => candidate.name === 'dsh-css-modules-inline')
if (plugin === undefined) throw new Error('CSS Modules plugin missing from client config')
const plugin = plugins.find(candidate => candidate.name === name)
if (plugin === undefined) throw new Error(`${name} missing from client config`)
return plugin
}
@@ -34,7 +34,7 @@ describe('client bundle CSS Modules', () => {
const stylesheet = join(root, 'Fixture.module.css')
const importer = join(root, 'index.ts')
await writeFile(stylesheet, '.root { color: red; }\n')
const plugin = cssPlugin()
const plugin = cssPlugin('dsh-css-modules-inline')
const virtualId = plugin.resolveId?.('./Fixture.module.css', importer)
if (typeof virtualId !== 'string' || plugin.load === undefined) {
throw new Error('CSS Modules plugin hooks are incomplete')
@@ -50,3 +50,51 @@ describe('client bundle CSS Modules', () => {
}
})
})
describe('client bundle global CSS', () => {
it('compiles a side-effect stylesheet into a watched style injector', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-client-global-css-watch-'))
try {
const stylesheet = join(root, 'base.css')
const importer = join(root, 'index.ts')
await writeFile(stylesheet, 'body { color: red; }\n')
const plugin = cssPlugin('dsh-css-global-inline')
const virtualId = plugin.resolveId?.('./base.css', importer)
if (typeof virtualId !== 'string' || plugin.load === undefined) {
throw new Error('global CSS plugin hooks are incomplete')
}
const watched: string[] = []
const output = await plugin.load.call({ addWatchFile: id => watched.push(id) }, virtualId)
expect(watched).toEqual([stylesheet])
expect(output).toContain('data-plugin-css')
expect(output).toContain('body{color:red}')
} finally {
await rm(root, { recursive: true, force: true })
}
})
it('compiles inline stylesheets as watched text without a module side effect', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-client-inline-css-watch-'))
try {
const stylesheet = join(root, 'base.css')
const importer = join(root, 'index.ts')
await writeFile(stylesheet, 'body { color: red; }\n')
const plugin = cssPlugin('dsh-css-text-inline')
const virtualId = plugin.resolveId?.('./base.css?inline', importer)
if (typeof virtualId !== 'string' || plugin.load === undefined) {
throw new Error('inline CSS plugin hooks are incomplete')
}
const watched: string[] = []
const output = await plugin.load.call({ addWatchFile: id => watched.push(id) }, virtualId)
expect(watched).toEqual([stylesheet])
expect(output).toContain('export default "body{color:red}"')
expect(output).not.toContain('data-plugin-css')
} finally {
await rm(root, { recursive: true, force: true })
}
})
})