@weapp-tailwindcss/react-native 0.2.4 → 0.2.6
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 +9 -0
- package/README.zh-CN.md +9 -0
- package/dist/compiler.cjs +85 -9
- package/dist/compiler.d.ts +2 -1
- package/dist/compiler.js +85 -10
- package/dist/env.cjs +0 -1
- package/dist/env.d.ts +0 -1
- package/dist/env.js +0 -1
- package/dist/metro-transformer.cjs +32 -15
- package/dist/metro-transformer.js +32 -16
- package/dist/metro.cjs +63 -4
- package/dist/metro.d.ts +13 -1
- package/dist/metro.js +61 -5
- package/dist/tailwind.cjs +10 -4
- package/dist/tailwind.d.ts +4 -0
- package/dist/tailwind.js +9 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,12 +27,19 @@ module.exports = withWeappTailwindcss(config, {
|
|
|
27
27
|
|
|
28
28
|
Metro scans sources, builds the exact candidate set and manifest, and connects the Babel JSX transform to the existing Expo transformer. You do not need to maintain a second `classNameSet`.
|
|
29
29
|
|
|
30
|
+
`withWeappTailwindcss` accepts `projectRoot`, `input`, `css`, `manifest`, `classSet`, `sourceGlobs`, and `watchFiles`. See the [React Native / Expo configuration reference](https://tw.icebreaker.top/docs/config/react-native) for the full field list, manifest shape, and non-Expo Metro usage.
|
|
31
|
+
|
|
32
|
+
On Android and iOS, the Metro integration resolves `react` and `react-native` from the app `projectRoot`. This keeps linked workspace packages from bundling a second React Native runtime that does not match the native binary. Expo's normal platform resolver remains unchanged on Web.
|
|
33
|
+
|
|
30
34
|
## Runtime model
|
|
31
35
|
|
|
32
36
|
- Static `className` values compile to pre-generated StyleSheet lookups without calling `tw()` during render.
|
|
33
37
|
- Dynamic class values are resolved through `tw(value)` at runtime.
|
|
34
38
|
- Regular inline `style` overrides Tailwind classes, while `!important` classes override inline styles.
|
|
35
39
|
- Unsupported CSS declarations are recorded in manifest `warnings` instead of producing incorrect native styles silently.
|
|
40
|
+
- The compiler only emits React Native style properties it recognizes; browser-only or unknown declarations never pass through to `StyleSheet.create`.
|
|
41
|
+
- `dark:`, `ios:`, `android:`, and `native:` are conditional native variants. State, responsive, structural, and other browser selector variants are reported as unsupported instead of being applied unconditionally.
|
|
42
|
+
- Static StyleSheet IDs are stable across unrelated class additions and CSS value changes, so Metro CSS HMR cannot redirect an existing Babel lookup to another rule.
|
|
36
43
|
|
|
37
44
|
## Public entry points
|
|
38
45
|
|
|
@@ -45,3 +52,5 @@ This package generates React Native style manifests. It does not emit Web CSS or
|
|
|
45
52
|
## Documentation
|
|
46
53
|
|
|
47
54
|
See the [React Native and Expo guide](https://tw.icebreaker.top/docs/quick-start/react-native-expo) for the complete setup.
|
|
55
|
+
|
|
56
|
+
The repository compatibility lab reuses the 118-case Tailwind catalog across Expo Web, Android, and iOS. Run `pnpm e2e:react-native:all` for the complete gate, or use `pnpm e2e:react-native-compatibility`, `pnpm e2e:react-native:web`, `pnpm e2e:react-native:android`, and `pnpm e2e:react-native:ios` separately to reproduce its static, runtime, screenshot, and separate TSX/CSS HMR gates. Use `pnpm e2e:react-native:update` only when intentionally refreshing static evidence.
|
package/README.zh-CN.md
CHANGED
|
@@ -27,12 +27,19 @@ module.exports = withWeappTailwindcss(config, {
|
|
|
27
27
|
|
|
28
28
|
Metro 会扫描源码、生成精确候选集合和 manifest,并把 Babel JSX transform 接入现有 Expo transformer。无需维护第二份 `classNameSet`。
|
|
29
29
|
|
|
30
|
+
`withWeappTailwindcss` 支持 `projectRoot`、`input`、`css`、`manifest`、`classSet`、`sourceGlobs` 和 `watchFiles`。完整字段、manifest 结构和非 Expo Metro 用法见 [React Native / Expo 配置参考](https://tw.icebreaker.top/zh-cn/docs/config/react-native)。
|
|
31
|
+
|
|
32
|
+
在 Android 与 iOS 上,Metro 集成会从应用 `projectRoot` 解析 `react` 和 `react-native`,避免 linked workspace 包把与原生二进制不一致的第二份 React Native runtime 打入 bundle。Web 端继续保留 Expo 原有的平台 resolver 行为。
|
|
33
|
+
|
|
30
34
|
## 运行模型
|
|
31
35
|
|
|
32
36
|
- 静态 `className` 编译为预生成的 StyleSheet lookup,不在 render 中调用 `tw()`。
|
|
33
37
|
- 动态 class 才通过 `tw(value)` 在运行时解析。
|
|
34
38
|
- 普通 inline `style` 覆盖 Tailwind class,`!important` class 覆盖 inline style。
|
|
35
39
|
- 不支持的 CSS 声明会记录在 manifest `warnings` 中,不会静默生成错误样式。
|
|
40
|
+
- 编译器只输出明确识别的 React Native style 属性;浏览器专属或未知声明不会透传到 `StyleSheet.create`。
|
|
41
|
+
- `dark:`、`ios:`、`android:`、`native:` 是原生条件变体;状态、响应式、结构等浏览器 selector 变体会明确报告为不支持,不会被错误地无条件应用。
|
|
42
|
+
- 静态 StyleSheet ID 在无关 class 增删和 CSS 值变化时保持稳定,Metro CSS HMR 不会让已有 Babel lookup 串到其他规则。
|
|
36
43
|
|
|
37
44
|
## 公开入口
|
|
38
45
|
|
|
@@ -45,3 +52,5 @@ Metro 会扫描源码、生成精确候选集合和 manifest,并把 Babel JSX
|
|
|
45
52
|
## 文档
|
|
46
53
|
|
|
47
54
|
完整接入说明见 [React Native / Expo 指南](https://tw.icebreaker.top/zh-cn/docs/quick-start/react-native-expo)。
|
|
55
|
+
|
|
56
|
+
仓库兼容性实验室在 Expo Web、Android、iOS 三端复用同一份 118 项 Tailwind catalog。运行 `pnpm e2e:react-native:all` 可执行完整三端门禁;也可分别运行 `pnpm e2e:react-native-compatibility`、`pnpm e2e:react-native:web`、`pnpm e2e:react-native:android` 和 `pnpm e2e:react-native:ios` 复现静态、运行时、截图以及独立的 TSX/CSS HMR 门禁。只有明确要刷新静态证据时才运行 `pnpm e2e:react-native:update`。
|
package/dist/compiler.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
3
4
|
let postcss = require("postcss");
|
|
4
5
|
postcss = require_rolldown_runtime.__toESM(postcss, 1);
|
|
5
6
|
//#region src/compiler.ts
|
|
@@ -61,6 +62,27 @@ const NUMERIC_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
61
62
|
"width",
|
|
62
63
|
"zIndex"
|
|
63
64
|
]);
|
|
65
|
+
const STRING_PROPERTIES = /* @__PURE__ */ new Set([
|
|
66
|
+
"alignContent",
|
|
67
|
+
"alignItems",
|
|
68
|
+
"alignSelf",
|
|
69
|
+
"borderStyle",
|
|
70
|
+
"direction",
|
|
71
|
+
"display",
|
|
72
|
+
"flexDirection",
|
|
73
|
+
"flexWrap",
|
|
74
|
+
"fontFamily",
|
|
75
|
+
"fontStyle",
|
|
76
|
+
"fontWeight",
|
|
77
|
+
"justifyContent",
|
|
78
|
+
"overflow",
|
|
79
|
+
"position",
|
|
80
|
+
"textAlign",
|
|
81
|
+
"textDecorationLine",
|
|
82
|
+
"textDecorationStyle",
|
|
83
|
+
"textTransform",
|
|
84
|
+
"writingDirection"
|
|
85
|
+
]);
|
|
64
86
|
const SHORTHANDS = {
|
|
65
87
|
margin: [
|
|
66
88
|
"marginTop",
|
|
@@ -94,6 +116,45 @@ const UNSUPPORTED_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
94
116
|
function decodeCssIdentifier(value) {
|
|
95
117
|
return value.replace(/\\([0-9a-f]{1,6})\s?/gi, (_, code) => String.fromCodePoint(Number.parseInt(code, 16))).replace(/\\(.)/g, "$1");
|
|
96
118
|
}
|
|
119
|
+
function splitClassName(className) {
|
|
120
|
+
const parts = [];
|
|
121
|
+
let current = "";
|
|
122
|
+
let bracketDepth = 0;
|
|
123
|
+
let escaped = false;
|
|
124
|
+
for (const character of className) {
|
|
125
|
+
if (escaped) {
|
|
126
|
+
current += character;
|
|
127
|
+
escaped = false;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (character === "\\") {
|
|
131
|
+
current += character;
|
|
132
|
+
escaped = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (character === "[") bracketDepth += 1;
|
|
136
|
+
if (character === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
137
|
+
if (character === ":" && bracketDepth === 0) {
|
|
138
|
+
parts.push(current);
|
|
139
|
+
current = "";
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
current += character;
|
|
143
|
+
}
|
|
144
|
+
parts.push(current);
|
|
145
|
+
return parts;
|
|
146
|
+
}
|
|
147
|
+
function baseClassName(className) {
|
|
148
|
+
return splitClassName(className).at(-1);
|
|
149
|
+
}
|
|
150
|
+
function unsupportedVariant(className) {
|
|
151
|
+
return splitClassName(className).slice(0, -1).find((variant) => ![
|
|
152
|
+
"dark",
|
|
153
|
+
"ios",
|
|
154
|
+
"android",
|
|
155
|
+
"native"
|
|
156
|
+
].includes(variant));
|
|
157
|
+
}
|
|
97
158
|
function propertyName(property) {
|
|
98
159
|
return {
|
|
99
160
|
"padding-inline": "paddingHorizontal",
|
|
@@ -115,6 +176,8 @@ function splitValue(value) {
|
|
|
115
176
|
}
|
|
116
177
|
function parseNumber(value, variables) {
|
|
117
178
|
const resolved = resolveVariables(value, variables);
|
|
179
|
+
const fraction = resolved.match(/^(-?(?:\d+\.\d+|\d+|\.\d+))\s*\/\s*(-?(?:\d+\.\d+|\d+|\.\d+))$/);
|
|
180
|
+
if (fraction && Number(fraction[2]) !== 0) return Number(fraction[1]) / Number(fraction[2]);
|
|
118
181
|
const calculated = resolved.match(/^calc\(\s*(-?(?:\d+\.\d+|\d+|\.\d+))(px|rem|em|%)?\s*\*\s*(-?(?:\d+\.\d+|\d+|\.\d+))\s*\)$/);
|
|
119
182
|
if (calculated) {
|
|
120
183
|
const base = parseNumber(`${calculated[1]}${calculated[2] ?? ""}`, variables);
|
|
@@ -210,11 +273,15 @@ function parseValue(property, value, variables) {
|
|
|
210
273
|
const color = resolveVariables(trimmed, variables);
|
|
211
274
|
return isColor(color) ? normalizeColor(color) : void 0;
|
|
212
275
|
}
|
|
213
|
-
if (NUMERIC_PROPERTIES.has(property))
|
|
276
|
+
if (NUMERIC_PROPERTIES.has(property)) {
|
|
277
|
+
const number = parseNumber(trimmed, variables);
|
|
278
|
+
if (property === "opacity" && typeof number === "string" && number.endsWith("%")) return Number.parseFloat(number) / 100;
|
|
279
|
+
return number;
|
|
280
|
+
}
|
|
214
281
|
if (property === "transform") return parseTransform(trimmed, variables);
|
|
215
282
|
if (property === "boxShadow") return parseShadow(trimmed, variables);
|
|
216
283
|
if (property === "display" && !["flex", "none"].includes(trimmed)) return;
|
|
217
|
-
return trimmed;
|
|
284
|
+
return STRING_PROPERTIES.has(property) ? trimmed : void 0;
|
|
218
285
|
}
|
|
219
286
|
function expandDeclaration(property, value, variables) {
|
|
220
287
|
if (property === "margin" || property === "padding") {
|
|
@@ -250,7 +317,7 @@ function walkClasses(selector) {
|
|
|
250
317
|
}
|
|
251
318
|
function variantForClass(className) {
|
|
252
319
|
const result = {};
|
|
253
|
-
for (const variant of className
|
|
320
|
+
for (const variant of splitClassName(className).slice(0, -1)) {
|
|
254
321
|
if (variant === "dark") result.colorScheme = "dark";
|
|
255
322
|
if (variant === "ios" || variant === "android" || variant === "native") result.platform = variant;
|
|
256
323
|
}
|
|
@@ -266,7 +333,7 @@ function atRuleVariant(node) {
|
|
|
266
333
|
}
|
|
267
334
|
function addNativeVariantRules(manifest, candidates) {
|
|
268
335
|
for (const candidate of candidates) {
|
|
269
|
-
const parts = candidate
|
|
336
|
+
const parts = splitClassName(candidate);
|
|
270
337
|
if (parts.length < 2) continue;
|
|
271
338
|
const base = parts.at(-1);
|
|
272
339
|
if (!base || manifest.rules[candidate] || !manifest.rules[base]) continue;
|
|
@@ -300,6 +367,15 @@ function addWarning(warnings, warning) {
|
|
|
300
367
|
if (!warnings.some((item) => item.message === warning.message && item.property === warning.property && item.className === warning.className)) warnings.push(warning);
|
|
301
368
|
}
|
|
302
369
|
function compileRule(rule, className, variables, warnings, order) {
|
|
370
|
+
const unsupported = unsupportedVariant(className);
|
|
371
|
+
if (unsupported) {
|
|
372
|
+
addWarning(warnings, {
|
|
373
|
+
className,
|
|
374
|
+
property: "variant",
|
|
375
|
+
message: `不支持将 ${unsupported}: 变体编译为 React Native 条件样式`
|
|
376
|
+
});
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
303
379
|
const styles = {
|
|
304
380
|
normal: {},
|
|
305
381
|
important: {}
|
|
@@ -311,8 +387,7 @@ function compileRule(rule, className, variables, warnings, order) {
|
|
|
311
387
|
const value = decl.value.replace(/\s*!important\s*$/i, "");
|
|
312
388
|
const expanded = expandDeclaration(property, value, variables);
|
|
313
389
|
if (!expanded || Object.values(expanded).includes(void 0)) {
|
|
314
|
-
|
|
315
|
-
if (!variableReference || !variables[variableReference]) addWarning(warnings, {
|
|
390
|
+
addWarning(warnings, {
|
|
316
391
|
className,
|
|
317
392
|
property,
|
|
318
393
|
message: `不支持将 ${decl.prop}: ${value} 编译为 React Native style`
|
|
@@ -340,9 +415,9 @@ function finalizeNativeManifest(manifest) {
|
|
|
340
415
|
const styleSheet = {};
|
|
341
416
|
const styleEntries = {};
|
|
342
417
|
const staticLookup = {};
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const id = rule.id ?? `s${
|
|
418
|
+
for (const [className, rules] of Object.entries(manifest.rules)) for (const [index, rule] of rules.entries()) {
|
|
419
|
+
const identity = `${className}\0${index}\0${rule.colorScheme ?? ""}\0${rule.platform ?? ""}\0${rule.important ? "important" : "normal"}`;
|
|
420
|
+
const id = rule.id ?? `s${(0, node_crypto.createHash)("sha256").update(identity).digest("hex").slice(0, 12)}`;
|
|
346
421
|
rule.id = id;
|
|
347
422
|
styleSheet[id] = rule.style;
|
|
348
423
|
styleEntries[id] = rule;
|
|
@@ -379,5 +454,6 @@ function compileNativeStylesheet(css, options = {}) {
|
|
|
379
454
|
}
|
|
380
455
|
//#endregion
|
|
381
456
|
exports.addNativeVariantRules = addNativeVariantRules;
|
|
457
|
+
exports.baseClassName = baseClassName;
|
|
382
458
|
exports.compileNativeStylesheet = compileNativeStylesheet;
|
|
383
459
|
exports.finalizeNativeManifest = finalizeNativeManifest;
|
package/dist/compiler.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { o as NativeStyleManifest, t as CompileNativeStylesheetOptions } from "./types-BTxnqaRV.js";
|
|
2
2
|
//#region src/compiler.d.ts
|
|
3
|
+
declare function baseClassName(className: string): string | undefined;
|
|
3
4
|
declare function addNativeVariantRules(manifest: NativeStyleManifest, candidates: Iterable<string>): void;
|
|
4
5
|
/** 为 manifest 生成稳定的 StyleSheet ID 和 Babel 静态 lookup。 */
|
|
5
6
|
declare function finalizeNativeManifest(manifest: NativeStyleManifest): NativeStyleManifest;
|
|
6
7
|
declare function compileNativeStylesheet(css: string, options?: CompileNativeStylesheetOptions): NativeStyleManifest;
|
|
7
8
|
//#endregion
|
|
8
|
-
export { addNativeVariantRules, compileNativeStylesheet, finalizeNativeManifest };
|
|
9
|
+
export { addNativeVariantRules, baseClassName, compileNativeStylesheet, finalizeNativeManifest };
|
package/dist/compiler.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import postcss from "postcss";
|
|
2
3
|
//#region src/compiler.ts
|
|
3
4
|
const CLASS_SELECTOR_RE = /\.((?:\\.|[^\s.#:[>+~])+)/g;
|
|
@@ -58,6 +59,27 @@ const NUMERIC_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
58
59
|
"width",
|
|
59
60
|
"zIndex"
|
|
60
61
|
]);
|
|
62
|
+
const STRING_PROPERTIES = /* @__PURE__ */ new Set([
|
|
63
|
+
"alignContent",
|
|
64
|
+
"alignItems",
|
|
65
|
+
"alignSelf",
|
|
66
|
+
"borderStyle",
|
|
67
|
+
"direction",
|
|
68
|
+
"display",
|
|
69
|
+
"flexDirection",
|
|
70
|
+
"flexWrap",
|
|
71
|
+
"fontFamily",
|
|
72
|
+
"fontStyle",
|
|
73
|
+
"fontWeight",
|
|
74
|
+
"justifyContent",
|
|
75
|
+
"overflow",
|
|
76
|
+
"position",
|
|
77
|
+
"textAlign",
|
|
78
|
+
"textDecorationLine",
|
|
79
|
+
"textDecorationStyle",
|
|
80
|
+
"textTransform",
|
|
81
|
+
"writingDirection"
|
|
82
|
+
]);
|
|
61
83
|
const SHORTHANDS = {
|
|
62
84
|
margin: [
|
|
63
85
|
"marginTop",
|
|
@@ -91,6 +113,45 @@ const UNSUPPORTED_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
91
113
|
function decodeCssIdentifier(value) {
|
|
92
114
|
return value.replace(/\\([0-9a-f]{1,6})\s?/gi, (_, code) => String.fromCodePoint(Number.parseInt(code, 16))).replace(/\\(.)/g, "$1");
|
|
93
115
|
}
|
|
116
|
+
function splitClassName(className) {
|
|
117
|
+
const parts = [];
|
|
118
|
+
let current = "";
|
|
119
|
+
let bracketDepth = 0;
|
|
120
|
+
let escaped = false;
|
|
121
|
+
for (const character of className) {
|
|
122
|
+
if (escaped) {
|
|
123
|
+
current += character;
|
|
124
|
+
escaped = false;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (character === "\\") {
|
|
128
|
+
current += character;
|
|
129
|
+
escaped = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (character === "[") bracketDepth += 1;
|
|
133
|
+
if (character === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
134
|
+
if (character === ":" && bracketDepth === 0) {
|
|
135
|
+
parts.push(current);
|
|
136
|
+
current = "";
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
current += character;
|
|
140
|
+
}
|
|
141
|
+
parts.push(current);
|
|
142
|
+
return parts;
|
|
143
|
+
}
|
|
144
|
+
function baseClassName(className) {
|
|
145
|
+
return splitClassName(className).at(-1);
|
|
146
|
+
}
|
|
147
|
+
function unsupportedVariant(className) {
|
|
148
|
+
return splitClassName(className).slice(0, -1).find((variant) => ![
|
|
149
|
+
"dark",
|
|
150
|
+
"ios",
|
|
151
|
+
"android",
|
|
152
|
+
"native"
|
|
153
|
+
].includes(variant));
|
|
154
|
+
}
|
|
94
155
|
function propertyName(property) {
|
|
95
156
|
return {
|
|
96
157
|
"padding-inline": "paddingHorizontal",
|
|
@@ -112,6 +173,8 @@ function splitValue(value) {
|
|
|
112
173
|
}
|
|
113
174
|
function parseNumber(value, variables) {
|
|
114
175
|
const resolved = resolveVariables(value, variables);
|
|
176
|
+
const fraction = resolved.match(/^(-?(?:\d+\.\d+|\d+|\.\d+))\s*\/\s*(-?(?:\d+\.\d+|\d+|\.\d+))$/);
|
|
177
|
+
if (fraction && Number(fraction[2]) !== 0) return Number(fraction[1]) / Number(fraction[2]);
|
|
115
178
|
const calculated = resolved.match(/^calc\(\s*(-?(?:\d+\.\d+|\d+|\.\d+))(px|rem|em|%)?\s*\*\s*(-?(?:\d+\.\d+|\d+|\.\d+))\s*\)$/);
|
|
116
179
|
if (calculated) {
|
|
117
180
|
const base = parseNumber(`${calculated[1]}${calculated[2] ?? ""}`, variables);
|
|
@@ -207,11 +270,15 @@ function parseValue(property, value, variables) {
|
|
|
207
270
|
const color = resolveVariables(trimmed, variables);
|
|
208
271
|
return isColor(color) ? normalizeColor(color) : void 0;
|
|
209
272
|
}
|
|
210
|
-
if (NUMERIC_PROPERTIES.has(property))
|
|
273
|
+
if (NUMERIC_PROPERTIES.has(property)) {
|
|
274
|
+
const number = parseNumber(trimmed, variables);
|
|
275
|
+
if (property === "opacity" && typeof number === "string" && number.endsWith("%")) return Number.parseFloat(number) / 100;
|
|
276
|
+
return number;
|
|
277
|
+
}
|
|
211
278
|
if (property === "transform") return parseTransform(trimmed, variables);
|
|
212
279
|
if (property === "boxShadow") return parseShadow(trimmed, variables);
|
|
213
280
|
if (property === "display" && !["flex", "none"].includes(trimmed)) return;
|
|
214
|
-
return trimmed;
|
|
281
|
+
return STRING_PROPERTIES.has(property) ? trimmed : void 0;
|
|
215
282
|
}
|
|
216
283
|
function expandDeclaration(property, value, variables) {
|
|
217
284
|
if (property === "margin" || property === "padding") {
|
|
@@ -247,7 +314,7 @@ function walkClasses(selector) {
|
|
|
247
314
|
}
|
|
248
315
|
function variantForClass(className) {
|
|
249
316
|
const result = {};
|
|
250
|
-
for (const variant of className
|
|
317
|
+
for (const variant of splitClassName(className).slice(0, -1)) {
|
|
251
318
|
if (variant === "dark") result.colorScheme = "dark";
|
|
252
319
|
if (variant === "ios" || variant === "android" || variant === "native") result.platform = variant;
|
|
253
320
|
}
|
|
@@ -263,7 +330,7 @@ function atRuleVariant(node) {
|
|
|
263
330
|
}
|
|
264
331
|
function addNativeVariantRules(manifest, candidates) {
|
|
265
332
|
for (const candidate of candidates) {
|
|
266
|
-
const parts = candidate
|
|
333
|
+
const parts = splitClassName(candidate);
|
|
267
334
|
if (parts.length < 2) continue;
|
|
268
335
|
const base = parts.at(-1);
|
|
269
336
|
if (!base || manifest.rules[candidate] || !manifest.rules[base]) continue;
|
|
@@ -297,6 +364,15 @@ function addWarning(warnings, warning) {
|
|
|
297
364
|
if (!warnings.some((item) => item.message === warning.message && item.property === warning.property && item.className === warning.className)) warnings.push(warning);
|
|
298
365
|
}
|
|
299
366
|
function compileRule(rule, className, variables, warnings, order) {
|
|
367
|
+
const unsupported = unsupportedVariant(className);
|
|
368
|
+
if (unsupported) {
|
|
369
|
+
addWarning(warnings, {
|
|
370
|
+
className,
|
|
371
|
+
property: "variant",
|
|
372
|
+
message: `不支持将 ${unsupported}: 变体编译为 React Native 条件样式`
|
|
373
|
+
});
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
300
376
|
const styles = {
|
|
301
377
|
normal: {},
|
|
302
378
|
important: {}
|
|
@@ -308,8 +384,7 @@ function compileRule(rule, className, variables, warnings, order) {
|
|
|
308
384
|
const value = decl.value.replace(/\s*!important\s*$/i, "");
|
|
309
385
|
const expanded = expandDeclaration(property, value, variables);
|
|
310
386
|
if (!expanded || Object.values(expanded).includes(void 0)) {
|
|
311
|
-
|
|
312
|
-
if (!variableReference || !variables[variableReference]) addWarning(warnings, {
|
|
387
|
+
addWarning(warnings, {
|
|
313
388
|
className,
|
|
314
389
|
property,
|
|
315
390
|
message: `不支持将 ${decl.prop}: ${value} 编译为 React Native style`
|
|
@@ -337,9 +412,9 @@ function finalizeNativeManifest(manifest) {
|
|
|
337
412
|
const styleSheet = {};
|
|
338
413
|
const styleEntries = {};
|
|
339
414
|
const staticLookup = {};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const id = rule.id ?? `s${
|
|
415
|
+
for (const [className, rules] of Object.entries(manifest.rules)) for (const [index, rule] of rules.entries()) {
|
|
416
|
+
const identity = `${className}\0${index}\0${rule.colorScheme ?? ""}\0${rule.platform ?? ""}\0${rule.important ? "important" : "normal"}`;
|
|
417
|
+
const id = rule.id ?? `s${createHash("sha256").update(identity).digest("hex").slice(0, 12)}`;
|
|
343
418
|
rule.id = id;
|
|
344
419
|
styleSheet[id] = rule.style;
|
|
345
420
|
styleEntries[id] = rule;
|
|
@@ -375,4 +450,4 @@ function compileNativeStylesheet(css, options = {}) {
|
|
|
375
450
|
});
|
|
376
451
|
}
|
|
377
452
|
//#endregion
|
|
378
|
-
export { addNativeVariantRules, compileNativeStylesheet, finalizeNativeManifest };
|
|
453
|
+
export { addNativeVariantRules, baseClassName, compileNativeStylesheet, finalizeNativeManifest };
|
package/dist/env.cjs
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
require("react-native");
|
package/dist/env.d.ts
CHANGED
package/dist/env.js
CHANGED
|
@@ -4,27 +4,35 @@ const require_babel = require("./babel.cjs");
|
|
|
4
4
|
const require_metro = require("./metro.cjs");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
|
|
7
|
+
let node_process = require("node:process");
|
|
8
|
+
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
7
9
|
let node_module = require("node:module");
|
|
8
10
|
let _babel_core = require("@babel/core");
|
|
9
11
|
//#region src/metro-transformer.ts
|
|
10
12
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
11
13
|
async function transform(config, projectRoot, filename, data, options) {
|
|
12
14
|
const virtualCode = await require_metro.getVirtualModuleCodeAsync(filename);
|
|
13
|
-
if (virtualCode) return {
|
|
14
|
-
output: [{
|
|
15
|
-
type: "js/module",
|
|
16
|
-
data: {
|
|
17
|
-
code: virtualCode,
|
|
18
|
-
map: null
|
|
19
|
-
}
|
|
20
|
-
}],
|
|
21
|
-
dependencies: []
|
|
22
|
-
};
|
|
23
15
|
const metroId = config.weappTailwindcssMetroId;
|
|
24
16
|
const manifestPath = config.weappTailwindcssManifestPath;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
const manifestReadyPath = config.weappTailwindcssManifestReadyPath;
|
|
18
|
+
const virtualModulePath = config.weappTailwindcssVirtualModulePath;
|
|
19
|
+
const projectManifestPaths = require_metro.getManifestPathsForProjectRoot(projectRoot);
|
|
20
|
+
const manifest = (metroId ? await require_metro.getRegisteredManifest(metroId) : void 0) ?? (manifestPath ? await require_metro.getRegisteredManifestByPath(manifestPath) : void 0) ?? await require_metro.getRegisteredManifestByProjectRoot(projectRoot) ?? await readManifest(manifestPath ?? projectManifestPaths.manifestPath, manifestReadyPath ?? projectManifestPaths.manifestReadyPath);
|
|
21
|
+
if (node_process.default.env.WEAPP_TW_RN_DEBUG === "1" && !filename.replaceAll("\\", "/").includes("/node_modules/")) console.error(`[react-native-debug] ${JSON.stringify({
|
|
22
|
+
filename,
|
|
23
|
+
projectRoot,
|
|
24
|
+
configKeys: Object.keys(config).filter((key) => key.startsWith("weappTailwindcss")),
|
|
25
|
+
manifestPath,
|
|
26
|
+
manifestReadyPath,
|
|
27
|
+
virtualModulePath,
|
|
28
|
+
virtualCode: Boolean(virtualCode),
|
|
29
|
+
manifestClasses: manifest?.classSet.length ?? null,
|
|
30
|
+
manifestReady: manifestReadyPath ? node_fs.default.existsSync(manifestReadyPath) : null,
|
|
31
|
+
inputBytes: data.byteLength
|
|
32
|
+
})}`);
|
|
33
|
+
let source = virtualCode ? Buffer.from(virtualCode) : data;
|
|
34
|
+
if (!virtualCode && manifest && virtualModulePath === filename) source = Buffer.from(node_fs.default.readFileSync(filename));
|
|
35
|
+
if (!virtualCode && manifest && /\.(?:[cm]?[jt]sx?|flow)$/i.test(filename) && !filename.replaceAll("\\", "/").includes("/node_modules/")) {
|
|
28
36
|
const transformed = (0, _babel_core.transformSync)(data.toString(), {
|
|
29
37
|
filename,
|
|
30
38
|
configFile: false,
|
|
@@ -41,9 +49,18 @@ async function transform(config, projectRoot, filename, data, options) {
|
|
|
41
49
|
const originalPath = config.weappTailwindcssOriginalTransformerPath;
|
|
42
50
|
return (originalPath ? require$1(originalPath) : require$1("metro-react-native-babel-transformer")).transform(config, projectRoot, filename, source, options);
|
|
43
51
|
}
|
|
44
|
-
function readManifest(filename) {
|
|
52
|
+
async function readManifest(filename, readyPath) {
|
|
53
|
+
if (readyPath) {
|
|
54
|
+
const deadline = Date.now() + 12e4;
|
|
55
|
+
while (Date.now() < deadline) try {
|
|
56
|
+
await node_fs.default.promises.access(readyPath);
|
|
57
|
+
break;
|
|
58
|
+
} catch {
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
45
62
|
try {
|
|
46
|
-
return JSON.parse(node_fs.default.
|
|
63
|
+
return JSON.parse(await node_fs.default.promises.readFile(filename, "utf8"));
|
|
47
64
|
} catch {
|
|
48
65
|
return;
|
|
49
66
|
}
|
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import weappReactNativeBabel from "./babel.js";
|
|
2
|
-
import { getRegisteredManifest, getVirtualModuleCodeAsync } from "./metro.js";
|
|
2
|
+
import { getManifestPathsForProjectRoot, getRegisteredManifest, getRegisteredManifestByPath, getRegisteredManifestByProjectRoot, getVirtualModuleCodeAsync } from "./metro.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import fs from "node:fs";
|
|
5
|
+
import process from "node:process";
|
|
5
6
|
import { transformSync } from "@babel/core";
|
|
6
7
|
//#region src/metro-transformer.ts
|
|
7
8
|
const require = createRequire(import.meta.url);
|
|
8
9
|
async function transform(config, projectRoot, filename, data, options) {
|
|
9
10
|
const virtualCode = await getVirtualModuleCodeAsync(filename);
|
|
10
|
-
if (virtualCode) return {
|
|
11
|
-
output: [{
|
|
12
|
-
type: "js/module",
|
|
13
|
-
data: {
|
|
14
|
-
code: virtualCode,
|
|
15
|
-
map: null
|
|
16
|
-
}
|
|
17
|
-
}],
|
|
18
|
-
dependencies: []
|
|
19
|
-
};
|
|
20
11
|
const metroId = config.weappTailwindcssMetroId;
|
|
21
12
|
const manifestPath = config.weappTailwindcssManifestPath;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
const manifestReadyPath = config.weappTailwindcssManifestReadyPath;
|
|
14
|
+
const virtualModulePath = config.weappTailwindcssVirtualModulePath;
|
|
15
|
+
const projectManifestPaths = getManifestPathsForProjectRoot(projectRoot);
|
|
16
|
+
const manifest = (metroId ? await getRegisteredManifest(metroId) : void 0) ?? (manifestPath ? await getRegisteredManifestByPath(manifestPath) : void 0) ?? await getRegisteredManifestByProjectRoot(projectRoot) ?? await readManifest(manifestPath ?? projectManifestPaths.manifestPath, manifestReadyPath ?? projectManifestPaths.manifestReadyPath);
|
|
17
|
+
if (process.env.WEAPP_TW_RN_DEBUG === "1" && !filename.replaceAll("\\", "/").includes("/node_modules/")) console.error(`[react-native-debug] ${JSON.stringify({
|
|
18
|
+
filename,
|
|
19
|
+
projectRoot,
|
|
20
|
+
configKeys: Object.keys(config).filter((key) => key.startsWith("weappTailwindcss")),
|
|
21
|
+
manifestPath,
|
|
22
|
+
manifestReadyPath,
|
|
23
|
+
virtualModulePath,
|
|
24
|
+
virtualCode: Boolean(virtualCode),
|
|
25
|
+
manifestClasses: manifest?.classSet.length ?? null,
|
|
26
|
+
manifestReady: manifestReadyPath ? fs.existsSync(manifestReadyPath) : null,
|
|
27
|
+
inputBytes: data.byteLength
|
|
28
|
+
})}`);
|
|
29
|
+
let source = virtualCode ? Buffer.from(virtualCode) : data;
|
|
30
|
+
if (!virtualCode && manifest && virtualModulePath === filename) source = Buffer.from(fs.readFileSync(filename));
|
|
31
|
+
if (!virtualCode && manifest && /\.(?:[cm]?[jt]sx?|flow)$/i.test(filename) && !filename.replaceAll("\\", "/").includes("/node_modules/")) {
|
|
25
32
|
const transformed = transformSync(data.toString(), {
|
|
26
33
|
filename,
|
|
27
34
|
configFile: false,
|
|
@@ -38,9 +45,18 @@ async function transform(config, projectRoot, filename, data, options) {
|
|
|
38
45
|
const originalPath = config.weappTailwindcssOriginalTransformerPath;
|
|
39
46
|
return (originalPath ? require(originalPath) : require("metro-react-native-babel-transformer")).transform(config, projectRoot, filename, source, options);
|
|
40
47
|
}
|
|
41
|
-
function readManifest(filename) {
|
|
48
|
+
async function readManifest(filename, readyPath) {
|
|
49
|
+
if (readyPath) {
|
|
50
|
+
const deadline = Date.now() + 12e4;
|
|
51
|
+
while (Date.now() < deadline) try {
|
|
52
|
+
await fs.promises.access(readyPath);
|
|
53
|
+
break;
|
|
54
|
+
} catch {
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
42
58
|
try {
|
|
43
|
-
return JSON.parse(fs.
|
|
59
|
+
return JSON.parse(await fs.promises.readFile(filename, "utf8"));
|
|
44
60
|
} catch {
|
|
45
61
|
return;
|
|
46
62
|
}
|
package/dist/metro.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
3
3
|
const require_compiler = require("./compiler.cjs");
|
|
4
|
+
let node_crypto = require("node:crypto");
|
|
4
5
|
let node_fs = require("node:fs");
|
|
5
6
|
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
|
|
6
7
|
let node_os = require("node:os");
|
|
@@ -13,7 +14,24 @@ let node_url = require("node:url");
|
|
|
13
14
|
//#region src/metro.ts
|
|
14
15
|
const VIRTUAL_MANIFEST_MODULE = "@weapp-tailwindcss/react-native/virtual";
|
|
15
16
|
const registry = /* @__PURE__ */ new Map();
|
|
17
|
+
const appRootSingletonModules = /* @__PURE__ */ new Set(["react", "react-native"]);
|
|
16
18
|
let nextId = 0;
|
|
19
|
+
function packageNameFromModuleId(moduleName) {
|
|
20
|
+
const segments = moduleName.split("/");
|
|
21
|
+
return moduleName.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
|
22
|
+
}
|
|
23
|
+
function shouldResolveFromAppRoot(moduleName, platform) {
|
|
24
|
+
return platform !== "web" && appRootSingletonModules.has(packageNameFromModuleId(moduleName));
|
|
25
|
+
}
|
|
26
|
+
/** 为 Metro worker 提供不依赖进程内 registry 的 manifest 入口。 */
|
|
27
|
+
function getManifestPathsForProjectRoot(projectRoot) {
|
|
28
|
+
const projectKey = (0, node_crypto.createHash)("sha256").update(node_path.default.resolve(projectRoot)).digest("hex").slice(0, 24);
|
|
29
|
+
const base = node_path.default.join(node_os.default.tmpdir(), "weapp-tailwindcss-native", `project-${projectKey}`);
|
|
30
|
+
return {
|
|
31
|
+
manifestPath: `${base}.manifest.json`,
|
|
32
|
+
manifestReadyPath: `${base}.manifest.ready`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
17
35
|
function resolveInput(input, projectRoot) {
|
|
18
36
|
return input ? node_path.default.resolve(projectRoot, input) : void 0;
|
|
19
37
|
}
|
|
@@ -34,6 +52,12 @@ function writeVirtualModule(entry) {
|
|
|
34
52
|
node_fs.default.writeFileSync(entry.virtualPath, virtualModuleCode(entry.manifest), "utf8");
|
|
35
53
|
node_fs.default.writeFileSync(entry.manifestPath, JSON.stringify(entry.manifest), "utf8");
|
|
36
54
|
}
|
|
55
|
+
function markManifestPending(entry) {
|
|
56
|
+
node_fs.default.rmSync(entry.manifestReadyPath, { force: true });
|
|
57
|
+
}
|
|
58
|
+
function markManifestReady(entry) {
|
|
59
|
+
node_fs.default.writeFileSync(entry.manifestReadyPath, `${entry.version}\n`, "utf8");
|
|
60
|
+
}
|
|
37
61
|
async function compileOptions(options, projectRoot) {
|
|
38
62
|
if (options.manifest) return options.manifest;
|
|
39
63
|
if (options.input) {
|
|
@@ -50,28 +74,38 @@ async function compileOptions(options, projectRoot) {
|
|
|
50
74
|
function register(options) {
|
|
51
75
|
const id = `weapp-tailwindcss-native-${nextId++}`;
|
|
52
76
|
const projectRoot = node_path.default.resolve(options.projectRoot ?? node_process.default.cwd());
|
|
77
|
+
const manifestPaths = getManifestPathsForProjectRoot(projectRoot);
|
|
53
78
|
const entry = {
|
|
54
79
|
version: 0,
|
|
55
80
|
manifest: options.manifest ?? (options.css ? require_compiler.compileNativeStylesheet(options.css, { classSet: options.classSet }) : emptyManifest()),
|
|
81
|
+
projectRoot,
|
|
56
82
|
virtualPath: node_path.default.join(node_os.default.tmpdir(), "weapp-tailwindcss-native", `${id}.js`),
|
|
57
|
-
manifestPath:
|
|
83
|
+
manifestPath: manifestPaths.manifestPath,
|
|
84
|
+
manifestReadyPath: manifestPaths.manifestReadyPath,
|
|
58
85
|
ready: Promise.resolve(),
|
|
59
86
|
refresh: async () => {}
|
|
60
87
|
};
|
|
61
88
|
registry.set(id, entry);
|
|
89
|
+
markManifestPending(entry);
|
|
62
90
|
writeVirtualModule(entry);
|
|
63
91
|
let generation = 0;
|
|
64
92
|
entry.refresh = async () => {
|
|
65
93
|
const currentGeneration = ++generation;
|
|
94
|
+
markManifestPending(entry);
|
|
66
95
|
const manifest = await compileOptions(options, projectRoot);
|
|
67
96
|
if (currentGeneration === generation) {
|
|
68
97
|
entry.manifest = manifest;
|
|
69
98
|
entry.version++;
|
|
70
99
|
writeVirtualModule(entry);
|
|
100
|
+
markManifestReady(entry);
|
|
71
101
|
}
|
|
72
102
|
};
|
|
73
103
|
entry.ready = entry.refresh().catch((error) => {
|
|
74
|
-
|
|
104
|
+
const message = `生成 React Native manifest 失败:${error instanceof Error ? error.message : String(error)}`;
|
|
105
|
+
entry.manifest.warnings.push({ message });
|
|
106
|
+
if (node_process.default.env.WEAPP_TW_RN_DEBUG === "1") console.error(`[react-native-debug] ${message}`);
|
|
107
|
+
writeVirtualModule(entry);
|
|
108
|
+
markManifestReady(entry);
|
|
75
109
|
});
|
|
76
110
|
const sourceRoots = (options.sourceGlobs ?? []).map((pattern) => pattern.split(/[*{[]/, 1)[0]?.replace(/[/\\]$/, "")).filter(Boolean);
|
|
77
111
|
[
|
|
@@ -106,6 +140,21 @@ async function getRegisteredManifest(id) {
|
|
|
106
140
|
await registered.ready;
|
|
107
141
|
return registered.manifest;
|
|
108
142
|
}
|
|
143
|
+
/** 等待并读取由 Metro 配置注册的 manifest 文件,兼容未透传自定义 Metro id 的 transformer。 */
|
|
144
|
+
async function getRegisteredManifestByPath(filename) {
|
|
145
|
+
for (const entry of registry.values()) if (entry.manifestPath === filename) {
|
|
146
|
+
await entry.ready;
|
|
147
|
+
return entry.manifest;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** 按 Metro 传入的项目根目录读取当前注册项,兼容 transformer 未透传自定义字段的实现。 */
|
|
151
|
+
async function getRegisteredManifestByProjectRoot(projectRoot) {
|
|
152
|
+
const resolvedRoot = node_path.default.resolve(projectRoot);
|
|
153
|
+
const entry = [...registry.values()].filter((entry) => entry.projectRoot === resolvedRoot).at(-1);
|
|
154
|
+
if (!entry) return;
|
|
155
|
+
await entry.ready;
|
|
156
|
+
return entry.manifest;
|
|
157
|
+
}
|
|
109
158
|
function getVirtualModuleCode(filename) {
|
|
110
159
|
const registered = getRegisteredVirtualModule(filename);
|
|
111
160
|
if (!registered) return;
|
|
@@ -128,7 +177,12 @@ function withWeappTailwindcss(config, options = {}) {
|
|
|
128
177
|
type: "sourceFile",
|
|
129
178
|
filePath: entry.virtualPath
|
|
130
179
|
};
|
|
131
|
-
|
|
180
|
+
const resolverContext = context;
|
|
181
|
+
const anchoredContext = resolverContext.originModulePath === entry.virtualPath || shouldResolveFromAppRoot(moduleName, platform) ? {
|
|
182
|
+
...resolverContext,
|
|
183
|
+
originModulePath: node_path.default.join(entry.projectRoot, "package.json")
|
|
184
|
+
} : resolverContext;
|
|
185
|
+
return originalResolver?.(anchoredContext, moduleName, platform) ?? resolverContext.resolveRequest?.(anchoredContext, moduleName, platform);
|
|
132
186
|
};
|
|
133
187
|
return {
|
|
134
188
|
...resolvedConfig,
|
|
@@ -138,7 +192,9 @@ function withWeappTailwindcss(config, options = {}) {
|
|
|
138
192
|
...resolvedConfig.transformer,
|
|
139
193
|
weappTailwindcssMetroId: id,
|
|
140
194
|
weappTailwindcssOriginalTransformerPath: resolvedConfig.transformerPath,
|
|
141
|
-
weappTailwindcssManifestPath: entry.manifestPath
|
|
195
|
+
weappTailwindcssManifestPath: entry.manifestPath,
|
|
196
|
+
weappTailwindcssManifestReadyPath: entry.manifestReadyPath,
|
|
197
|
+
weappTailwindcssVirtualModulePath: entry.virtualPath
|
|
142
198
|
},
|
|
143
199
|
resolver: {
|
|
144
200
|
...resolvedConfig.resolver,
|
|
@@ -149,7 +205,10 @@ function withWeappTailwindcss(config, options = {}) {
|
|
|
149
205
|
}
|
|
150
206
|
//#endregion
|
|
151
207
|
exports.VIRTUAL_MANIFEST_MODULE = VIRTUAL_MANIFEST_MODULE;
|
|
208
|
+
exports.getManifestPathsForProjectRoot = getManifestPathsForProjectRoot;
|
|
152
209
|
exports.getRegisteredManifest = getRegisteredManifest;
|
|
210
|
+
exports.getRegisteredManifestByPath = getRegisteredManifestByPath;
|
|
211
|
+
exports.getRegisteredManifestByProjectRoot = getRegisteredManifestByProjectRoot;
|
|
153
212
|
exports.getRegisteredVirtualModule = getRegisteredVirtualModule;
|
|
154
213
|
exports.getVirtualModuleCode = getVirtualModuleCode;
|
|
155
214
|
exports.getVirtualModuleCodeAsync = getVirtualModuleCodeAsync;
|
package/dist/metro.d.ts
CHANGED
|
@@ -25,15 +25,27 @@ interface WeappReactNativeMetroOptions {
|
|
|
25
25
|
interface RegisteredManifest {
|
|
26
26
|
version: number;
|
|
27
27
|
manifest: NativeStyleManifest;
|
|
28
|
+
projectRoot: string;
|
|
28
29
|
virtualPath: string;
|
|
29
30
|
manifestPath: string;
|
|
31
|
+
manifestReadyPath: string;
|
|
30
32
|
ready: Promise<void>;
|
|
31
33
|
refresh: () => Promise<void>;
|
|
32
34
|
}
|
|
35
|
+
interface NativeManifestPaths {
|
|
36
|
+
manifestPath: string;
|
|
37
|
+
manifestReadyPath: string;
|
|
38
|
+
}
|
|
39
|
+
/** 为 Metro worker 提供不依赖进程内 registry 的 manifest 入口。 */
|
|
40
|
+
declare function getManifestPathsForProjectRoot(projectRoot: string): NativeManifestPaths;
|
|
33
41
|
declare function getRegisteredVirtualModule(filename: string): RegisteredManifest | undefined;
|
|
34
42
|
declare function getRegisteredManifest(id: string): Promise<NativeStyleManifest | undefined>;
|
|
43
|
+
/** 等待并读取由 Metro 配置注册的 manifest 文件,兼容未透传自定义 Metro id 的 transformer。 */
|
|
44
|
+
declare function getRegisteredManifestByPath(filename: string): Promise<NativeStyleManifest | undefined>;
|
|
45
|
+
/** 按 Metro 传入的项目根目录读取当前注册项,兼容 transformer 未透传自定义字段的实现。 */
|
|
46
|
+
declare function getRegisteredManifestByProjectRoot(projectRoot: string): Promise<NativeStyleManifest | undefined>;
|
|
35
47
|
declare function getVirtualModuleCode(filename: string): string | undefined;
|
|
36
48
|
declare function getVirtualModuleCodeAsync(filename: string): Promise<string | undefined>;
|
|
37
49
|
declare function withWeappTailwindcss<T extends MetroConfigLike>(config: T | Promise<T> | (() => T | Promise<T>), options?: WeappReactNativeMetroOptions): T | Promise<T>;
|
|
38
50
|
//#endregion
|
|
39
|
-
export { MetroConfigLike, VIRTUAL_MANIFEST_MODULE, WeappReactNativeMetroOptions, getRegisteredManifest, getRegisteredVirtualModule, getVirtualModuleCode, getVirtualModuleCodeAsync, withWeappTailwindcss };
|
|
51
|
+
export { MetroConfigLike, NativeManifestPaths, VIRTUAL_MANIFEST_MODULE, WeappReactNativeMetroOptions, getManifestPathsForProjectRoot, getRegisteredManifest, getRegisteredManifestByPath, getRegisteredManifestByProjectRoot, getRegisteredVirtualModule, getVirtualModuleCode, getVirtualModuleCodeAsync, withWeappTailwindcss };
|
package/dist/metro.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compileNativeStylesheet } from "./compiler.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
@@ -7,7 +8,24 @@ import { fileURLToPath } from "node:url";
|
|
|
7
8
|
//#region src/metro.ts
|
|
8
9
|
const VIRTUAL_MANIFEST_MODULE = "@weapp-tailwindcss/react-native/virtual";
|
|
9
10
|
const registry = /* @__PURE__ */ new Map();
|
|
11
|
+
const appRootSingletonModules = /* @__PURE__ */ new Set(["react", "react-native"]);
|
|
10
12
|
let nextId = 0;
|
|
13
|
+
function packageNameFromModuleId(moduleName) {
|
|
14
|
+
const segments = moduleName.split("/");
|
|
15
|
+
return moduleName.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0];
|
|
16
|
+
}
|
|
17
|
+
function shouldResolveFromAppRoot(moduleName, platform) {
|
|
18
|
+
return platform !== "web" && appRootSingletonModules.has(packageNameFromModuleId(moduleName));
|
|
19
|
+
}
|
|
20
|
+
/** 为 Metro worker 提供不依赖进程内 registry 的 manifest 入口。 */
|
|
21
|
+
function getManifestPathsForProjectRoot(projectRoot) {
|
|
22
|
+
const projectKey = createHash("sha256").update(path.resolve(projectRoot)).digest("hex").slice(0, 24);
|
|
23
|
+
const base = path.join(os.tmpdir(), "weapp-tailwindcss-native", `project-${projectKey}`);
|
|
24
|
+
return {
|
|
25
|
+
manifestPath: `${base}.manifest.json`,
|
|
26
|
+
manifestReadyPath: `${base}.manifest.ready`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
11
29
|
function resolveInput(input, projectRoot) {
|
|
12
30
|
return input ? path.resolve(projectRoot, input) : void 0;
|
|
13
31
|
}
|
|
@@ -28,6 +46,12 @@ function writeVirtualModule(entry) {
|
|
|
28
46
|
fs.writeFileSync(entry.virtualPath, virtualModuleCode(entry.manifest), "utf8");
|
|
29
47
|
fs.writeFileSync(entry.manifestPath, JSON.stringify(entry.manifest), "utf8");
|
|
30
48
|
}
|
|
49
|
+
function markManifestPending(entry) {
|
|
50
|
+
fs.rmSync(entry.manifestReadyPath, { force: true });
|
|
51
|
+
}
|
|
52
|
+
function markManifestReady(entry) {
|
|
53
|
+
fs.writeFileSync(entry.manifestReadyPath, `${entry.version}\n`, "utf8");
|
|
54
|
+
}
|
|
31
55
|
async function compileOptions(options, projectRoot) {
|
|
32
56
|
if (options.manifest) return options.manifest;
|
|
33
57
|
if (options.input) {
|
|
@@ -44,28 +68,38 @@ async function compileOptions(options, projectRoot) {
|
|
|
44
68
|
function register(options) {
|
|
45
69
|
const id = `weapp-tailwindcss-native-${nextId++}`;
|
|
46
70
|
const projectRoot = path.resolve(options.projectRoot ?? process.cwd());
|
|
71
|
+
const manifestPaths = getManifestPathsForProjectRoot(projectRoot);
|
|
47
72
|
const entry = {
|
|
48
73
|
version: 0,
|
|
49
74
|
manifest: options.manifest ?? (options.css ? compileNativeStylesheet(options.css, { classSet: options.classSet }) : emptyManifest()),
|
|
75
|
+
projectRoot,
|
|
50
76
|
virtualPath: path.join(os.tmpdir(), "weapp-tailwindcss-native", `${id}.js`),
|
|
51
|
-
manifestPath:
|
|
77
|
+
manifestPath: manifestPaths.manifestPath,
|
|
78
|
+
manifestReadyPath: manifestPaths.manifestReadyPath,
|
|
52
79
|
ready: Promise.resolve(),
|
|
53
80
|
refresh: async () => {}
|
|
54
81
|
};
|
|
55
82
|
registry.set(id, entry);
|
|
83
|
+
markManifestPending(entry);
|
|
56
84
|
writeVirtualModule(entry);
|
|
57
85
|
let generation = 0;
|
|
58
86
|
entry.refresh = async () => {
|
|
59
87
|
const currentGeneration = ++generation;
|
|
88
|
+
markManifestPending(entry);
|
|
60
89
|
const manifest = await compileOptions(options, projectRoot);
|
|
61
90
|
if (currentGeneration === generation) {
|
|
62
91
|
entry.manifest = manifest;
|
|
63
92
|
entry.version++;
|
|
64
93
|
writeVirtualModule(entry);
|
|
94
|
+
markManifestReady(entry);
|
|
65
95
|
}
|
|
66
96
|
};
|
|
67
97
|
entry.ready = entry.refresh().catch((error) => {
|
|
68
|
-
|
|
98
|
+
const message = `生成 React Native manifest 失败:${error instanceof Error ? error.message : String(error)}`;
|
|
99
|
+
entry.manifest.warnings.push({ message });
|
|
100
|
+
if (process.env.WEAPP_TW_RN_DEBUG === "1") console.error(`[react-native-debug] ${message}`);
|
|
101
|
+
writeVirtualModule(entry);
|
|
102
|
+
markManifestReady(entry);
|
|
69
103
|
});
|
|
70
104
|
const sourceRoots = (options.sourceGlobs ?? []).map((pattern) => pattern.split(/[*{[]/, 1)[0]?.replace(/[/\\]$/, "")).filter(Boolean);
|
|
71
105
|
[
|
|
@@ -100,6 +134,21 @@ async function getRegisteredManifest(id) {
|
|
|
100
134
|
await registered.ready;
|
|
101
135
|
return registered.manifest;
|
|
102
136
|
}
|
|
137
|
+
/** 等待并读取由 Metro 配置注册的 manifest 文件,兼容未透传自定义 Metro id 的 transformer。 */
|
|
138
|
+
async function getRegisteredManifestByPath(filename) {
|
|
139
|
+
for (const entry of registry.values()) if (entry.manifestPath === filename) {
|
|
140
|
+
await entry.ready;
|
|
141
|
+
return entry.manifest;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** 按 Metro 传入的项目根目录读取当前注册项,兼容 transformer 未透传自定义字段的实现。 */
|
|
145
|
+
async function getRegisteredManifestByProjectRoot(projectRoot) {
|
|
146
|
+
const resolvedRoot = path.resolve(projectRoot);
|
|
147
|
+
const entry = [...registry.values()].filter((entry) => entry.projectRoot === resolvedRoot).at(-1);
|
|
148
|
+
if (!entry) return;
|
|
149
|
+
await entry.ready;
|
|
150
|
+
return entry.manifest;
|
|
151
|
+
}
|
|
103
152
|
function getVirtualModuleCode(filename) {
|
|
104
153
|
const registered = getRegisteredVirtualModule(filename);
|
|
105
154
|
if (!registered) return;
|
|
@@ -122,7 +171,12 @@ function withWeappTailwindcss(config, options = {}) {
|
|
|
122
171
|
type: "sourceFile",
|
|
123
172
|
filePath: entry.virtualPath
|
|
124
173
|
};
|
|
125
|
-
|
|
174
|
+
const resolverContext = context;
|
|
175
|
+
const anchoredContext = resolverContext.originModulePath === entry.virtualPath || shouldResolveFromAppRoot(moduleName, platform) ? {
|
|
176
|
+
...resolverContext,
|
|
177
|
+
originModulePath: path.join(entry.projectRoot, "package.json")
|
|
178
|
+
} : resolverContext;
|
|
179
|
+
return originalResolver?.(anchoredContext, moduleName, platform) ?? resolverContext.resolveRequest?.(anchoredContext, moduleName, platform);
|
|
126
180
|
};
|
|
127
181
|
return {
|
|
128
182
|
...resolvedConfig,
|
|
@@ -132,7 +186,9 @@ function withWeappTailwindcss(config, options = {}) {
|
|
|
132
186
|
...resolvedConfig.transformer,
|
|
133
187
|
weappTailwindcssMetroId: id,
|
|
134
188
|
weappTailwindcssOriginalTransformerPath: resolvedConfig.transformerPath,
|
|
135
|
-
weappTailwindcssManifestPath: entry.manifestPath
|
|
189
|
+
weappTailwindcssManifestPath: entry.manifestPath,
|
|
190
|
+
weappTailwindcssManifestReadyPath: entry.manifestReadyPath,
|
|
191
|
+
weappTailwindcssVirtualModulePath: entry.virtualPath
|
|
136
192
|
},
|
|
137
193
|
resolver: {
|
|
138
194
|
...resolvedConfig.resolver,
|
|
@@ -142,4 +198,4 @@ function withWeappTailwindcss(config, options = {}) {
|
|
|
142
198
|
};
|
|
143
199
|
}
|
|
144
200
|
//#endregion
|
|
145
|
-
export { VIRTUAL_MANIFEST_MODULE, getRegisteredManifest, getRegisteredVirtualModule, getVirtualModuleCode, getVirtualModuleCodeAsync, withWeappTailwindcss };
|
|
201
|
+
export { VIRTUAL_MANIFEST_MODULE, getManifestPathsForProjectRoot, getRegisteredManifest, getRegisteredManifestByPath, getRegisteredManifestByProjectRoot, getRegisteredVirtualModule, getVirtualModuleCode, getVirtualModuleCodeAsync, withWeappTailwindcss };
|
package/dist/tailwind.cjs
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
2
3
|
const require_compiler = require("./compiler.cjs");
|
|
4
|
+
let node_process = require("node:process");
|
|
5
|
+
node_process = require_rolldown_runtime.__toESM(node_process, 1);
|
|
3
6
|
let weapp_tailwindcss_generator = require("weapp-tailwindcss/generator");
|
|
4
7
|
//#region src/tailwind.ts
|
|
5
8
|
/**
|
|
6
9
|
* 使用 weapp-tailwindcss 的 Tailwind v4 generator 生成原始 CSS,再编译为 RN manifest。
|
|
7
10
|
*/
|
|
8
11
|
async function generateNativeStylesheet(options = {}) {
|
|
9
|
-
const source = await (0, weapp_tailwindcss_generator.resolveTailwindV4Source)(
|
|
12
|
+
const source = await (0, weapp_tailwindcss_generator.resolveTailwindV4Source)({
|
|
13
|
+
...options,
|
|
14
|
+
projectRoot: options.projectRoot ?? node_process.default.cwd()
|
|
15
|
+
});
|
|
10
16
|
const generator = (0, weapp_tailwindcss_generator.createWeappTailwindcssGenerator)(source);
|
|
11
17
|
const sourcePatterns = options.sourceGlobs?.map((pattern) => ({
|
|
12
18
|
base: source.projectRoot,
|
|
@@ -15,7 +21,7 @@ async function generateNativeStylesheet(options = {}) {
|
|
|
15
21
|
}));
|
|
16
22
|
const generatorCandidates = new Set(options.candidates ?? []);
|
|
17
23
|
for (const candidate of generatorCandidates) {
|
|
18
|
-
const base =
|
|
24
|
+
const base = require_compiler.baseClassName(candidate);
|
|
19
25
|
if (base && /^(?:ios|android|native):/.test(candidate)) generatorCandidates.add(base);
|
|
20
26
|
}
|
|
21
27
|
let generated = await generator.generate({
|
|
@@ -25,7 +31,7 @@ async function generateNativeStylesheet(options = {}) {
|
|
|
25
31
|
});
|
|
26
32
|
const platformBases = /* @__PURE__ */ new Set();
|
|
27
33
|
for (const candidate of generated.rawCandidates ?? []) {
|
|
28
|
-
const base =
|
|
34
|
+
const base = require_compiler.baseClassName(candidate);
|
|
29
35
|
if (base && /^(?:ios|android|native):/.test(candidate) && !generated.classSet.has(base)) platformBases.add(base);
|
|
30
36
|
}
|
|
31
37
|
if (platformBases.size) generated = await generator.generate({
|
|
@@ -36,7 +42,7 @@ async function generateNativeStylesheet(options = {}) {
|
|
|
36
42
|
const classSet = new Set(generated.classSet);
|
|
37
43
|
const requestedCandidates = new Set(options.candidates ?? []);
|
|
38
44
|
for (const candidate of generated.rawCandidates ?? []) {
|
|
39
|
-
const base =
|
|
45
|
+
const base = require_compiler.baseClassName(candidate);
|
|
40
46
|
if (base && generated.classSet.has(base) && /^(?:dark|ios|android|native):/.test(candidate)) requestedCandidates.add(candidate);
|
|
41
47
|
}
|
|
42
48
|
for (const candidate of requestedCandidates) classSet.add(candidate);
|
package/dist/tailwind.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { o as NativeStyleManifest } from "./types-BTxnqaRV.js";
|
|
|
2
2
|
import { TailwindV4SourceOptions } from "weapp-tailwindcss/generator";
|
|
3
3
|
//#region src/tailwind.d.ts
|
|
4
4
|
interface GenerateNativeStylesheetOptions extends TailwindV4SourceOptions {
|
|
5
|
+
/** Tailwind source 扫描根目录;用于 workspace 示例和 Metro 项目。 */
|
|
6
|
+
projectRoot?: string | undefined;
|
|
7
|
+
/** Tailwind CSS 入口文件;相对路径会按 projectRoot 解析。 */
|
|
8
|
+
cssEntries?: string[] | undefined;
|
|
5
9
|
candidates?: Iterable<string> | undefined;
|
|
6
10
|
sourceGlobs?: string[] | undefined;
|
|
7
11
|
}
|
package/dist/tailwind.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { addNativeVariantRules, compileNativeStylesheet, finalizeNativeManifest } from "./compiler.js";
|
|
1
|
+
import { addNativeVariantRules, baseClassName, compileNativeStylesheet, finalizeNativeManifest } from "./compiler.js";
|
|
2
|
+
import process from "node:process";
|
|
2
3
|
import { createWeappTailwindcssGenerator, resolveTailwindV4Source } from "weapp-tailwindcss/generator";
|
|
3
4
|
//#region src/tailwind.ts
|
|
4
5
|
/**
|
|
5
6
|
* 使用 weapp-tailwindcss 的 Tailwind v4 generator 生成原始 CSS,再编译为 RN manifest。
|
|
6
7
|
*/
|
|
7
8
|
async function generateNativeStylesheet(options = {}) {
|
|
8
|
-
const source = await resolveTailwindV4Source(
|
|
9
|
+
const source = await resolveTailwindV4Source({
|
|
10
|
+
...options,
|
|
11
|
+
projectRoot: options.projectRoot ?? process.cwd()
|
|
12
|
+
});
|
|
9
13
|
const generator = createWeappTailwindcssGenerator(source);
|
|
10
14
|
const sourcePatterns = options.sourceGlobs?.map((pattern) => ({
|
|
11
15
|
base: source.projectRoot,
|
|
@@ -14,7 +18,7 @@ async function generateNativeStylesheet(options = {}) {
|
|
|
14
18
|
}));
|
|
15
19
|
const generatorCandidates = new Set(options.candidates ?? []);
|
|
16
20
|
for (const candidate of generatorCandidates) {
|
|
17
|
-
const base = candidate
|
|
21
|
+
const base = baseClassName(candidate);
|
|
18
22
|
if (base && /^(?:ios|android|native):/.test(candidate)) generatorCandidates.add(base);
|
|
19
23
|
}
|
|
20
24
|
let generated = await generator.generate({
|
|
@@ -24,7 +28,7 @@ async function generateNativeStylesheet(options = {}) {
|
|
|
24
28
|
});
|
|
25
29
|
const platformBases = /* @__PURE__ */ new Set();
|
|
26
30
|
for (const candidate of generated.rawCandidates ?? []) {
|
|
27
|
-
const base = candidate
|
|
31
|
+
const base = baseClassName(candidate);
|
|
28
32
|
if (base && /^(?:ios|android|native):/.test(candidate) && !generated.classSet.has(base)) platformBases.add(base);
|
|
29
33
|
}
|
|
30
34
|
if (platformBases.size) generated = await generator.generate({
|
|
@@ -35,7 +39,7 @@ async function generateNativeStylesheet(options = {}) {
|
|
|
35
39
|
const classSet = new Set(generated.classSet);
|
|
36
40
|
const requestedCandidates = new Set(options.candidates ?? []);
|
|
37
41
|
for (const candidate of generated.rawCandidates ?? []) {
|
|
38
|
-
const base = candidate
|
|
42
|
+
const base = baseClassName(candidate);
|
|
39
43
|
if (base && generated.classSet.has(base) && /^(?:dark|ios|android|native):/.test(candidate)) requestedCandidates.add(candidate);
|
|
40
44
|
}
|
|
41
45
|
for (const candidate of requestedCandidates) classSet.add(candidate);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weapp-tailwindcss/react-native",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"description": "Tailwind CSS compiler and Expo Metro integration for cross-platform React Native apps. 面向跨端 React Native 应用的 Tailwind CSS 编译器与 Expo Metro 集成。",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"dependencies": {
|
|
93
93
|
"@babel/types": "^8.0.4",
|
|
94
94
|
"postcss": "^8.5.26",
|
|
95
|
-
"weapp-tailwindcss": "5.3.
|
|
95
|
+
"weapp-tailwindcss": "5.3.4"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
98
|
"@babel/core": "^8.0.1",
|