@weapp-tailwindcss/lynx 0.4.4 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -3
- package/README.zh-CN.md +44 -6
- package/dist/decoder-data/AGENTS.md +31 -0
- package/dist/decoder-data/fixtures/selector-identifiers.json +159 -0
- package/dist/decoder-data/manifest.json +130 -0
- package/dist/decoder-data/patches/0001-decode-page-config-before-css.patch +33 -0
- package/dist/decoder-data/patches/0002-serialize-css-identifiers.patch +55 -0
- package/dist/decoder-data/rebuild/bootstrap-inputs.json +25 -0
- package/dist/decoder-data/rebuild/inputs.json +198 -0
- package/dist/decoder-data/vendor/.gitattributes +2 -0
- package/dist/decoder-data/vendor/README.md +11 -0
- package/dist/decoder-data/vendor/lepus.cjs.gz +0 -0
- package/dist/decoder-data/vendor/licenses/emsdk-upstream-emscripten-LICENSE.txt +102 -0
- package/dist/decoder-data/vendor/licenses/emsdk-upstream-emscripten-system-lib-compiler-rt-LICENSE.TXT.txt +311 -0
- package/dist/decoder-data/vendor/licenses/emsdk-upstream-emscripten-system-lib-libc-musl-COPYRIGHT.txt +193 -0
- package/dist/decoder-data/vendor/licenses/emsdk-upstream-emscripten-system-lib-libcxx-LICENSE.TXT.txt +311 -0
- package/dist/decoder-data/vendor/licenses/emsdk-upstream-emscripten-system-lib-libcxxabi-LICENSE.TXT.txt +311 -0
- package/dist/decoder-data/vendor/licenses/emsdk-upstream-emscripten-system-lib-libunwind-LICENSE.TXT.txt +311 -0
- package/dist/decoder-data/vendor/licenses/source-LICENSE.txt +203 -0
- package/dist/decoder-data/vendor/licenses/source-NOTICE.txt +4 -0
- package/dist/decoder-data/vendor/licenses/source-third_party-aes-LICENSE.txt +21 -0
- package/dist/decoder-data/vendor/licenses/source-third_party-double-conversion-LICENSE.txt +26 -0
- package/dist/decoder-data/vendor/licenses/source-third_party-jsoncpp-LICENSE.txt +55 -0
- package/dist/decoder-data/vendor/licenses/source-third_party-modp_b64-LICENSE.txt +33 -0
- package/dist/decoder-data/vendor/licenses/source-third_party-quickjs-LICENSE.txt +22 -0
- package/dist/decoder-data/vendor/licenses/source-third_party-quickjs-src-LICENSE.txt +202 -0
- package/dist/decoder-data/vendor/licenses/source-third_party-rapidjson-license.txt.txt +57 -0
- package/dist/decoder-data/vendor/reproducibility.json +63 -0
- package/dist/decoder.cjs +108 -0
- package/dist/decoder.d.cts +25 -0
- package/dist/decoder.d.ts +25 -0
- package/dist/decoder.js +101 -0
- package/dist/index.cjs +574 -8
- package/dist/index.d.cts +92 -0
- package/dist/index.d.ts +73 -3
- package/dist/index.js +572 -8
- package/dist/rolldown-runtime-CmQyd0Gu.cjs +27 -0
- package/package.json +30 -10
package/dist/index.cjs
CHANGED
|
@@ -1,28 +1,591 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("./rolldown-runtime-CmQyd0Gu.cjs");
|
|
2
3
|
let weapp_tailwindcss_rspack = require("weapp-tailwindcss/rspack");
|
|
3
|
-
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
5
|
+
let weapp_tailwindcss_core = require("weapp-tailwindcss/core");
|
|
6
|
+
let _jridgewell_trace_mapping = require("@jridgewell/trace-mapping");
|
|
7
|
+
let node_fs_promises = require("node:fs/promises");
|
|
8
|
+
let node_module = require("node:module");
|
|
9
|
+
let node_path = require("node:path");
|
|
10
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
11
|
+
let node_url = require("node:url");
|
|
12
|
+
// src/diagnostics/evidence.ts
|
|
13
|
+
const guardKey = (conditions) => JSON.stringify(conditions.map((condition) => condition.trim()));
|
|
14
|
+
const branchKey = (branch, conditions = branch.conditions) => JSON.stringify([branch.identity, guardKey(conditions)]);
|
|
15
|
+
function mayRepresent(property, actual) {
|
|
16
|
+
if (property === "all") return true;
|
|
17
|
+
const plain = (name) => name.replace(/^-(?:webkit|moz|ms|o)-/, "");
|
|
18
|
+
if (plain(property) === plain(actual)) return true;
|
|
19
|
+
if (actual.startsWith(`${property}-`) || property.startsWith(`${actual}-`)) return true;
|
|
20
|
+
return [
|
|
21
|
+
["border-", "border-"],
|
|
22
|
+
[
|
|
23
|
+
"gap",
|
|
24
|
+
"grid-gap",
|
|
25
|
+
"row-gap",
|
|
26
|
+
"column-gap",
|
|
27
|
+
"grid-row-gap",
|
|
28
|
+
"grid-column-gap"
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
"columns",
|
|
32
|
+
"column-width",
|
|
33
|
+
"column-count"
|
|
34
|
+
],
|
|
35
|
+
["word-wrap", "overflow-wrap"],
|
|
36
|
+
[
|
|
37
|
+
"inset",
|
|
38
|
+
"top",
|
|
39
|
+
"right",
|
|
40
|
+
"bottom",
|
|
41
|
+
"left"
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
"padding-inline",
|
|
45
|
+
"padding-left",
|
|
46
|
+
"padding-right"
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
"padding-block",
|
|
50
|
+
"padding-top",
|
|
51
|
+
"padding-bottom"
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"margin-inline",
|
|
55
|
+
"margin-left",
|
|
56
|
+
"margin-right"
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
"margin-block",
|
|
60
|
+
"margin-top",
|
|
61
|
+
"margin-bottom"
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
"place-items",
|
|
65
|
+
"align-items",
|
|
66
|
+
"justify-items"
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"place-content",
|
|
70
|
+
"align-content",
|
|
71
|
+
"justify-content"
|
|
72
|
+
],
|
|
73
|
+
[
|
|
74
|
+
"place-self",
|
|
75
|
+
"align-self",
|
|
76
|
+
"justify-self"
|
|
77
|
+
]
|
|
78
|
+
].some((group) => group.some((name) => name.endsWith("-") ? property.startsWith(name) : property === name) && group.some((name) => name.endsWith("-") ? actual.startsWith(name) : actual === name));
|
|
79
|
+
}
|
|
80
|
+
/** 每个模板只解析一次完整产物和输入分支,避免 warning 数量放大解析开销。 */
|
|
81
|
+
function createEncodingInspector(rules, evidence) {
|
|
82
|
+
const sources = /* @__PURE__ */ new Map();
|
|
83
|
+
const owners = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const ref of rules) try {
|
|
85
|
+
const branches = (0, weapp_tailwindcss_core.collectCssRuleEvidence)(`${ref.rule.selectorText?.value} { placeholder: 0; }`);
|
|
86
|
+
sources.set(ref, branches);
|
|
87
|
+
for (const branch of branches) {
|
|
88
|
+
const key = branchKey(branch, ref.conditions);
|
|
89
|
+
const refs = owners.get(key) ?? /* @__PURE__ */ new Set();
|
|
90
|
+
refs.add(ref);
|
|
91
|
+
owners.set(key, refs);
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
const output = /* @__PURE__ */ new Map();
|
|
95
|
+
const outputClasses = /* @__PURE__ */ new Set();
|
|
96
|
+
let verified = false;
|
|
97
|
+
let error = evidence?.error ?? "缺少本轮 bundle 身份或完整 decoder 文本。";
|
|
98
|
+
if (evidence?.cssText !== void 0 && evidence.bundleSha256 && evidence.decoderIdentity) try {
|
|
99
|
+
const bundled = (0, weapp_tailwindcss_core.collectCssRuleEvidence)(evidence.cssText.replace(/\{\{(--[a-z0-9-]+)\}\}/gi, "var($1)"));
|
|
100
|
+
for (const branch of bundled) {
|
|
101
|
+
const key = branchKey(branch);
|
|
102
|
+
output.set(key, [...output.get(key) ?? [], branch]);
|
|
103
|
+
branch.classes.forEach((name) => outputClasses.add(name));
|
|
104
|
+
}
|
|
105
|
+
verified = true;
|
|
106
|
+
error = void 0;
|
|
107
|
+
} catch {}
|
|
108
|
+
return {
|
|
109
|
+
error,
|
|
110
|
+
classes: (ref) => sources.get(ref)?.flatMap((branch) => branch.classes) ?? [],
|
|
111
|
+
status(ref, declaration) {
|
|
112
|
+
const branches = sources.get(ref);
|
|
113
|
+
if (!verified || ref.uncertain || !branches?.length) return "unknown";
|
|
114
|
+
if (branches.some((branch) => owners.get(branchKey(branch, ref.conditions))?.size !== 1)) return "unknown";
|
|
115
|
+
let expected;
|
|
116
|
+
if (declaration) {
|
|
117
|
+
if ((ref.rule.style ?? []).filter((item) => item.name === declaration.name).length !== 1) return "unknown";
|
|
118
|
+
try {
|
|
119
|
+
const parsed = (0, weapp_tailwindcss_core.collectCssRuleEvidence)(`.input { value: ${declaration.value}; }`)[0]?.declarations;
|
|
120
|
+
if (parsed?.length !== 1 || parsed[0]?.property !== "value") return "unknown";
|
|
121
|
+
expected = {
|
|
122
|
+
value: parsed[0].value,
|
|
123
|
+
important: !!declaration.important || parsed[0].important
|
|
124
|
+
};
|
|
125
|
+
} catch {
|
|
126
|
+
return "unknown";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const states = branches.map((branch) => {
|
|
130
|
+
const matches = output.get(branchKey(branch, ref.conditions)) ?? [];
|
|
131
|
+
if (!matches.length) return branch.classes.some((name) => outputClasses.has(name)) ? "unknown" : "encoder-rejected";
|
|
132
|
+
if (!declaration) return "bundled";
|
|
133
|
+
const declarations = matches.flatMap((item) => item.declarations).filter((item) => item.property === declaration.name);
|
|
134
|
+
if (!declarations.length) return matches.some((item) => item.declarations.some((other) => mayRepresent(declaration.name ?? "", other.property))) ? "unknown" : "encoder-rejected";
|
|
135
|
+
if (declarations.length !== 1) return "unknown";
|
|
136
|
+
return declarations[0]?.value === expected?.value && declarations[0]?.important === expected?.important ? "bundled" : "unknown";
|
|
137
|
+
});
|
|
138
|
+
if (states.includes("encoder-rejected")) return "encoder-rejected";
|
|
139
|
+
return states.every((state) => state === "bundled") ? "bundled" : "unknown";
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// src/diagnostics/guidance.ts
|
|
144
|
+
/** 只说明可观察的删除证据;替代建议不承诺未经目标端验证的等价性。 */
|
|
145
|
+
function diagnosticGuidance(diagnostic) {
|
|
146
|
+
if (diagnostic.status === "unknown") return {
|
|
147
|
+
reason: diagnostic.reason ?? "编码证据不足,无法确认该输入声明的最终效果。",
|
|
148
|
+
suggestion: "先补齐唯一来源、实际 bundle 和目标端证据;当前没有已验证的等价替代建议。"
|
|
149
|
+
};
|
|
150
|
+
if (diagnostic.status === "bundled") return {
|
|
151
|
+
reason: "该输入规则仍存在于本轮实际 bundle;warning 不能证明删除。",
|
|
152
|
+
suggestion: "保留声明,继续验证目标宿主实际效果。"
|
|
153
|
+
};
|
|
154
|
+
let suggestion = "缩小为单条业务样式,在目标宿主确认可用实现;当前没有已验证的等价替代建议。";
|
|
155
|
+
if (diagnostic.property === "padding-inline" || diagnostic.property === "margin-inline") {
|
|
156
|
+
const prefix = diagnostic.property.startsWith("padding") ? "padding" : "margin";
|
|
157
|
+
suggestion = `可按业务方向选择 ${prefix}-left / ${prefix}-right;这不是完整逻辑属性等价替换,RTL 与动态方向须分别验证。`;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
reason: "唯一输入与本轮实际 bundle 比较确认该规则或声明缺失;这不推断 encoder 内部拒绝根因。",
|
|
161
|
+
suggestion
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// src/diagnostics/input.ts
|
|
165
|
+
function isRecord(value) {
|
|
166
|
+
return value !== null && typeof value === "object";
|
|
167
|
+
}
|
|
168
|
+
function parseRawDiagnostics(value) {
|
|
169
|
+
if (value === void 0 || value === null || value === "") return [];
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
173
|
+
} catch {
|
|
174
|
+
return [{ raw: value }];
|
|
175
|
+
}
|
|
176
|
+
return (Array.isArray(parsed) ? parsed : [parsed]).map((raw) => {
|
|
177
|
+
if (!isRecord(raw)) return { raw };
|
|
178
|
+
const source = isRecord(raw.source) ? raw.source : {};
|
|
179
|
+
return {
|
|
180
|
+
raw,
|
|
181
|
+
...typeof raw.type === "string" ? { type: raw.type.toLowerCase() } : {},
|
|
182
|
+
...typeof raw.name === "string" ? { name: raw.name } : {},
|
|
183
|
+
...typeof raw.module === "string" ? { module: raw.module } : {},
|
|
184
|
+
...typeof raw.file === "string" ? { file: raw.file } : typeof source.file === "string" ? { file: source.file } : {},
|
|
185
|
+
...Number.isSafeInteger(raw.line) && Number(raw.line) > 0 ? { line: Number(raw.line) } : {},
|
|
186
|
+
...Number.isSafeInteger(raw.column) && Number(raw.column) > 0 ? { column: Number(raw.column) } : {}
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function flattenRules(rules, module, conditions = [], uncertain = false) {
|
|
191
|
+
return (rules ?? []).flatMap((rule) => {
|
|
192
|
+
const guard = rule.type === "MediaRule" ? `@media ${rule.prelude?.value}` : void 0;
|
|
193
|
+
const next = guard ? [...conditions, guard] : conditions;
|
|
194
|
+
const unsupportedParent = uncertain || rule.type !== "StyleRule" && rule.type !== "MediaRule";
|
|
195
|
+
return [...rule.type === "StyleRule" ? [{
|
|
196
|
+
module,
|
|
197
|
+
rule,
|
|
198
|
+
conditions,
|
|
199
|
+
uncertain
|
|
200
|
+
}] : [], ...flattenRules(rule.rules, module, next, unsupportedParent)];
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function exactLocation(location, raw) {
|
|
204
|
+
return !!raw.line && !!raw.column && location?.line === raw.line && location?.column === raw.column;
|
|
205
|
+
}
|
|
206
|
+
/** 只接受唯一、名称和位置同时匹配的输入;模块缺失不回退到其他入口。 */
|
|
207
|
+
function matchInput(rules, raw, input) {
|
|
208
|
+
if (!raw.name || !["property", "selector"].includes(raw.type ?? "")) return;
|
|
209
|
+
const matches = rules.flatMap((ref) => {
|
|
210
|
+
if (raw.module && raw.module !== ref.module && raw.module !== input.cssSource?.[ref.module]) return [];
|
|
211
|
+
if (raw.file && raw.file !== ref.module && raw.file !== input.cssSource?.[ref.module]) return [];
|
|
212
|
+
if (raw.type === "property") return (ref.rule.style ?? []).flatMap((declaration) => declaration.name === raw.name && (exactLocation(declaration.keyLoc, raw) || exactLocation(declaration.valLoc, raw)) ? [{
|
|
213
|
+
ref,
|
|
214
|
+
declaration
|
|
215
|
+
}] : []);
|
|
216
|
+
try {
|
|
217
|
+
return ref.rule.selectorText?.value && (0, weapp_tailwindcss_core.cssSelectorDiagnosticMatches)(ref.rule.selectorText.value, raw.name) && exactLocation(ref.rule.selectorText.loc, raw) ? [{
|
|
218
|
+
ref,
|
|
219
|
+
declaration: void 0
|
|
220
|
+
}] : [];
|
|
221
|
+
} catch {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
226
|
+
}
|
|
227
|
+
// src/diagnostics/origins.ts
|
|
228
|
+
function cssInputIdentity(css) {
|
|
229
|
+
if (!isRecord(css)) return;
|
|
230
|
+
return (0, node_crypto.createHash)("sha256").update(JSON.stringify([css.cssMap, css.cssSource])).digest("hex");
|
|
231
|
+
}
|
|
232
|
+
function readAssets(chunks, getAsset) {
|
|
233
|
+
if (!Array.isArray(chunks)) return [];
|
|
234
|
+
return chunks.map((asset) => {
|
|
235
|
+
const source = asset.source.source();
|
|
236
|
+
let map = asset.source.map?.() ?? null;
|
|
237
|
+
let mapAsset;
|
|
238
|
+
const related = asset.info?.related?.sourceMap;
|
|
239
|
+
if (!map && typeof related === "string") {
|
|
240
|
+
const external = getAsset?.(related)?.source.source();
|
|
241
|
+
if (external !== void 0) {
|
|
242
|
+
const text = typeof external === "string" ? external : new TextDecoder().decode(external);
|
|
243
|
+
map = JSON.parse(text);
|
|
244
|
+
mapAsset = {
|
|
245
|
+
name: related,
|
|
246
|
+
text
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
name: asset.name,
|
|
252
|
+
text: typeof source === "string" ? source : new TextDecoder().decode(source),
|
|
253
|
+
map: structuredClone(map),
|
|
254
|
+
mapAsset
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/** 保存当前构建图的数据,禁止为诊断重新扫描源码或重复执行 CSS 插件。 */
|
|
259
|
+
function captureCssSnapshot(css, getAsset) {
|
|
260
|
+
if (!isRecord(css) || !(css.contentMap instanceof Map)) return;
|
|
261
|
+
const identity = cssInputIdentity(css);
|
|
262
|
+
if (!identity) return;
|
|
263
|
+
try {
|
|
264
|
+
return {
|
|
265
|
+
identity,
|
|
266
|
+
contents: new Map([...css.contentMap].map(([id, segments]) => [String(id), [...segments]])),
|
|
267
|
+
assets: readAssets(css.chunks, getAsset)
|
|
268
|
+
};
|
|
269
|
+
} catch {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function snapshotMatches(snapshot, css, chunks, getAsset) {
|
|
274
|
+
try {
|
|
275
|
+
return snapshot.identity === cssInputIdentity(css) && JSON.stringify(snapshot.assets) === JSON.stringify(readAssets(chunks, getAsset));
|
|
276
|
+
} catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function offsetOf(text, line, column) {
|
|
281
|
+
const lines = text.split("\n");
|
|
282
|
+
const row = lines[line - 1];
|
|
283
|
+
if (row === void 0 || column < 1 || column > row.length + 1) return;
|
|
284
|
+
return lines.slice(0, line - 1).reduce((size, value) => size + value.length + 1, 0) + column - 1;
|
|
285
|
+
}
|
|
286
|
+
/** 先撤销 cssId 分段拼接的偏移,唯一绑定 asset 后才消费其 source map。 */
|
|
287
|
+
function mapCssOrigin(snapshot, module, line, column) {
|
|
288
|
+
if (!snapshot || !line || !column) return;
|
|
289
|
+
const segments = snapshot.contents.get(module);
|
|
290
|
+
if (!segments) return;
|
|
291
|
+
const offset = offsetOf(segments.join("\n"), line, column);
|
|
292
|
+
if (offset === void 0) return;
|
|
293
|
+
let start = 0;
|
|
294
|
+
for (const segment of segments) {
|
|
295
|
+
if (offset >= start && offset < start + segment.length) {
|
|
296
|
+
const matches = snapshot.assets.flatMap((asset) => {
|
|
297
|
+
const index = asset.text.indexOf(segment);
|
|
298
|
+
return index >= 0 && !asset.text.includes(segment, index + 1) ? [{
|
|
299
|
+
asset,
|
|
300
|
+
index
|
|
301
|
+
}] : [];
|
|
302
|
+
});
|
|
303
|
+
if (matches.length !== 1 || !matches[0]?.asset.map) return;
|
|
304
|
+
const { asset, index } = matches[0];
|
|
305
|
+
const prefix = asset.text.slice(0, index + offset - start).split("\n");
|
|
306
|
+
try {
|
|
307
|
+
const original = (0, _jridgewell_trace_mapping.originalPositionFor)(new _jridgewell_trace_mapping.TraceMap(asset.map), {
|
|
308
|
+
line: prefix.length,
|
|
309
|
+
column: prefix.at(-1).length
|
|
310
|
+
});
|
|
311
|
+
return original.source && original.line !== null && original.column !== null ? {
|
|
312
|
+
file: original.source,
|
|
313
|
+
line: original.line,
|
|
314
|
+
column: original.column + 1,
|
|
315
|
+
asset: asset.name
|
|
316
|
+
} : void 0;
|
|
317
|
+
} catch {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
start += segment.length + 1;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// src/diagnostics/versions.ts
|
|
325
|
+
/** 只关联与宿主暴露类身份一致的构建器依赖;不把目标 SDK 当作宿主运行版本。 */
|
|
326
|
+
async function installedEncoderVersion(root, template) {
|
|
327
|
+
try {
|
|
328
|
+
if (!root) throw new Error("构建器未提供项目根目录");
|
|
329
|
+
const project = (0, node_module.createRequire)(node_path.default.join(root, "package.json"));
|
|
330
|
+
const file = (0, node_module.createRequire)(project.resolve("@lynx-js/react-rsbuild-plugin/package.json")).resolve("@lynx-js/template-webpack-plugin/package.json");
|
|
331
|
+
const entry = JSON.parse(await (0, node_fs_promises.readFile)(file, "utf8")).exports?.["."]?.import;
|
|
332
|
+
if (typeof entry !== "string" || !entry.startsWith("./")) throw new Error("模板包缺少明确的 ESM 导出");
|
|
333
|
+
if ((await import((0, node_url.pathToFileURL)(node_path.default.resolve(node_path.default.dirname(file), entry)).href)).LynxTemplatePlugin !== template) throw new Error("已解析模板类与宿主暴露类身份不一致");
|
|
334
|
+
const encoderFile = (0, node_module.createRequire)(file).resolve("@lynx-js/tasm/package.json");
|
|
335
|
+
const encoder = JSON.parse(await (0, node_fs_promises.readFile)(encoderFile, "utf8"));
|
|
336
|
+
if (encoder.name !== "@lynx-js/tasm" || typeof encoder.version !== "string" || !encoder.version.trim()) throw new Error("编码包版本元数据无效");
|
|
337
|
+
return {
|
|
338
|
+
version: encoder.version,
|
|
339
|
+
source: "@lynx-js/tasm:从已绑定模板构建器的依赖解析;未独立确认实际 encode hook 或执行二进制,不是宿主引擎版本。"
|
|
340
|
+
};
|
|
341
|
+
} catch (error) {
|
|
342
|
+
return { source: `编码器版本未确认:${error instanceof Error ? error.message : String(error)}` };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function diagnosticVersions(options, encoder) {
|
|
346
|
+
const declared = (key) => typeof options?.[key] === "string" ? options[key] : void 0;
|
|
347
|
+
return {
|
|
348
|
+
versions: {
|
|
349
|
+
encoder: encoder?.version ?? declared("encoderVersion") ?? "unknown",
|
|
350
|
+
engine: declared("engineVersion") ?? "unknown",
|
|
351
|
+
targetSdk: declared("targetSdkVersion")
|
|
352
|
+
},
|
|
353
|
+
versionEvidence: {
|
|
354
|
+
encoder: encoder?.version ? encoder.source : [encoder?.source, declared("encoderVersion") ? "版本来自 compilerOptions 显式声明;未独立验证执行二进制。" : "编码器未提供可关联的版本证据。"].filter(Boolean).join(" "),
|
|
355
|
+
engine: declared("engineVersion") ? "compilerOptions 显式声明;宿主运行版本仍须设备验收。" : "构建不能推断宿主运行引擎版本;本轮未提供。"
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
// src/diagnostics.ts
|
|
360
|
+
function classifyRule(ref, classes, property) {
|
|
361
|
+
const selector = ref.rule.selectorText?.value ?? "";
|
|
362
|
+
if (classes.length > 0) return "business";
|
|
363
|
+
if (property?.startsWith("--tw-") && Object.keys(ref.rule.variables ?? {}).every((name) => name.startsWith("--tw-")) && (ref.rule.style ?? []).every((declaration) => declaration.name?.startsWith("--tw-"))) return "tailwind-default";
|
|
364
|
+
const defaults = ":root, :host, *, ::before, ::after, ::backdrop, ::file-selector-button";
|
|
365
|
+
try {
|
|
366
|
+
const branches = (0, weapp_tailwindcss_core.collectCssRuleEvidence)(`${selector} { placeholder: 0; }`);
|
|
367
|
+
if (branches.length && branches.every((branch) => (0, weapp_tailwindcss_core.cssSelectorDiagnosticMatches)(defaults, branch.selector))) return "preflight";
|
|
368
|
+
} catch {}
|
|
369
|
+
return "unknown";
|
|
370
|
+
}
|
|
371
|
+
function ignoredBy(diagnostic, ignores = []) {
|
|
372
|
+
const rule = ignores.find((ignore) => (ignore.selector || ignore.property) && (!ignore.selector || ignore.selector === diagnostic.selector) && (!ignore.property || ignore.property === diagnostic.property));
|
|
373
|
+
return rule ? {
|
|
374
|
+
reason: rule.reason ?? "按显式 selector/property 配置忽略失败;原诊断仍保留。",
|
|
375
|
+
rule
|
|
376
|
+
} : void 0;
|
|
377
|
+
}
|
|
378
|
+
function createLynxDiagnosticsReport(rawDiagnostics, css, options = {}, compilerOptions, evidence, origins, encoder) {
|
|
379
|
+
const input = isRecord(css) ? css : {};
|
|
380
|
+
const rules = Object.entries(input.cssMap ?? {}).flatMap(([module, moduleRules]) => flattenRules(moduleRules, module));
|
|
381
|
+
const inspector = createEncodingInspector(rules, evidence);
|
|
382
|
+
const diagnostics = [];
|
|
383
|
+
const handled = /* @__PURE__ */ new Map();
|
|
384
|
+
const seen = /* @__PURE__ */ new Set();
|
|
385
|
+
for (const raw of parseRawDiagnostics(rawDiagnostics)) {
|
|
386
|
+
const key = JSON.stringify(raw.raw);
|
|
387
|
+
if (seen.has(key)) continue;
|
|
388
|
+
seen.add(key);
|
|
389
|
+
const match = matchInput(rules, raw, input);
|
|
390
|
+
let status = "unknown";
|
|
391
|
+
let category = "unknown";
|
|
392
|
+
let classes = [];
|
|
393
|
+
if (match) {
|
|
394
|
+
if (raw.type === "property" && handled.get(match.ref) !== "selector") {
|
|
395
|
+
const declarations = handled.get(match.ref) ?? /* @__PURE__ */ new Set();
|
|
396
|
+
declarations.add(match.declaration);
|
|
397
|
+
handled.set(match.ref, declarations);
|
|
398
|
+
}
|
|
399
|
+
classes = inspector.classes(match.ref);
|
|
400
|
+
status = inspector.status(match.ref, match.declaration);
|
|
401
|
+
if (raw.type === "selector" && status === "encoder-rejected") handled.set(match.ref, "selector");
|
|
402
|
+
if (status !== "unknown") category = classifyRule(match.ref, classes, raw.type === "property" ? raw.name : void 0);
|
|
403
|
+
}
|
|
404
|
+
const origin = match ? mapCssOrigin(origins, match.ref.module, raw.line, raw.column) : void 0;
|
|
405
|
+
const diagnostic = {
|
|
406
|
+
origin: "encoder-log",
|
|
407
|
+
raw: raw.raw,
|
|
408
|
+
status,
|
|
409
|
+
category,
|
|
410
|
+
message: status === "encoder-rejected" ? `encoder 产物确证删除 ${raw.type}:${raw.name}` : status === "bundled" ? `encoder warning 对应规则仍保留:${raw.name}` : "encoder 诊断缺少唯一输入关联或可信编码后证明。",
|
|
411
|
+
...match ? {
|
|
412
|
+
selector: match.ref.rule.selectorText?.value,
|
|
413
|
+
utility: classes[0],
|
|
414
|
+
property: raw.type === "property" ? raw.name : void 0,
|
|
415
|
+
value: match.declaration?.value
|
|
416
|
+
} : {},
|
|
417
|
+
source: {
|
|
418
|
+
module: match?.ref.module ?? raw.module,
|
|
419
|
+
file: origin?.file,
|
|
420
|
+
line: origin?.line,
|
|
421
|
+
column: origin?.column,
|
|
422
|
+
generated: {
|
|
423
|
+
line: raw.line,
|
|
424
|
+
column: raw.column
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
reason: status === "unknown" ? evidence?.error ?? "来源歧义、未知日志或编码证据不足;不推断删除。" : void 0
|
|
428
|
+
};
|
|
429
|
+
Object.assign(diagnostic, diagnosticGuidance(diagnostic));
|
|
430
|
+
const ignored = ignoredBy(diagnostic, options.ignore);
|
|
431
|
+
if (ignored) diagnostic.ignored = ignored;
|
|
432
|
+
diagnostics.push(diagnostic);
|
|
433
|
+
}
|
|
434
|
+
if (inspector.error && !diagnostics.some((item) => item.status === "unknown")) diagnostics.push({
|
|
435
|
+
origin: "bundle-comparison",
|
|
436
|
+
status: "unknown",
|
|
437
|
+
category: "unknown",
|
|
438
|
+
message: "本轮模板缺少可验证的完整编码后证据。",
|
|
439
|
+
reason: inspector.error,
|
|
440
|
+
suggestion: "先补齐本轮实际 bundle 的解码证据,再判断兼容性;当前不提供未经验证的样式替代。"
|
|
441
|
+
});
|
|
442
|
+
for (const ref of rules) {
|
|
443
|
+
const classes = inspector.classes(ref);
|
|
444
|
+
const covered = handled.get(ref);
|
|
445
|
+
if (!classes.length || covered === "selector") continue;
|
|
446
|
+
for (const declaration of ref.rule.style ?? []) {
|
|
447
|
+
const status = inspector.status(ref, declaration);
|
|
448
|
+
if (covered?.has(declaration) || status === "bundled" || inspector.error) continue;
|
|
449
|
+
const location = declaration.keyLoc;
|
|
450
|
+
const origin = mapCssOrigin(origins, ref.module, location?.line, location?.column);
|
|
451
|
+
const diagnostic = {
|
|
452
|
+
origin: "bundle-comparison",
|
|
453
|
+
status,
|
|
454
|
+
category: "business",
|
|
455
|
+
message: status === "encoder-rejected" ? `编码输入声明在实际 bundle 中缺失:${declaration.name}` : `编码声明缺少独立等价证明:${declaration.name}`,
|
|
456
|
+
reason: status === "unknown" ? "来源歧义、重复声明或编码表示变化,尚无独立等价 oracle。" : void 0,
|
|
457
|
+
selector: ref.rule.selectorText?.value,
|
|
458
|
+
utility: classes[0],
|
|
459
|
+
property: declaration.name,
|
|
460
|
+
value: declaration.value,
|
|
461
|
+
source: {
|
|
462
|
+
module: ref.module,
|
|
463
|
+
file: origin?.file,
|
|
464
|
+
line: origin?.line,
|
|
465
|
+
column: origin?.column,
|
|
466
|
+
generated: location
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
Object.assign(diagnostic, diagnosticGuidance(diagnostic));
|
|
470
|
+
const ignored = ignoredBy(diagnostic, options.ignore);
|
|
471
|
+
if (ignored) diagnostic.ignored = ignored;
|
|
472
|
+
diagnostics.push(diagnostic);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const { cssText: _cssText, ...identity } = evidence ?? {};
|
|
476
|
+
return {
|
|
477
|
+
schemaVersion: 1,
|
|
478
|
+
counts: {
|
|
479
|
+
generated: rules.length,
|
|
480
|
+
encoderRejected: diagnostics.filter((item) => item.status === "encoder-rejected").length,
|
|
481
|
+
unknown: diagnostics.filter((item) => item.status === "unknown").length
|
|
482
|
+
},
|
|
483
|
+
diagnostics,
|
|
484
|
+
...evidence ? { evidence: identity } : {},
|
|
485
|
+
...diagnosticVersions(compilerOptions, encoder)
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function hasStrictLynxDiagnosticFailure(report, options = {}) {
|
|
489
|
+
return options.strict === true && report.diagnostics.some((item) => item.status === "encoder-rejected" && item.category === "business" && !item.ignored);
|
|
490
|
+
}
|
|
491
|
+
function formatLynxDiagnosticsSummary(report) {
|
|
492
|
+
return `Lynx CSS 诊断:生成 ${report.counts.generated} 条,encoder 删除 ${report.counts.encoderRejected} 条,未知 ${report.counts.unknown} 条。`;
|
|
493
|
+
}
|
|
494
|
+
// src/css-variables.ts
|
|
4
495
|
/** 通过模板编码生命周期启用原生递归变量解析,保留显式配置及动态依赖。 */
|
|
5
496
|
var LynxCssVariablesPlugin = class {
|
|
6
497
|
templatePlugin;
|
|
7
|
-
|
|
498
|
+
diagnostics;
|
|
499
|
+
root;
|
|
500
|
+
constructor(templatePlugin, diagnostics = {}, root) {
|
|
8
501
|
this.templatePlugin = templatePlugin;
|
|
502
|
+
this.diagnostics = diagnostics;
|
|
503
|
+
this.root = root;
|
|
9
504
|
}
|
|
10
505
|
apply(compiler) {
|
|
11
506
|
const name = "weapp-tailwindcss:lynx-css-variables";
|
|
507
|
+
const pending = /* @__PURE__ */ new WeakMap();
|
|
508
|
+
if (!compiler.hooks.afterEmit?.tapPromise && (this.diagnostics.strict || this.diagnostics.onReport)) throw new Error("Lynx compiler afterEmit hook 不可用,不能验证实际发出的模板。");
|
|
509
|
+
compiler.hooks.afterEmit?.tapPromise(name, async (compilation) => {
|
|
510
|
+
for (const { args, origins } of pending.get(compilation) ?? []) await this.reportEmittedTemplate(compilation, args, origins);
|
|
511
|
+
pending.delete(compilation);
|
|
512
|
+
});
|
|
12
513
|
compiler.hooks.thisCompilation.tap(name, (compilation) => {
|
|
13
|
-
this.templatePlugin.getLynxTemplatePluginHooks(compilation)
|
|
514
|
+
const hooks = this.templatePlugin.getLynxTemplatePluginHooks(compilation);
|
|
515
|
+
const getAsset = compilation.getAsset?.bind(compilation);
|
|
516
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
517
|
+
hooks.beforeEncode.tap(name, (args) => {
|
|
14
518
|
const config = args.encodeData.sourceContent.config;
|
|
15
519
|
if (config.enableCSSInlineVariables === void 0) config.enableCSSInlineVariables = true;
|
|
520
|
+
const snapshot = captureCssSnapshot(args.encodeData.css, getAsset);
|
|
521
|
+
if (snapshot) {
|
|
522
|
+
const previous = snapshots.get(snapshot.identity);
|
|
523
|
+
snapshots.set(snapshot.identity, snapshots.has(snapshot.identity) && (previous === null || JSON.stringify([previous?.assets, [...previous?.contents ?? []]]) !== JSON.stringify([snapshot.assets, [...snapshot.contents]])) ? null : snapshot);
|
|
524
|
+
}
|
|
525
|
+
return args;
|
|
526
|
+
});
|
|
527
|
+
if (!hooks.beforeEmit?.tapPromise && (this.diagnostics.strict || this.diagnostics.onReport)) throw new Error("Lynx beforeEmit 异步模板 hook 不可用,不能生成可信 CSS 诊断。");
|
|
528
|
+
hooks.beforeEmit?.tapPromise(name, async (args) => {
|
|
529
|
+
const identity = cssInputIdentity(args.finalEncodeOptions.css);
|
|
530
|
+
const snapshot = identity ? snapshots.get(identity) : void 0;
|
|
531
|
+
const origins = snapshot && snapshotMatches(snapshot, args.finalEncodeOptions.css, args.cssChunks, getAsset) ? snapshot : void 0;
|
|
532
|
+
const entries = pending.get(compilation) ?? [];
|
|
533
|
+
entries.push({
|
|
534
|
+
args: structuredClone({
|
|
535
|
+
outputName: args.outputName,
|
|
536
|
+
cssDiagnostics: args.cssDiagnostics,
|
|
537
|
+
template: /* @__PURE__ */ new Uint8Array(),
|
|
538
|
+
finalEncodeOptions: {
|
|
539
|
+
css: args.finalEncodeOptions.css,
|
|
540
|
+
compilerOptions: args.finalEncodeOptions.compilerOptions,
|
|
541
|
+
customSections: args.finalEncodeOptions.customSections
|
|
542
|
+
}
|
|
543
|
+
}),
|
|
544
|
+
origins
|
|
545
|
+
});
|
|
546
|
+
pending.set(compilation, entries);
|
|
16
547
|
return args;
|
|
17
548
|
});
|
|
18
549
|
});
|
|
19
550
|
}
|
|
551
|
+
async reportEmittedTemplate(compilation, args, origins) {
|
|
552
|
+
let evidence;
|
|
553
|
+
evidence = {
|
|
554
|
+
outputName: args.outputName,
|
|
555
|
+
inputCssSha256: cssInputIdentity(args.finalEncodeOptions.css)
|
|
556
|
+
};
|
|
557
|
+
try {
|
|
558
|
+
const emitted = compilation.getAsset?.(args.outputName)?.source.source();
|
|
559
|
+
if (!(emitted instanceof Uint8Array) || emitted.byteLength === 0) throw new Error("构建图缺少本轮已发出的 template 字节");
|
|
560
|
+
evidence.bundleSha256 = (0, node_crypto.createHash)("sha256").update(emitted).digest("hex");
|
|
561
|
+
const sections = args.finalEncodeOptions.customSections;
|
|
562
|
+
if (isRecord(sections) && Object.values(sections).some((section) => isRecord(section) && section.encoding === "CSS")) throw new Error("固定 decoder 尚不能证明独立 CSS custom section 的完整性");
|
|
563
|
+
const { decodeLynxBundle, lynxDecoderIdentity } = await import("@weapp-tailwindcss/lynx/decoder");
|
|
564
|
+
const decoded = await decodeLynxBundle(emitted);
|
|
565
|
+
evidence = {
|
|
566
|
+
outputName: args.outputName,
|
|
567
|
+
bundleSha256: (0, node_crypto.createHash)("sha256").update(emitted).digest("hex"),
|
|
568
|
+
decoderIdentity: lynxDecoderIdentity(),
|
|
569
|
+
decodedCssSha256: (0, node_crypto.createHash)("sha256").update(decoded.css.text).digest("hex"),
|
|
570
|
+
cssText: decoded.css.text,
|
|
571
|
+
inputCssSha256: cssInputIdentity(args.finalEncodeOptions.css)
|
|
572
|
+
};
|
|
573
|
+
} catch (error) {
|
|
574
|
+
evidence.error = error instanceof Error ? error.message : String(error);
|
|
575
|
+
}
|
|
576
|
+
const report = createLynxDiagnosticsReport(args.cssDiagnostics, args.finalEncodeOptions.css, this.diagnostics, args.finalEncodeOptions.compilerOptions, evidence, origins, await installedEncoderVersion(this.root, this.templatePlugin));
|
|
577
|
+
this.diagnostics.onReport?.(report);
|
|
578
|
+
if (report.diagnostics.length > 0) {
|
|
579
|
+
const summary = new Error(formatLynxDiagnosticsSummary(report));
|
|
580
|
+
if (hasStrictLynxDiagnosticFailure(report, this.diagnostics)) compilation.errors?.push(summary);
|
|
581
|
+
else compilation.warnings?.push(summary);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
20
584
|
};
|
|
21
|
-
|
|
22
|
-
//#region src/index.ts
|
|
585
|
+
// src/index.ts
|
|
23
586
|
const PLUGIN_NAME = "weapp-tailwindcss:lynx";
|
|
24
587
|
function normalizeOptions(options) {
|
|
25
|
-
const { rspack: _rspack, generator, ...rest } = options;
|
|
588
|
+
const { rspack: _rspack, diagnostics: _diagnostics, generator, ...rest } = options;
|
|
26
589
|
const generatorOptions = generator === false ? {} : generator;
|
|
27
590
|
return {
|
|
28
591
|
...rest,
|
|
@@ -76,13 +639,16 @@ function pluginLynxTailwindcss(options = {}) {
|
|
|
76
639
|
const templatePlugin = rsbuildApi.useExposed?.(Symbol.for("LynxTemplatePlugin"))?.LynxTemplatePlugin;
|
|
77
640
|
if (!templatePlugin?.getLynxTemplatePluginHooks) throw new Error("Lynx 模板编码 hook 不可用;请注册或升级 ReactLynx 构建插件(至少 0.12.4),确认其提供 LynxTemplatePlugin API。");
|
|
78
641
|
chain.plugin(PLUGIN_NAME).use(weapp_tailwindcss_rspack.WeappTailwindcss, [normalizedOptions]);
|
|
79
|
-
chain.plugin(`${PLUGIN_NAME}:css-variables`).use(LynxCssVariablesPlugin, [
|
|
642
|
+
chain.plugin(`${PLUGIN_NAME}:css-variables`).use(LynxCssVariablesPlugin, [
|
|
643
|
+
templatePlugin,
|
|
644
|
+
options.diagnostics ?? {},
|
|
645
|
+
rsbuildApi.context?.rootPath
|
|
646
|
+
]);
|
|
80
647
|
return chain;
|
|
81
648
|
});
|
|
82
649
|
rsbuildApi.modifyRspackConfig((config) => (0, weapp_tailwindcss_rspack.patchRspackConfig)(config, normalizeRspackOptions(options.rspack)));
|
|
83
650
|
}
|
|
84
651
|
};
|
|
85
652
|
}
|
|
86
|
-
//#endregion
|
|
87
653
|
exports.PLUGIN_NAME = PLUGIN_NAME;
|
|
88
654
|
exports.pluginLynxTailwindcss = pluginLynxTailwindcss;
|