-
-
Notifications
You must be signed in to change notification settings - Fork 102
Expand file tree
/
Copy pathhandler.ts
More file actions
175 lines (157 loc) · 6.48 KB
/
Copy pathhandler.ts
File metadata and controls
175 lines (157 loc) · 6.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// 样式处理入口,负责构建和复用 PostCSS 管线
import type { Result as PostcssResult, Root } from 'postcss'
import type { FeatureSignal } from './content-probe'
import type { IStyleHandlerOptions, StyleHandler } from './types'
import { defuOverrideArray } from '@weapp-tailwindcss/shared'
import { LRUCache } from 'lru-cache'
import postcss from 'postcss'
import { protectDynamicColorMixAlpha } from './compat/color-mix'
import { removeEmptyBlockAtRules } from './compat/mini-program-css/root-cleanups'
import { probeFeatures, signalToCacheKey } from './content-probe'
import { getDefaultOptions } from './defaults'
import { fingerprintOptions } from './fingerprint'
import { resolvePostcssFrameworkProfile } from './frameworks'
import { createOptionsResolver } from './options-resolver'
import { createInjectPreflight } from './preflight'
import { StyleProcessorCache } from './processor-cache'
/** CSS 结果缓存最大条目数 */
const CSS_RESULT_CACHE_MAX = 256
/**
* 简单字符串哈希函数(FNV-1a 变体),用于生成缓存键。
* 不依赖 crypto 模块,适合高频调用场景。
*/
function simpleHash(str: string): string {
let hash = 0x811C9DC5 | 0
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i)
hash = (hash * 0x01000193) | 0
}
return (hash >>> 0).toString(36)
}
// createStyleHandler 提供带缓存的高阶处理器,同时暴露 getPipeline 供外部调试/扩展
export function createStyleHandler(options?: Partial<IStyleHandlerOptions>): StyleHandler {
const cachedOptions = defuOverrideArray<
IStyleHandlerOptions,
Partial<IStyleHandlerOptions>[]
>(
options as IStyleHandlerOptions,
getDefaultOptions(options),
)
cachedOptions.cssInjectPreflight = createInjectPreflight(cachedOptions.cssPreflight)
const resolver = createOptionsResolver(cachedOptions)
const processorCache = new StyleProcessorCache()
const base = resolver.resolve()
processorCache.getProcessor(base)
processorCache.getProcessOptions(base)
/** 选项指纹缓存,避免重复序列化 */
const optionsFingerprintCache = new WeakMap<IStyleHandlerOptions, string>()
/**
* 获取选项指纹(带缓存)
*/
function getOptionsFingerprint(opts: IStyleHandlerOptions): string {
const cached = optionsFingerprintCache.get(opts)
if (cached) {
return cached
}
const fp = fingerprintOptions(opts)
optionsFingerprintCache.set(opts, fp)
return fp
}
/** CSS 处理结果 LRU 缓存 */
const resultCache = new LRUCache<string, PostcssResult>({ max: CSS_RESULT_CACHE_MAX })
/** 检测是否配置了用户 postcss 插件(如 tailwindcss),有用户插件时不做内容探测 */
const hasUserPlugins = Boolean(
cachedOptions.postcssOptions?.plugins
&& (Array.isArray(cachedOptions.postcssOptions.plugins)
? cachedOptions.postcssOptions.plugins.length > 0
: typeof cachedOptions.postcssOptions.plugins === 'object'
&& Object.keys(cachedOptions.postcssOptions.plugins).length > 0),
)
function cloneResult(result: PostcssResult): PostcssResult {
if (!result.root || typeof result.root.clone !== 'function') {
return result
}
const cloned = result.root.clone().toResult(result.opts)
cloned.messages.push(...(result.messages ?? []))
return cloned
}
function processSource(
rawSource: string,
root: Root | undefined,
cloneOutput: boolean,
opt?: Partial<IStyleHandlerOptions>,
) {
const resolvedOptions = resolver.resolve(opt)
const protectedColorMix = resolvedOptions.majorVersion === 4
? protectDynamicColorMixAlpha(rawSource)
: undefined
const source = protectedColorMix?.css ?? rawSource
const processInput = protectedColorMix ? source : root?.clone() ?? source
// 当有用户插件时跳过内容探测,因为用户插件(如 tailwindcss)可能在 pre 阶段
// 生成新的 CSS 特征(如现代颜色函数、:is() 伪类等),而 probeFeatures 只看原始输入
let signal: FeatureSignal | undefined
if (!hasUserPlugins) {
try {
signal = probeFeatures(source)
}
catch {
signal = undefined
}
}
// 构建缓存键:选项指纹 + 信号 + 内容哈希
const optsFp = getOptionsFingerprint(resolvedOptions)
const signalKey = signal ? signalToCacheKey(signal) : ''
const contentHash = simpleHash(source)
const cacheKey = `${optsFp}|${signalKey}|${contentHash}`
const cachedResult = resultCache.get(cacheKey)
if (cachedResult) {
return Promise.resolve(cloneOutput ? cloneResult(cachedResult) : cachedResult)
}
const processor = processorCache.getProcessor(resolvedOptions, signal)
const processOptions = processorCache.getProcessOptions(resolvedOptions)
return processor.process(
processInput,
processOptions,
).async().then((result) => {
const styleBranch = resolvePostcssFrameworkProfile(resolvedOptions)
let finalResult = styleBranch.postprocess(result, resolvedOptions)
if (resolvedOptions.isMainChunk !== false && finalResult.root) {
let removed = 0
let removedTotal = 0
do {
removed = removeEmptyBlockAtRules(finalResult.root)
removedTotal += removed
} while (removed > 0)
if (removedTotal > 0) {
const nextResult = finalResult.root.toResult(finalResult.opts)
nextResult.messages.push(...finalResult.messages)
finalResult = nextResult
}
}
if (protectedColorMix) {
const restoredCss = protectedColorMix.restore(finalResult.css)
if (restoredCss !== finalResult.css) {
const nextResult = finalResult.root.clone().toResult(finalResult.opts)
nextResult.css = restoredCss
nextResult.root = postcss.parse(restoredCss, finalResult.opts)
nextResult.messages.push(...finalResult.messages)
finalResult = nextResult
}
}
// 缓存最终结果
resultCache.set(cacheKey, finalResult)
return cloneOutput ? cloneResult(finalResult) : finalResult
})
}
const handler = ((rawSource: string, opt?: Partial<IStyleHandlerOptions>) => {
return processSource(rawSource, undefined, false, opt)
}) as StyleHandler
handler.transformRoot = (root, opt) => {
return processSource(root.toString(), root, true, opt)
}
handler.getPipeline = (opt?: Partial<IStyleHandlerOptions>) => {
const resolvedOptions = resolver.resolve(opt)
return processorCache.getPipeline(resolvedOptions)
}
return handler
}