Skip to content

Commit 0fe0882

Browse files
committed
perf: warm tailwind v4 incremental cache
1 parent 94d8dfd commit 0fe0882

3 files changed

Lines changed: 100 additions & 13 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"weapp-tailwindcss": patch
3+
---
4+
5+
Tailwind CSS v4 初始源码扫描生成完成后会同步预热增量生成缓存,避免第一次热更新因为没有基线缓存而再次触发完整 v4 生成。

packages/weapp-tailwindcss/src/tailwindcss/v4-engine/generator.ts

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ interface TailwindV4IncrementalGenerateCacheEntry {
3636
target: TailwindV4GenerateTarget
3737
}
3838

39+
interface TailwindV4IncrementalCacheSeedOptions {
40+
compatibleSource: TailwindV4ResolvedSource
41+
generated: Awaited<ReturnType<TailwindV4Engine['generate']>>
42+
requestedCandidates: Set<string>
43+
styleOptions: Partial<IStyleHandlerOptions> | undefined
44+
tailwindcssV3Compatibility: boolean | undefined
45+
target: TailwindV4GenerateTarget
46+
}
47+
3948
function findLeadingImportInsertionIndex(css: string) {
4049
const importPattern = /(?:^|\n)\s*@import\b[^;]*;/g
4150
let insertionIndex = 0
@@ -134,6 +143,36 @@ function resolveTargetCandidates(
134143
: collected
135144
}
136145

146+
function collectSeenCandidates(
147+
generated: Pick<Awaited<ReturnType<TailwindV4Engine['generate']>>, 'rawCandidates' | 'classSet'>,
148+
requestedCandidates: Set<string>,
149+
) {
150+
return new Set([
151+
...requestedCandidates,
152+
...generated.rawCandidates,
153+
...generated.classSet,
154+
])
155+
}
156+
157+
function seedIncrementalGenerateCache(options: TailwindV4IncrementalCacheSeedOptions) {
158+
const cacheKey = createIncrementalGenerateCacheKey(
159+
options.compatibleSource,
160+
options.target,
161+
options.styleOptions,
162+
options.tailwindcssV3Compatibility,
163+
)
164+
incrementalGenerateCache.set(cacheKey, {
165+
seenCandidates: collectSeenCandidates(options.generated, options.requestedCandidates),
166+
classSet: new Set(options.generated.classSet),
167+
css: options.generated.css,
168+
rawCss: options.generated.rawCss,
169+
dependencies: options.generated.dependencies,
170+
sources: options.generated.sources,
171+
root: options.generated.root,
172+
target: options.generated.target,
173+
})
174+
}
175+
137176
function parseImportSourceParam(params: string) {
138177
const match = /\bsource\(\s*(none|(['"])(.*?)\2)\s*\)/.exec(params)
139178
if (!match) {
@@ -360,13 +399,27 @@ export function createTailwindV4Engine(source: TailwindV4ResolvedSource): Tailwi
360399
}
361400

362401
async function generateWithIncrementalCache(options: TailwindV4GenerateOptions = {}) {
363-
if ((options.sources?.length ?? 0) > 0 || options.bareArbitraryValues !== undefined || options.scanSources === true || Array.isArray(options.scanSources)) {
364-
return generateOnce(source, options)
365-
}
366-
367402
const target = options.target ?? 'weapp'
368403
const compatibleSource = createCompatibleSource(source, target, options.tailwindcssV3Compatibility)
369404
const requestedCandidates = resolveTargetCandidates(options.candidates, target)
405+
406+
if ((options.sources?.length ?? 0) > 0 || options.bareArbitraryValues !== undefined || Array.isArray(options.scanSources)) {
407+
return generateOnce(source, options)
408+
}
409+
410+
if (options.scanSources === true) {
411+
const generated = await generateOnce(source, options)
412+
seedIncrementalGenerateCache({
413+
compatibleSource,
414+
generated,
415+
requestedCandidates,
416+
styleOptions: options.styleOptions,
417+
tailwindcssV3Compatibility: options.tailwindcssV3Compatibility,
418+
target,
419+
})
420+
return generated
421+
}
422+
370423
const cacheKey = createIncrementalGenerateCacheKey(
371424
compatibleSource,
372425
target,
@@ -427,15 +480,13 @@ export function createTailwindV4Engine(source: TailwindV4ResolvedSource): Tailwi
427480
}
428481

429482
const generated = await generateOnce(source, options)
430-
incrementalGenerateCache.set(cacheKey, {
431-
seenCandidates: new Set(requestedCandidates),
432-
classSet: new Set(generated.classSet),
433-
css: generated.css,
434-
rawCss: generated.rawCss,
435-
dependencies: generated.dependencies,
436-
sources: generated.sources,
437-
root: generated.root,
438-
target: generated.target,
483+
seedIncrementalGenerateCache({
484+
compatibleSource,
485+
generated,
486+
requestedCandidates,
487+
styleOptions: options.styleOptions,
488+
tailwindcssV3Compatibility: options.tailwindcssV3Compatibility,
489+
target,
439490
})
440491
return generated
441492
}

packages/weapp-tailwindcss/test/tailwindcss/v4-engine.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,37 @@ describe('tailwindcss v4 engine', () => {
110110
expect(second.css.match(/\.text-_b88rpx_B/g) ?? []).toHaveLength(1)
111111
})
112112

113+
it('seeds the v4 incremental cache from the initial source scan', async () => {
114+
const source = await resolveTailwindV4Source({
115+
css: MINIMAL_THEME_CSS,
116+
base: process.cwd(),
117+
})
118+
const engine = createTailwindV4Engine(source)
119+
120+
const first = await engine.generate({
121+
candidates: ['text-[88rpx]'],
122+
incrementalCache: true,
123+
scanSources: true,
124+
styleOptions: {
125+
isMainChunk: false,
126+
},
127+
})
128+
const second = await engine.generate({
129+
candidates: ['text-[88rpx]', 'text-[188rpx]'],
130+
incrementalCache: true,
131+
scanSources: false,
132+
styleOptions: {
133+
isMainChunk: false,
134+
},
135+
})
136+
137+
expect(first.classSet).toEqual(new Set(['text-[88rpx]']))
138+
expect(second.classSet).toEqual(new Set(['text-[88rpx]', 'text-[188rpx]']))
139+
expect(second.css).toContain('.text-_b88rpx_B')
140+
expect(second.css).toContain('.text-_b188rpx_B')
141+
expect(second.css.match(/\.text-_b88rpx_B/g) ?? []).toHaveLength(1)
142+
})
143+
113144
it('uses mini-program-safe Tailwind v4 default color variables for native v4 weapp output', async () => {
114145
const source = await resolveTailwindV4Source({
115146
css: `

0 commit comments

Comments
 (0)