@@ -4,6 +4,7 @@ import type { InternalUserDefinedOptions } from '@/types'
44import { existsSync , readFileSync } from 'node:fs'
55import path from 'node:path'
66import process from 'node:process'
7+ import { resolveTailwindV4EntriesFromCss } from '@/bundlers/vite/source-scan'
78import {
89 resolveTailwindV3Source ,
910 resolveTailwindV3SourceFromPatcher ,
@@ -33,11 +34,27 @@ interface GeneratorSourceRuntimeState {
3334 twPatcher : InternalUserDefinedOptions [ 'twPatcher' ]
3435}
3536
37+ export interface GeneratorSourceMetadata {
38+ matchedCssSourceFile ?: string | undefined
39+ }
40+
41+ export type GeneratorResolvedSource = TailwindResolvedSource & {
42+ __weappTailwindcssMeta ?: GeneratorSourceMetadata | undefined
43+ }
44+
3645function resolvePostcssFromOption ( cssHandlerOptions : IStyleHandlerOptions ) {
3746 const from = cssHandlerOptions . postcssOptions ?. options ?. from
3847 return typeof from === 'string' && from . length > 0 ? from : undefined
3948}
4049
50+ function resolvePostcssSourceFile ( cssHandlerOptions : IStyleHandlerOptions ) {
51+ const from = resolvePostcssFromOption ( cssHandlerOptions )
52+ if ( ! from || ! path . isAbsolute ( from ) ) {
53+ return undefined
54+ }
55+ return from . replace ( / [ ? # ] .* $ / , '' )
56+ }
57+
4158export function resolveCssSourceBase ( file : string , cssHandlerOptions : IStyleHandlerOptions ) {
4259 const from = resolvePostcssFromOption ( cssHandlerOptions )
4360 const baseFile = from ?? file
@@ -107,6 +124,41 @@ function getOutputFileStem(file: string) {
107124 return path . basename ( normalized , path . extname ( normalized ) )
108125}
109126
127+ function getOutputFileWithoutExtension ( file : string ) {
128+ const normalized = file . replace ( / [ ? # ] .* $ / , '' )
129+ const ext = path . extname ( normalized )
130+ return ext ? normalized . slice ( 0 , - ext . length ) : normalized
131+ }
132+
133+ function normalizeMatchPath ( file : string ) {
134+ return file . split ( path . sep ) . join ( '/' )
135+ }
136+
137+ function stripKnownBuildRootPrefix ( file : string ) {
138+ const segments = normalizeMatchPath ( file ) . split ( '/' )
139+ const knownRoots = new Set ( [ 'dist' , 'src' ] )
140+ for ( let index = segments . length - 1 ; index >= 0 ; index -- ) {
141+ if ( knownRoots . has ( segments [ index ] ! ) ) {
142+ return segments . slice ( index + 1 ) . join ( '/' )
143+ }
144+ }
145+ return segments . join ( '/' )
146+ }
147+
148+ function isMatchingTailwindV4CssSourceFile ( file : string , cssSourceFile : string ) {
149+ const outputBase = normalizeMatchPath ( getOutputFileWithoutExtension ( path . resolve ( file ) ) )
150+ const sourceBase = normalizeMatchPath ( getOutputFileWithoutExtension ( path . resolve ( cssSourceFile ) ) )
151+ const outputRelativeBase = stripKnownBuildRootPrefix ( outputBase )
152+ const sourceRelativeBase = stripKnownBuildRootPrefix ( sourceBase )
153+ return outputBase === sourceBase
154+ || outputBase . endsWith ( `/${ sourceBase } ` )
155+ || sourceBase . endsWith ( `/${ outputBase } ` )
156+ || (
157+ outputRelativeBase . length > 0
158+ && outputRelativeBase === sourceRelativeBase
159+ )
160+ }
161+
110162function resolveMatchingTailwindV4CssEntry (
111163 rawSource : string ,
112164 file : string ,
@@ -143,6 +195,43 @@ function resolveMatchingTailwindV4CssEntry(
143195 } )
144196}
145197
198+ async function resolveMatchingTailwindV4CssSource (
199+ rawSource : string ,
200+ file : string ,
201+ cssHandlerOptions : IStyleHandlerOptions ,
202+ sourceOptions : ReturnType < typeof resolveTailwindV4SourceOptionsFromPatcher > ,
203+ ) {
204+ const cssSources = sourceOptions . cssSources
205+ if ( ! cssSources ?. length ) {
206+ return undefined
207+ }
208+
209+ const normalizedRawSource = normalizeCssSourceForCompare ( rawSource )
210+ const sourceFile = resolvePostcssSourceFile ( cssHandlerOptions )
211+ const matchingSource = cssSources . find ( ( cssSource ) => {
212+ if ( typeof cssSource . css !== 'string' || cssSource . css . length === 0 ) {
213+ return false
214+ }
215+ if ( sourceFile && typeof cssSource . file === 'string' && path . resolve ( sourceFile ) === path . resolve ( cssSource . file ) ) {
216+ return true
217+ }
218+ if ( typeof cssSource . file === 'string' && isMatchingTailwindV4CssSourceFile ( file , cssSource . file ) ) {
219+ return true
220+ }
221+ return normalizeCssSourceForCompare ( cssSource . css ) === normalizedRawSource
222+ } )
223+ if ( ! matchingSource ) {
224+ return undefined
225+ }
226+ const source = await resolveTailwindV4Source ( {
227+ ...omitUndefined ( sourceOptions ) ,
228+ cssSources : [ matchingSource ] ,
229+ } )
230+ return withGeneratorSourceMetadata ( source , {
231+ matchedCssSourceFile : typeof matchingSource . file === 'string' ? matchingSource . file : undefined ,
232+ } )
233+ }
234+
146235function tryResolveTailwindV4SourceOptions (
147236 runtimeState : GeneratorSourceRuntimeState ,
148237) {
@@ -161,6 +250,32 @@ function hasConfiguredTailwindV4CssSource(
161250 || Boolean ( sourceOptions ?. cssSources ?. length )
162251}
163252
253+ function createTailwindV4CssSourceResolver (
254+ sourceOptions : ReturnType < typeof resolveTailwindV4SourceOptionsFromPatcher > ,
255+ generatorOptions : NormalizedWeappTailwindcssGeneratorOptions | undefined ,
256+ ) {
257+ return ( cssSource : NonNullable < typeof sourceOptions . cssSources > [ number ] ) =>
258+ resolveTailwindV4Source ( {
259+ ...omitUndefined ( sourceOptions ) ,
260+ cssSources : [ cssSource ] ,
261+ } ) . then ( source => generatorOptions ?. config
262+ ? {
263+ ...source ,
264+ css : prependConfigDirective ( source . css , generatorOptions . config ) ,
265+ }
266+ : source )
267+ }
268+
269+ function withGeneratorSourceMetadata (
270+ source : TailwindResolvedSource ,
271+ metadata : GeneratorSourceMetadata ,
272+ ) : GeneratorResolvedSource {
273+ return {
274+ ...source ,
275+ __weappTailwindcssMeta : metadata ,
276+ }
277+ }
278+
164279function createTailwindV4ApplyReferenceSource ( css : string , sourceOptions : { packageName ?: string } ) {
165280 if ( ! hasTailwindApplyDirective ( css ) || hasTailwindRootDirectives ( css ) ) {
166281 return css
@@ -211,10 +326,13 @@ export async function resolveGeneratorSource(
211326 }
212327
213328 const sourceOptions = tryResolveTailwindV4SourceOptions ( runtimeState )
329+ const matchedCssSource = sourceOptions
330+ ? await resolveMatchingTailwindV4CssSource ( rawSource , file , cssHandlerOptions , sourceOptions )
331+ : undefined
214332 const configuredCssSource = sourceOptions
215333 && hasConfiguredTailwindV4CssSource ( sourceOptions )
216334 && hasTailwindGeneratedCssMarkers ( rawSource )
217- ? await resolveTailwindV4Source ( sourceOptions )
335+ ? matchedCssSource ?? await resolveTailwindV4Source ( sourceOptions )
218336 : undefined
219337 if ( configuredCssSource ) {
220338 return generatorOptions ?. config
@@ -240,7 +358,7 @@ export async function resolveGeneratorSource(
240358 cssEntries : [ sourceOptions . cssEntries [ 0 ] ! ] ,
241359 } )
242360 : undefined
243- const preferredCssEntrySource = matchedCssEntrySource ?? mainCssEntrySource
361+ const preferredCssEntrySource = matchedCssEntrySource ?? matchedCssSource ?? mainCssEntrySource
244362 if ( preferredCssEntrySource ) {
245363 return generatorOptions ?. config
246364 ? {
@@ -310,13 +428,32 @@ export async function resolveGeneratorSources(
310428 ]
311429 }
312430
431+ const matchedCssEntrySource = cssEntrySource
432+ ? await resolveMatchingTailwindV4CssEntry ( rawSource , file , sourceOptions )
433+ : undefined
434+ const matchedCssSource = await resolveMatchingTailwindV4CssSource ( rawSource , file , cssHandlerOptions , sourceOptions )
435+ const preferredCssEntrySource = matchedCssEntrySource ?? matchedCssSource
436+ if ( preferredCssEntrySource ) {
437+ return [
438+ generatorOptions ?. config
439+ ? {
440+ ...preferredCssEntrySource ,
441+ css : prependConfigDirective ( preferredCssEntrySource . css , generatorOptions . config ) ,
442+ }
443+ : preferredCssEntrySource ,
444+ ]
445+ }
446+
313447 if ( ! sourceOptions . cssEntries || sourceOptions . cssEntries . length <= 1 ) {
448+ if ( sourceOptions . cssSources ?. length ) {
449+ return Promise . all ( sourceOptions . cssSources . map ( createTailwindV4CssSourceResolver ( sourceOptions , generatorOptions ) ) )
450+ }
314451 return [
315452 await resolveGeneratorSource ( majorVersion , runtimeState , rawSource , file , cssHandlerOptions , generatorOptions ) ,
316453 ]
317454 }
318455
319- const sources = await Promise . all ( sourceOptions . cssEntries . map ( cssEntry =>
456+ const cssEntrySources = await Promise . all ( sourceOptions . cssEntries . map ( cssEntry =>
320457 resolveTailwindV4Source ( {
321458 ...omitUndefined ( sourceOptions ) ,
322459 cssEntries : [ cssEntry ] ,
@@ -327,5 +464,65 @@ export async function resolveGeneratorSources(
327464 }
328465 : source ) ,
329466 ) )
330- return sources
467+ const cssSources = sourceOptions . cssSources ?. length
468+ ? await Promise . all ( sourceOptions . cssSources . map ( createTailwindV4CssSourceResolver ( sourceOptions , generatorOptions ) ) )
469+ : [ ]
470+ return [
471+ ...cssEntrySources ,
472+ ...cssSources ,
473+ ]
474+ }
475+
476+ export async function resolveGeneratorSourceEntries ( source : TailwindResolvedSource , runtimeState ?: GeneratorSourceRuntimeState ) {
477+ if ( ! ( 'css' in source ) || ! ( 'base' in source ) || ! ( 'baseFallbacks' in source ) ) {
478+ return undefined
479+ }
480+ const sourceMetadata = ( source as GeneratorResolvedSource ) . __weappTailwindcssMeta
481+ const resolved = await resolveTailwindV4EntriesFromCss ( source . css , source . base )
482+ if ( resolved ?. entries . length || ( ! resolved ?. explicit && ! sourceMetadata ?. matchedCssSourceFile ) || ! runtimeState ) {
483+ return resolved ?. entries
484+ }
485+ const sourceOptions = tryResolveTailwindV4SourceOptions ( runtimeState )
486+ const matchingCssSource = sourceOptions ?. cssSources ?. find ( ( cssSource ) => {
487+ if (
488+ sourceMetadata ?. matchedCssSourceFile
489+ && typeof cssSource . file === 'string'
490+ && path . resolve ( cssSource . file ) === path . resolve ( sourceMetadata . matchedCssSourceFile )
491+ ) {
492+ return true
493+ }
494+ return cssSource . css === source . css
495+ } )
496+ if ( ! matchingCssSource ) {
497+ return resolved ?. entries
498+ }
499+ const sourceResolved = await resolveTailwindV4EntriesFromCss (
500+ matchingCssSource . css ,
501+ typeof matchingCssSource . base === 'string' && matchingCssSource . base . length > 0
502+ ? matchingCssSource . base
503+ : typeof matchingCssSource . file === 'string' && matchingCssSource . file . length > 0
504+ ? path . dirname ( matchingCssSource . file )
505+ : source . base ,
506+ )
507+ if ( sourceResolved ?. entries . length ) {
508+ return sourceResolved . entries
509+ }
510+ for ( const dependency of matchingCssSource . dependencies ?? [ ] ) {
511+ if ( ! existsSync ( dependency ) ) {
512+ continue
513+ }
514+ try {
515+ const dependencyResolved = await resolveTailwindV4EntriesFromCss (
516+ readFileSync ( dependency , 'utf8' ) ,
517+ path . dirname ( dependency ) ,
518+ )
519+ if ( dependencyResolved ?. entries . length ) {
520+ return dependencyResolved . entries
521+ }
522+ }
523+ catch {
524+ // 依赖内容只用于裁剪候选,读取失败时回退到 Tailwind 自身生成逻辑。
525+ }
526+ }
527+ return resolved . entries
331528}
0 commit comments