@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 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)) return parseNumber(trimmed, variables);
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.split(":").slice(0, -1)) {
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.split(":");
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
- const variableReference = /var\((--[\w-]+)/.exec(value)?.[1];
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
- let nextId = 0;
344
- for (const [className, rules] of Object.entries(manifest.rules)) for (const rule of rules) {
345
- const id = rule.id ?? `s${nextId++}`;
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;
@@ -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)) return parseNumber(trimmed, variables);
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.split(":").slice(0, -1)) {
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.split(":");
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
- const variableReference = /var\((--[\w-]+)/.exec(value)?.[1];
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
- let nextId = 0;
341
- for (const [className, rules] of Object.entries(manifest.rules)) for (const rule of rules) {
342
- const id = rule.id ?? `s${nextId++}`;
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
@@ -1,4 +1,3 @@
1
- import "react-native";
2
1
  //#region src/env.d.ts
3
2
  /** 供业务组件复用的 React Native className 属性。 */
4
3
  interface NativeClassNameProps {
package/dist/env.js CHANGED
@@ -1,2 +1 @@
1
- import "react-native";
2
1
  export {};
@@ -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 manifest = (metroId ? await require_metro.getRegisteredManifest(metroId) : void 0) ?? (manifestPath ? readManifest(manifestPath) : void 0);
26
- let source = data;
27
- if (manifest && /\.(?:[cm]?[jt]sx?|flow)$/i.test(filename) && !filename.replaceAll("\\", "/").includes("/node_modules/")) {
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.readFileSync(filename, "utf8"));
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 manifest = (metroId ? await getRegisteredManifest(metroId) : void 0) ?? (manifestPath ? readManifest(manifestPath) : void 0);
23
- let source = data;
24
- if (manifest && /\.(?:[cm]?[jt]sx?|flow)$/i.test(filename) && !filename.replaceAll("\\", "/").includes("/node_modules/")) {
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.readFileSync(filename, "utf8"));
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: node_path.default.join(node_os.default.tmpdir(), "weapp-tailwindcss-native", `${id}.manifest.json`),
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
- entry.manifest.warnings.push({ message: `生成 React Native manifest 失败:${error instanceof Error ? error.message : String(error)}` });
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
- return originalResolver?.(context, moduleName, platform) ?? context.resolveRequest?.(context, moduleName, platform);
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: path.join(os.tmpdir(), "weapp-tailwindcss-native", `${id}.manifest.json`),
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
- entry.manifest.warnings.push({ message: `生成 React Native manifest 失败:${error instanceof Error ? error.message : String(error)}` });
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
- return originalResolver?.(context, moduleName, platform) ?? context.resolveRequest?.(context, moduleName, platform);
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)(options);
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 = candidate.split(":").at(-1);
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 = candidate.split(":").at(-1);
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 = candidate.split(":").at(-1);
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);
@@ -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(options);
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.split(":").at(-1);
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.split(":").at(-1);
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.split(":").at(-1);
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",
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.2"
95
+ "weapp-tailwindcss": "5.3.4"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@babel/core": "^8.0.1",