@colorye/react-native-css 0.3.1 → 0.3.2

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.
Files changed (37) hide show
  1. package/crates/transformer/index.js +1 -0
  2. package/crates/transformer/transformer.darwin-arm64.node +0 -0
  3. package/crates/transformer/transformer.darwin-x64.node +0 -0
  4. package/crates/transformer/transformer.linux-x64-gnu.node +0 -0
  5. package/crates/transformer/transformer.node +0 -0
  6. package/crates/transformer/transformer.win32-x64-msvc.node +0 -0
  7. package/dist/babel.js +68 -159
  8. package/dist/index.d.ts +22 -8
  9. package/dist/interop.js +17 -5
  10. package/dist/metro.js +55 -0
  11. package/dist/transformer-runtime.js +170 -18
  12. package/dist/transformer.js +73 -20
  13. package/package.json +3 -1
  14. package/src/babel.js +71 -224
  15. package/src/index.d.ts +22 -8
  16. package/src/interop.js +13 -1
  17. package/src/metro.js +42 -0
  18. package/src/transformer-runtime.js +169 -15
  19. package/src/transformer.js +75 -15
  20. package/dist/features/build-transform.js +0 -575
  21. package/dist/features/css-calc.js +0 -539
  22. package/dist/features/css-media.js +0 -77
  23. package/dist/features/css-transform.js +0 -426
  24. package/dist/features/css-vars.js +0 -153
  25. package/dist/features/stylesheet.js +0 -45
  26. package/dist/utils/babel.js +0 -363
  27. package/dist/utils/css.js +0 -263
  28. package/dist/utils/helper.js +0 -11
  29. package/src/features/build-transform.js +0 -536
  30. package/src/features/css-calc.js +0 -490
  31. package/src/features/css-media.js +0 -78
  32. package/src/features/css-transform.js +0 -446
  33. package/src/features/css-vars.js +0 -138
  34. package/src/features/stylesheet.js +0 -29
  35. package/src/utils/babel.js +0 -425
  36. package/src/utils/css.js +0 -221
  37. package/src/utils/helper.js +0 -3
package/src/babel.js CHANGED
@@ -1,26 +1,21 @@
1
1
  import fs from "fs";
2
2
  import nodePath from "path";
3
- import {
4
- getInheritStyleExpression,
5
- getRootInheritStyleExpression,
6
- getStaticMergeExpression,
7
- getStyleExpression,
8
- inlineStaticAttributes,
9
- isFragmentElement,
10
- isImportOrRequire,
11
- isRootLevelJSXElement,
12
- tryGetStaticStyleInfo,
13
- } from "./utils/babel.js";
3
+ import { parseSync } from "@babel/core";
14
4
 
15
- const libRoot = nodePath.dirname(__filename);
5
+ let native = null;
6
+ try {
7
+ native = require("./native");
8
+ } catch {
9
+ try {
10
+ native = require("../crates/transformer");
11
+ } catch {}
12
+ }
16
13
 
17
- // Cache for loaded stylesheets
14
+ const libRoot = nodePath.dirname(__filename);
18
15
  const stylesheetCache = new Map();
19
16
 
20
- /**
21
- * Load pre-computed stylesheet JSON (generated by Metro transformer)
22
- */
23
17
  function loadStylesheet(cssPath, fileDir) {
18
+ if (!cssPath) return "{}";
24
19
  if (stylesheetCache.has(cssPath)) {
25
20
  return stylesheetCache.get(cssPath);
26
21
  }
@@ -29,242 +24,94 @@ function loadStylesheet(cssPath, fileDir) {
29
24
  const cwd = process.cwd();
30
25
  const cleanPath = cssPath.replace(/^@\//, "");
31
26
 
32
- // 1. Try project root relative paths
33
- const projectRootJson = nodePath.resolve(cwd, cleanPath.endsWith(".json") ? cleanPath : `${cleanPath}.json`);
34
- if (fs.existsSync(projectRootJson)) {
35
- const content = fs.readFileSync(projectRootJson, "utf-8");
36
- const stylesheet = JSON.parse(content);
37
- stylesheetCache.set(cssPath, stylesheet);
38
- return stylesheet;
39
- }
40
-
41
- const defaultIndexJson = nodePath.resolve(cwd, "index.css.json");
42
- if (fs.existsSync(defaultIndexJson)) {
43
- const content = fs.readFileSync(defaultIndexJson, "utf-8");
44
- const stylesheet = JSON.parse(content);
45
- stylesheetCache.set(cssPath, stylesheet);
46
- return stylesheet;
47
- }
48
-
49
- // 2. Try fileDir if provided
50
- if (fileDir) {
51
- const localJson = nodePath.resolve(fileDir, cssPath);
52
- if (fs.existsSync(localJson)) {
53
- const content = fs.readFileSync(localJson, "utf-8");
54
- const stylesheet = JSON.parse(content);
55
- stylesheetCache.set(cssPath, stylesheet);
56
- return stylesheet;
57
- }
58
- }
59
-
60
- // 3. Try resolving the module if it's a module specifier like @colorye/react-native-css/exported-stylesheet.json
61
- try {
62
- const resolvedModule = require.resolve(cssPath, { paths: [cwd, libRoot] });
63
- if (fs.existsSync(resolvedModule)) {
64
- const content = fs.readFileSync(resolvedModule, "utf-8");
65
- const stylesheet = JSON.parse(content);
66
- stylesheetCache.set(cssPath, stylesheet);
67
- return stylesheet;
27
+ const candidates = [
28
+ nodePath.resolve(cwd, cleanPath),
29
+ nodePath.resolve(cwd, cleanPath.endsWith(".json") ? cleanPath : `${cleanPath}.json`),
30
+ fileDir ? nodePath.resolve(fileDir, cssPath) : null,
31
+ nodePath.resolve(cwd, "index.css.json"),
32
+ nodePath.resolve(cwd, "index.css"),
33
+ nodePath.join(libRoot, "exported-stylesheet.json"),
34
+ ].filter(Boolean);
35
+
36
+ for (const p of candidates) {
37
+ if (fs.existsSync(p)) {
38
+ const content = fs.readFileSync(p, "utf-8");
39
+ let sheetJson = content;
40
+ if (p.endsWith(".css") && native && native.compileCss) {
41
+ sheetJson = native.compileCss(content);
42
+ }
43
+ stylesheetCache.set(cssPath, sheetJson);
44
+ return sheetJson;
68
45
  }
69
- } catch {}
70
-
71
- // 4. Try the exported-stylesheet.json from lib directory
72
- const exportedPath = nodePath.join(libRoot, "exported-stylesheet.json");
73
- if (fs.existsSync(exportedPath)) {
74
- const content = fs.readFileSync(exportedPath, "utf-8");
75
- const stylesheet = JSON.parse(content);
76
- stylesheetCache.set(cssPath, stylesheet);
77
- return stylesheet;
78
46
  }
47
+ } catch {}
79
48
 
80
- // 5. Try .json next to the CSS file
81
- const cssJsonPath = `${cssPath}.json`;
82
- if (fs.existsSync(cssJsonPath)) {
83
- const content = fs.readFileSync(cssJsonPath, "utf-8");
84
- const stylesheet = JSON.parse(content);
85
- stylesheetCache.set(cssPath, stylesheet);
86
- return stylesheet;
87
- }
88
-
89
- return null;
90
- } catch {
91
- return null;
92
- }
49
+ return "{}";
93
50
  }
94
51
 
95
- export default function ({ types: t }) {
52
+ export default function () {
96
53
  return {
97
54
  name: "@colorye/react-native-css",
98
55
  visitor: {
99
56
  Program: {
100
57
  enter(path, state) {
101
- if (!this.opts.css) {
102
- console.warn("No css file provided");
103
- return;
104
- }
105
-
106
58
  const { filename } = state.file.opts;
59
+ if (!filename || filename.includes("node_modules")) return;
60
+
107
61
  const options = {
108
- paths: this.opts.paths || [],
109
- excludes: this.opts.excludes || [],
110
- css: this.opts.css,
62
+ paths: this.opts?.paths || [],
63
+ excludes: this.opts?.excludes || [],
64
+ css: this.opts?.css,
111
65
  };
112
66
 
113
- // Normalize filename for cross-platform regex matching
114
67
  const normalizedFilename = filename.replace(/\\/g, "/");
115
68
  const regex = options.paths.map((p) => new RegExp(p));
116
69
  const excludesRegex = options.excludes.map((p) => new RegExp(p));
70
+
117
71
  if (
118
- !regex.some((re) => normalizedFilename.match(re) || filename.match(re)) ||
119
- excludesRegex.some((re) => normalizedFilename.match(re) || filename.match(re))
72
+ options.paths.length > 0 &&
73
+ !regex.some((re) => normalizedFilename.match(re) || filename.match(re))
120
74
  ) {
121
75
  return;
122
76
  }
77
+ if (excludesRegex.some((re) => normalizedFilename.match(re) || filename.match(re))) {
78
+ return;
79
+ }
123
80
 
124
- state.enabled = true;
81
+ if (!native || !native.transformJsx) return;
125
82
 
126
- // Load pre-computed stylesheet for static inlining
127
- state.stylesheetData = loadStylesheet(
83
+ const stylesheetJson = loadStylesheet(
128
84
  options.css,
129
85
  filename ? nodePath.dirname(filename) : null,
130
86
  );
131
-
132
- // Track which runtime helpers we need
133
- state.needsGetStyle = false;
134
- state.needsGetInheritStyle = false;
135
- state.needsMergeStyles = false;
136
-
137
- // Prepare runtime helper declarations
138
- state.stylesheetId = path.scope.generateUidIdentifier();
139
- state.stylesheet = t.variableDeclaration("var", [
140
- t.variableDeclarator(
141
- state.stylesheetId,
142
- t.callExpression(t.identifier("require"), [t.stringLiteral(options.css)]),
143
- ),
144
- ]);
145
-
146
- state.getStyleId = path.scope.generateUidIdentifier();
147
- state.getStyle = t.variableDeclaration("var", [
148
- t.variableDeclarator(
149
- state.getStyleId,
150
- t.memberExpression(
151
- t.memberExpression(
152
- t.callExpression(t.identifier("require"), [
153
- t.stringLiteral("@colorye/react-native-css/dist/transformer-runtime"),
154
- ]),
155
- t.identifier("default"),
156
- ),
157
- t.identifier("getStyle"),
158
- ),
159
- ),
160
- ]);
161
-
162
- state.getInheritStyleId = path.scope.generateUidIdentifier();
163
- state.getInheritStyle = t.variableDeclaration("var", [
164
- t.variableDeclarator(
165
- state.getInheritStyleId,
166
- t.memberExpression(
167
- t.memberExpression(
168
- t.callExpression(t.identifier("require"), [
169
- t.stringLiteral("@colorye/react-native-css/dist/transformer-runtime"),
170
- ]),
171
- t.identifier("default"),
172
- ),
173
- t.identifier("getInheritStyle"),
174
- ),
175
- ),
176
- ]);
177
-
178
- state.mergeStylesId = path.scope.generateUidIdentifier();
179
- state.mergeStyles = t.variableDeclaration("var", [
180
- t.variableDeclarator(
181
- state.mergeStylesId,
182
- t.memberExpression(
183
- t.memberExpression(
184
- t.callExpression(t.identifier("require"), [
185
- t.stringLiteral("@colorye/react-native-css/dist/transformer-runtime"),
186
- ]),
187
- t.identifier("default"),
188
- ),
189
- t.identifier("mergeStyles"),
190
- ),
191
- ),
192
- ]);
193
- },
194
- exit(path, state) {
195
- if (!state.enabled) return;
196
-
197
- const helpers = [];
198
-
199
- // Only inject helpers that are actually used
200
- if (state.needsGetStyle) {
201
- helpers.push(state.stylesheet);
202
- helpers.push(state.getStyle);
203
- }
204
- if (state.needsGetInheritStyle) {
205
- helpers.push(state.getInheritStyle);
206
- }
207
- if (state.needsMergeStyles) {
208
- helpers.push(state.mergeStyles);
209
- }
210
-
211
- if (helpers.length === 0) return;
212
-
213
- const lastImport = path
214
- .get("body")
215
- .filter((p) => isImportOrRequire(p))
216
- .pop();
217
-
218
- helpers
219
- .reverse()
220
- .forEach((node) =>
221
- lastImport ? lastImport.insertAfter(node) : path.unshiftContainer("body", node),
222
- );
223
- },
224
- },
225
- JSXElement(path, state) {
226
- if (!state.enabled) return;
227
- if (isFragmentElement(t, path.node.openingElement.name)) return;
228
-
229
- // Perform zero-runtime static style inlining for static classes
230
- inlineStaticAttributes(path, state, t);
231
-
232
- const openingElement = path.node.openingElement;
233
- const isRootLevel = isRootLevelJSXElement(path);
234
-
235
- const shouldCombineStyles = openingElement.attributes.some(
236
- (attr) => attr.name?.name === "__combinedStyles",
237
- );
238
-
239
- if (shouldCombineStyles) {
240
- let styleExpressions;
241
-
242
- // Try static inlining first (if stylesheet loaded and class is static)
243
- if (state.stylesheetData) {
244
- const staticInfo = tryGetStaticStyleInfo(path, state, t);
245
-
246
- if (staticInfo) {
247
- // Static class! Use lightweight mergeStyles
248
- styleExpressions = getStaticMergeExpression(path, state, t, staticInfo);
249
- state.needsMergeStyles = true;
87
+ const rawCode = state.file.code;
88
+
89
+ try {
90
+ const res = native.transformJsx(rawCode, {
91
+ filename,
92
+ stylesheetJson,
93
+ sourceMaps: false,
94
+ });
95
+
96
+ if (res && res.code && res.code !== rawCode) {
97
+ const newAst = parseSync(res.code, {
98
+ filename,
99
+ parserOpts: {
100
+ plugins: ["jsx", "typescript"],
101
+ },
102
+ configFile: false,
103
+ babelrc: false,
104
+ });
105
+
106
+ if (newAst && newAst.program) {
107
+ path.replaceWith(newAst.program);
108
+ path.stop();
109
+ }
250
110
  }
111
+ } catch {
112
+ // Silently fall back if native transformation fails
251
113
  }
252
-
253
- if (!styleExpressions) {
254
- // Fall back to full runtime getStyle
255
- state.needsGetStyle = true;
256
- styleExpressions = getStyleExpression(path, state, t);
257
- }
258
-
259
- const style = openingElement.attributes.find((attr) => attr.name?.name === "style");
260
- if (style) {
261
- style.value = t.jsxExpressionContainer(styleExpressions);
262
- } else {
263
- openingElement.attributes.push(
264
- t.jsxAttribute(t.jsxIdentifier("style"), t.jsxExpressionContainer(styleExpressions)),
265
- );
266
- }
267
- }
114
+ },
268
115
  },
269
116
  },
270
117
  };
package/src/index.d.ts CHANGED
@@ -13,8 +13,20 @@ export interface GroupState {
13
13
  focus?: boolean;
14
14
  }
15
15
 
16
+ export interface InheritedStyles {
17
+ color?: string;
18
+ fontSize?: number;
19
+ fontWeight?: string;
20
+ fontFamily?: string;
21
+ letterSpacing?: number;
22
+ lineHeight?: number;
23
+ textAlign?: string;
24
+ textTransform?: string;
25
+ [key: string]: any;
26
+ }
27
+
16
28
  export const GroupContext: React.Context<GroupState>;
17
- export const InheritContext: React.Context<Record<string, any> | undefined>;
29
+ export const InheritContext: React.Context<InheritedStyles | undefined>;
18
30
 
19
31
  export function setGlobalStylesheet(sheet: Record<string, any>): void;
20
32
  export function getGlobalStylesheet(): Record<string, any>;
@@ -33,7 +45,7 @@ export function getGlobalStylesheet(): Record<string, any>;
33
45
  * });
34
46
  * ```
35
47
  */
36
- export function cssInterop<P extends object>(
48
+ export function cssInterop<T = any, P extends object = any>(
37
49
  Component: React.ComponentType<P>,
38
50
  mapping?: StyleMapping
39
51
  ): React.ForwardRefExoticComponent<
@@ -41,13 +53,13 @@ export function cssInterop<P extends object>(
41
53
  className?: string;
42
54
  contentContainerClassName?: string;
43
55
  inheritStyle?: any;
44
- } & React.RefAttributes<any>
56
+ } & React.RefAttributes<T>
45
57
  >;
46
58
 
47
59
  /**
48
60
  * Alias for cssInterop.
49
61
  */
50
- export function remapProps<P extends object>(
62
+ export function remapProps<T = any, P extends object = any>(
51
63
  Component: React.ComponentType<P>,
52
64
  mapping: StyleMapping
53
65
  ): React.ForwardRefExoticComponent<
@@ -55,7 +67,7 @@ export function remapProps<P extends object>(
55
67
  className?: string;
56
68
  contentContainerClassName?: string;
57
69
  inheritStyle?: any;
58
- } & React.RefAttributes<any>
70
+ } & React.RefAttributes<T>
59
71
  >;
60
72
 
61
73
  export namespace Runtime {
@@ -73,6 +85,11 @@ export function getStylesheet(css: string, filename?: string): string;
73
85
  export function writeStylesheetJSON(content: string, filename?: string): void;
74
86
  export function transform(args: { src: string; filename: string; options?: any }): any;
75
87
 
88
+ /**
89
+ * Plug-and-play zero-configuration helper for Expo and React Native Metro bundler.
90
+ */
91
+ export function withReactNativeCss<T = any>(metroConfig: T, options?: { input?: string }): T;
92
+
76
93
  declare const _default: {
77
94
  cssInterop: typeof cssInterop;
78
95
  remapProps: typeof remapProps;
@@ -81,9 +98,6 @@ declare const _default: {
81
98
  GroupContext: typeof GroupContext;
82
99
  InheritContext: typeof InheritContext;
83
100
  Runtime: typeof Runtime;
84
- getStylesheet: typeof getStylesheet;
85
- transform: typeof transform;
86
- writeStylesheetJSON: typeof writeStylesheetJSON;
87
101
  };
88
102
 
89
103
  export default _default;
package/src/interop.js CHANGED
@@ -1,6 +1,18 @@
1
1
  import React, { createContext, forwardRef, useContext } from "react";
2
2
  import Runtime from "./transformer-runtime.js";
3
3
 
4
+ function shallowEqual(a, b) {
5
+ if (a === b) return true;
6
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
7
+ const keysA = Object.keys(a);
8
+ const keysB = Object.keys(b);
9
+ if (keysA.length !== keysB.length) return false;
10
+ for (const k of keysA) {
11
+ if (a[k] !== b[k]) return false;
12
+ }
13
+ return true;
14
+ }
15
+
4
16
  export const InheritContext = createContext(undefined);
5
17
  export const GroupContext = createContext({ pressed: false, hovered: false, focus: false });
6
18
 
@@ -249,7 +261,7 @@ export function cssInterop(Component, mapping = { className: "style" }) {
249
261
  );
250
262
  }
251
263
 
252
- if (currentInherit && currentInherit !== parentInherit) {
264
+ if (currentInherit && !shallowEqual(currentInherit, parentInherit)) {
253
265
  return React.createElement(InheritContext.Provider, { value: currentInherit }, element);
254
266
  }
255
267
 
package/src/metro.js ADDED
@@ -0,0 +1,42 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * withReactNativeCss
6
+ *
7
+ * Plug-and-play zero-configuration helper for Expo and React Native Metro bundler.
8
+ *
9
+ * Automatically:
10
+ * 1. Configures Metro to handle .css files via @colorye/react-native-css/transformer.
11
+ * 2. Connects the high-performance Rust SWC native transformer for JSX/TSX.
12
+ * 3. Adds 'css' to sourceExts.
13
+ * 4. In-process caches and synchronizes stylesheet JSON without manual setup.
14
+ *
15
+ * @param {import('metro-config').MetroConfig} metroConfig
16
+ * @param {Object} [options]
17
+ * @param {string} [options.input] - Input CSS path relative to project root (default: 'src/assets/styles/index.css' or 'index.css')
18
+ * @returns {import('metro-config').MetroConfig}
19
+ */
20
+ function withReactNativeCss(metroConfig, options = {}) {
21
+ const config = { ...metroConfig };
22
+
23
+ // Ensure resolver & transformer objects exist
24
+ config.resolver = config.resolver || {};
25
+ config.transformer = config.transformer || {};
26
+
27
+ // 1. Add 'css' to sourceExts if missing
28
+ const currentSourceExts = config.resolver.sourceExts || [];
29
+ if (!currentSourceExts.includes("css")) {
30
+ config.resolver.sourceExts = [...currentSourceExts, "css"];
31
+ }
32
+
33
+ // 2. Wrap or assign babelTransformerPath
34
+ const defaultTransformerPath = config.transformer.babelTransformerPath || require.resolve("./transformer");
35
+ config.transformer.babelTransformerPath = defaultTransformerPath;
36
+
37
+ return config;
38
+ }
39
+
40
+ module.exports = {
41
+ withReactNativeCss,
42
+ };