@weapp-tailwindcss/react-native 0.1.0
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/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/babel.cjs +112 -0
- package/dist/babel.d.ts +21 -0
- package/dist/babel.js +110 -0
- package/dist/compiler.cjs +383 -0
- package/dist/compiler.d.ts +8 -0
- package/dist/compiler.js +378 -0
- package/dist/env.cjs +1 -0
- package/dist/env.d.ts +14 -0
- package/dist/env.js +2 -0
- package/dist/index.cjs +22 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/metro-transformer.cjs +52 -0
- package/dist/metro-transformer.d.ts +5 -0
- package/dist/metro-transformer.js +49 -0
- package/dist/metro.cjs +156 -0
- package/dist/metro.d.ts +39 -0
- package/dist/metro.js +145 -0
- package/dist/rolldown-runtime-D6vf50IK.cjs +28 -0
- package/dist/runtime.cjs +187 -0
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.js +179 -0
- package/dist/tailwind.cjs +50 -0
- package/dist/tailwind.d.ts +13 -0
- package/dist/tailwind.js +49 -0
- package/dist/types-BTxnqaRV.d.ts +48 -0
- package/package.json +109 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 ice breaker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @weapp-tailwindcss/react-native
|
|
2
|
+
|
|
3
|
+
面向 React Native 与 Expo 的 Tailwind CSS v4 编译器。它复用 `weapp-tailwindcss` 的 source 扫描与候选生成,将原始 CSS 编译为可序列化的 React Native style manifest,不引入 NativeWind 或 `react-native-css` 运行时。
|
|
4
|
+
|
|
5
|
+
## Expo 配置
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @weapp-tailwindcss/react-native tailwindcss
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// metro.config.js
|
|
13
|
+
const { getDefaultConfig } = require('expo/metro-config')
|
|
14
|
+
const { withWeappTailwindcss } = require('@weapp-tailwindcss/react-native/metro')
|
|
15
|
+
|
|
16
|
+
const config = getDefaultConfig(__dirname)
|
|
17
|
+
|
|
18
|
+
module.exports = withWeappTailwindcss(config, {
|
|
19
|
+
input: './global.css',
|
|
20
|
+
sourceGlobs: ['./src/**/*.{js,jsx,ts,tsx}'],
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Expo 只需要配置一次 Metro。Metro 会扫描 source、生成精确候选集合和 manifest,并自动把 Babel JSX transform 注入原有 Expo transformer;不需要再维护第二份 `classNameSet`。非 Expo 或定制 Metro 场景仍可显式使用 `@weapp-tailwindcss/react-native/babel`。
|
|
25
|
+
|
|
26
|
+
静态 `className` 会编译为预生成的 StyleSheet lookup,不在 render 中调用 `tw()`;动态值才使用 `tw(value)`。普通 inline `style` 覆盖 Tailwind class,`!important` class 覆盖 inline style。不支持的 CSS 声明会写入 manifest 的 `warnings`,不会静默产出错误的 RN style。
|
|
27
|
+
|
|
28
|
+
公开入口:`compiler`、`babel`、`metro`、`runtime`、`tailwind` 与中性类型入口 `env`。
|
package/dist/babel.cjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
|
|
2
|
+
let _babel_types = require("@babel/types");
|
|
3
|
+
_babel_types = require_rolldown_runtime.__toESM(_babel_types, 1);
|
|
4
|
+
//#region src/babel.ts
|
|
5
|
+
function classTokens(value) {
|
|
6
|
+
return value.split(/\s+/).filter(Boolean);
|
|
7
|
+
}
|
|
8
|
+
function ensureRuntimeIdentifier(program, state, kind) {
|
|
9
|
+
const identifiers = state.runtimeIdentifiers ??= {};
|
|
10
|
+
if (identifiers[kind]) return identifiers[kind];
|
|
11
|
+
const moduleName = state.opts.runtimeModule ?? "@weapp-tailwindcss/react-native/runtime";
|
|
12
|
+
const local = program.scope.generateUidIdentifier(kind === "static" ? "twStatic" : kind === "compose" ? "twCompose" : "tw");
|
|
13
|
+
identifiers[kind] = local;
|
|
14
|
+
if (!state.virtualImported) {
|
|
15
|
+
program.unshiftContainer("body", _babel_types.importDeclaration([], _babel_types.stringLiteral("@weapp-tailwindcss/react-native/virtual")));
|
|
16
|
+
state.virtualImported = true;
|
|
17
|
+
}
|
|
18
|
+
program.unshiftContainer("body", _babel_types.importDeclaration([_babel_types.importSpecifier(local, _babel_types.identifier(kind === "static" ? "getStaticStyle" : kind === "compose" ? "composeStyle" : "tw"))], _babel_types.stringLiteral(moduleName)));
|
|
19
|
+
return local;
|
|
20
|
+
}
|
|
21
|
+
function shouldSkipFile(filename) {
|
|
22
|
+
if (!filename) return false;
|
|
23
|
+
const normalized = filename.replaceAll("\\", "/");
|
|
24
|
+
const basename = normalized.slice(normalized.lastIndexOf("/") + 1).toLowerCase();
|
|
25
|
+
return normalized.includes("/node_modules/") || /^(?:react|react-native)(?:[.-]|$)/.test(basename);
|
|
26
|
+
}
|
|
27
|
+
function hasKnownStaticTokens(value, classNameSet) {
|
|
28
|
+
if (!classNameSet) return false;
|
|
29
|
+
return classTokens(value).every((token) => classNameSet.has(token));
|
|
30
|
+
}
|
|
31
|
+
function styleExpression(attribute) {
|
|
32
|
+
if (!attribute) return;
|
|
33
|
+
const value = attribute.value;
|
|
34
|
+
if (!value) return;
|
|
35
|
+
if (_babel_types.isJSXExpressionContainer(value)) return _babel_types.isJSXEmptyExpression(value.expression) ? void 0 : value.expression;
|
|
36
|
+
if (_babel_types.isStringLiteral(value)) return value;
|
|
37
|
+
}
|
|
38
|
+
function composeExpression(program, state, tailwind, inline) {
|
|
39
|
+
if (!inline) return tailwind;
|
|
40
|
+
return _babel_types.callExpression(ensureRuntimeIdentifier(program, state, "compose"), [tailwind, inline]);
|
|
41
|
+
}
|
|
42
|
+
function staticStyleExpression(program, state, value) {
|
|
43
|
+
const ids = classTokens(value).flatMap((token) => state.opts.staticStyleMap?.[token] ?? [token]);
|
|
44
|
+
const staticIdentifier = ensureRuntimeIdentifier(program, state, "static");
|
|
45
|
+
return _babel_types.callExpression(staticIdentifier, [_babel_types.arrayExpression(ids.map((id) => _babel_types.stringLiteral(id)))]);
|
|
46
|
+
}
|
|
47
|
+
function dynamicStyleExpression(program, state, value) {
|
|
48
|
+
return _babel_types.callExpression(ensureRuntimeIdentifier(program, state, "tw"), [value]);
|
|
49
|
+
}
|
|
50
|
+
function applyStyleAttribute(opening, expression) {
|
|
51
|
+
const existing = opening.node.attributes.find((attribute) => _babel_types.isJSXAttribute(attribute) && attribute.name.name === "style");
|
|
52
|
+
if (existing && _babel_types.isJSXAttribute(existing)) {
|
|
53
|
+
existing.value = _babel_types.jsxExpressionContainer(expression);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
opening.node.attributes.push(_babel_types.jsxAttribute(_babel_types.jsxIdentifier("style"), _babel_types.jsxExpressionContainer(expression)));
|
|
57
|
+
}
|
|
58
|
+
function propertyName(node) {
|
|
59
|
+
if (_babel_types.isIdentifier(node.key) && !node.computed) return node.key.name;
|
|
60
|
+
if (_babel_types.isStringLiteral(node.key)) return node.key.value;
|
|
61
|
+
}
|
|
62
|
+
function applyCreateElementProps(props, program, state) {
|
|
63
|
+
const classProperty = props.properties.find((property) => _babel_types.isObjectProperty(property) && propertyName(property) === "className");
|
|
64
|
+
if (!classProperty || !_babel_types.isObjectProperty(classProperty)) return;
|
|
65
|
+
const value = classProperty.value;
|
|
66
|
+
const classSet = state.opts.classNameSet ? new Set(state.opts.classNameSet) : void 0;
|
|
67
|
+
const styleProperty = props.properties.find((property) => _babel_types.isObjectProperty(property) && propertyName(property) === "style");
|
|
68
|
+
const inline = styleProperty && _babel_types.isObjectProperty(styleProperty) ? styleProperty.value : void 0;
|
|
69
|
+
let style;
|
|
70
|
+
if (_babel_types.isStringLiteral(value)) {
|
|
71
|
+
if (!hasKnownStaticTokens(value.value, classSet)) return;
|
|
72
|
+
style = composeExpression(program, state, staticStyleExpression(program, state, value.value), inline);
|
|
73
|
+
} else if (_babel_types.isExpression(value)) style = composeExpression(program, state, dynamicStyleExpression(program, state, value), inline);
|
|
74
|
+
else return;
|
|
75
|
+
if (styleProperty && _babel_types.isObjectProperty(styleProperty)) styleProperty.value = style;
|
|
76
|
+
else props.properties.push(_babel_types.objectProperty(_babel_types.identifier("style"), style));
|
|
77
|
+
props.properties = props.properties.filter((property) => property !== classProperty);
|
|
78
|
+
}
|
|
79
|
+
function weappReactNativeBabel() {
|
|
80
|
+
return {
|
|
81
|
+
name: "weapp-tailwindcss-react-native",
|
|
82
|
+
visitor: {
|
|
83
|
+
JSXOpeningElement(path, state) {
|
|
84
|
+
if (shouldSkipFile(state.filename)) return;
|
|
85
|
+
const program = path.findParent((parent) => parent.isProgram());
|
|
86
|
+
if (!program?.isProgram()) return;
|
|
87
|
+
const classAttribute = path.node.attributes.find((attribute) => _babel_types.isJSXAttribute(attribute) && attribute.name.name === "className");
|
|
88
|
+
if (!classAttribute || !_babel_types.isJSXAttribute(classAttribute)) return;
|
|
89
|
+
if (_babel_types.isStringLiteral(classAttribute.value)) {
|
|
90
|
+
if (!hasKnownStaticTokens(classAttribute.value.value, state.opts.classNameSet ? new Set(state.opts.classNameSet) : void 0)) return;
|
|
91
|
+
applyStyleAttribute(path, composeExpression(program, state, staticStyleExpression(program, state, classAttribute.value.value), styleExpression(path.node.attributes.find((attribute) => _babel_types.isJSXAttribute(attribute) && attribute.name.name === "style"))));
|
|
92
|
+
path.node.attributes = path.node.attributes.filter((attribute) => attribute !== classAttribute);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (_babel_types.isJSXExpressionContainer(classAttribute.value) && classAttribute.value.expression) {
|
|
96
|
+
const existing = path.node.attributes.find((attribute) => _babel_types.isJSXAttribute(attribute) && attribute.name.name === "style");
|
|
97
|
+
applyStyleAttribute(path, composeExpression(program, state, dynamicStyleExpression(program, state, classAttribute.value.expression), styleExpression(existing)));
|
|
98
|
+
path.node.attributes = path.node.attributes.filter((attribute) => attribute !== classAttribute);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
CallExpression(path, state) {
|
|
102
|
+
if (shouldSkipFile(state.filename)) return;
|
|
103
|
+
const callee = path.node.callee;
|
|
104
|
+
if (!(_babel_types.isMemberExpression(callee) && !callee.computed && _babel_types.isIdentifier(callee.object, { name: "React" }) && _babel_types.isIdentifier(callee.property, { name: "createElement" })) || !_babel_types.isObjectExpression(path.node.arguments[1])) return;
|
|
105
|
+
const program = path.findParent((parent) => parent.isProgram());
|
|
106
|
+
if (program?.isProgram()) applyCreateElementProps(path.node.arguments[1], program, state);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
module.exports = weappReactNativeBabel;
|
package/dist/babel.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PluginObject, PluginPass } from "@babel/core";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
//#region src/babel.d.ts
|
|
4
|
+
interface WeappReactNativeBabelOptions {
|
|
5
|
+
classNameSet?: Iterable<string> | undefined;
|
|
6
|
+
/** Metro 生成的 token -> StyleSheet ID lookup。 */
|
|
7
|
+
staticStyleMap?: Record<string, string[]> | undefined;
|
|
8
|
+
runtimeModule?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
interface PluginState extends PluginPass {
|
|
11
|
+
opts: WeappReactNativeBabelOptions;
|
|
12
|
+
runtimeIdentifiers?: {
|
|
13
|
+
tw?: t.Identifier;
|
|
14
|
+
static?: t.Identifier;
|
|
15
|
+
compose?: t.Identifier;
|
|
16
|
+
};
|
|
17
|
+
virtualImported?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare function weappReactNativeBabel(): PluginObject<PluginState>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { WeappReactNativeBabelOptions, weappReactNativeBabel as default };
|
package/dist/babel.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as t from "@babel/types";
|
|
2
|
+
//#region src/babel.ts
|
|
3
|
+
function classTokens(value) {
|
|
4
|
+
return value.split(/\s+/).filter(Boolean);
|
|
5
|
+
}
|
|
6
|
+
function ensureRuntimeIdentifier(program, state, kind) {
|
|
7
|
+
const identifiers = state.runtimeIdentifiers ??= {};
|
|
8
|
+
if (identifiers[kind]) return identifiers[kind];
|
|
9
|
+
const moduleName = state.opts.runtimeModule ?? "@weapp-tailwindcss/react-native/runtime";
|
|
10
|
+
const local = program.scope.generateUidIdentifier(kind === "static" ? "twStatic" : kind === "compose" ? "twCompose" : "tw");
|
|
11
|
+
identifiers[kind] = local;
|
|
12
|
+
if (!state.virtualImported) {
|
|
13
|
+
program.unshiftContainer("body", t.importDeclaration([], t.stringLiteral("@weapp-tailwindcss/react-native/virtual")));
|
|
14
|
+
state.virtualImported = true;
|
|
15
|
+
}
|
|
16
|
+
program.unshiftContainer("body", t.importDeclaration([t.importSpecifier(local, t.identifier(kind === "static" ? "getStaticStyle" : kind === "compose" ? "composeStyle" : "tw"))], t.stringLiteral(moduleName)));
|
|
17
|
+
return local;
|
|
18
|
+
}
|
|
19
|
+
function shouldSkipFile(filename) {
|
|
20
|
+
if (!filename) return false;
|
|
21
|
+
const normalized = filename.replaceAll("\\", "/");
|
|
22
|
+
const basename = normalized.slice(normalized.lastIndexOf("/") + 1).toLowerCase();
|
|
23
|
+
return normalized.includes("/node_modules/") || /^(?:react|react-native)(?:[.-]|$)/.test(basename);
|
|
24
|
+
}
|
|
25
|
+
function hasKnownStaticTokens(value, classNameSet) {
|
|
26
|
+
if (!classNameSet) return false;
|
|
27
|
+
return classTokens(value).every((token) => classNameSet.has(token));
|
|
28
|
+
}
|
|
29
|
+
function styleExpression(attribute) {
|
|
30
|
+
if (!attribute) return;
|
|
31
|
+
const value = attribute.value;
|
|
32
|
+
if (!value) return;
|
|
33
|
+
if (t.isJSXExpressionContainer(value)) return t.isJSXEmptyExpression(value.expression) ? void 0 : value.expression;
|
|
34
|
+
if (t.isStringLiteral(value)) return value;
|
|
35
|
+
}
|
|
36
|
+
function composeExpression(program, state, tailwind, inline) {
|
|
37
|
+
if (!inline) return tailwind;
|
|
38
|
+
return t.callExpression(ensureRuntimeIdentifier(program, state, "compose"), [tailwind, inline]);
|
|
39
|
+
}
|
|
40
|
+
function staticStyleExpression(program, state, value) {
|
|
41
|
+
const ids = classTokens(value).flatMap((token) => state.opts.staticStyleMap?.[token] ?? [token]);
|
|
42
|
+
const staticIdentifier = ensureRuntimeIdentifier(program, state, "static");
|
|
43
|
+
return t.callExpression(staticIdentifier, [t.arrayExpression(ids.map((id) => t.stringLiteral(id)))]);
|
|
44
|
+
}
|
|
45
|
+
function dynamicStyleExpression(program, state, value) {
|
|
46
|
+
return t.callExpression(ensureRuntimeIdentifier(program, state, "tw"), [value]);
|
|
47
|
+
}
|
|
48
|
+
function applyStyleAttribute(opening, expression) {
|
|
49
|
+
const existing = opening.node.attributes.find((attribute) => t.isJSXAttribute(attribute) && attribute.name.name === "style");
|
|
50
|
+
if (existing && t.isJSXAttribute(existing)) {
|
|
51
|
+
existing.value = t.jsxExpressionContainer(expression);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
opening.node.attributes.push(t.jsxAttribute(t.jsxIdentifier("style"), t.jsxExpressionContainer(expression)));
|
|
55
|
+
}
|
|
56
|
+
function propertyName(node) {
|
|
57
|
+
if (t.isIdentifier(node.key) && !node.computed) return node.key.name;
|
|
58
|
+
if (t.isStringLiteral(node.key)) return node.key.value;
|
|
59
|
+
}
|
|
60
|
+
function applyCreateElementProps(props, program, state) {
|
|
61
|
+
const classProperty = props.properties.find((property) => t.isObjectProperty(property) && propertyName(property) === "className");
|
|
62
|
+
if (!classProperty || !t.isObjectProperty(classProperty)) return;
|
|
63
|
+
const value = classProperty.value;
|
|
64
|
+
const classSet = state.opts.classNameSet ? new Set(state.opts.classNameSet) : void 0;
|
|
65
|
+
const styleProperty = props.properties.find((property) => t.isObjectProperty(property) && propertyName(property) === "style");
|
|
66
|
+
const inline = styleProperty && t.isObjectProperty(styleProperty) ? styleProperty.value : void 0;
|
|
67
|
+
let style;
|
|
68
|
+
if (t.isStringLiteral(value)) {
|
|
69
|
+
if (!hasKnownStaticTokens(value.value, classSet)) return;
|
|
70
|
+
style = composeExpression(program, state, staticStyleExpression(program, state, value.value), inline);
|
|
71
|
+
} else if (t.isExpression(value)) style = composeExpression(program, state, dynamicStyleExpression(program, state, value), inline);
|
|
72
|
+
else return;
|
|
73
|
+
if (styleProperty && t.isObjectProperty(styleProperty)) styleProperty.value = style;
|
|
74
|
+
else props.properties.push(t.objectProperty(t.identifier("style"), style));
|
|
75
|
+
props.properties = props.properties.filter((property) => property !== classProperty);
|
|
76
|
+
}
|
|
77
|
+
function weappReactNativeBabel() {
|
|
78
|
+
return {
|
|
79
|
+
name: "weapp-tailwindcss-react-native",
|
|
80
|
+
visitor: {
|
|
81
|
+
JSXOpeningElement(path, state) {
|
|
82
|
+
if (shouldSkipFile(state.filename)) return;
|
|
83
|
+
const program = path.findParent((parent) => parent.isProgram());
|
|
84
|
+
if (!program?.isProgram()) return;
|
|
85
|
+
const classAttribute = path.node.attributes.find((attribute) => t.isJSXAttribute(attribute) && attribute.name.name === "className");
|
|
86
|
+
if (!classAttribute || !t.isJSXAttribute(classAttribute)) return;
|
|
87
|
+
if (t.isStringLiteral(classAttribute.value)) {
|
|
88
|
+
if (!hasKnownStaticTokens(classAttribute.value.value, state.opts.classNameSet ? new Set(state.opts.classNameSet) : void 0)) return;
|
|
89
|
+
applyStyleAttribute(path, composeExpression(program, state, staticStyleExpression(program, state, classAttribute.value.value), styleExpression(path.node.attributes.find((attribute) => t.isJSXAttribute(attribute) && attribute.name.name === "style"))));
|
|
90
|
+
path.node.attributes = path.node.attributes.filter((attribute) => attribute !== classAttribute);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (t.isJSXExpressionContainer(classAttribute.value) && classAttribute.value.expression) {
|
|
94
|
+
const existing = path.node.attributes.find((attribute) => t.isJSXAttribute(attribute) && attribute.name.name === "style");
|
|
95
|
+
applyStyleAttribute(path, composeExpression(program, state, dynamicStyleExpression(program, state, classAttribute.value.expression), styleExpression(existing)));
|
|
96
|
+
path.node.attributes = path.node.attributes.filter((attribute) => attribute !== classAttribute);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
CallExpression(path, state) {
|
|
100
|
+
if (shouldSkipFile(state.filename)) return;
|
|
101
|
+
const callee = path.node.callee;
|
|
102
|
+
if (!(t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.object, { name: "React" }) && t.isIdentifier(callee.property, { name: "createElement" })) || !t.isObjectExpression(path.node.arguments[1])) return;
|
|
103
|
+
const program = path.findParent((parent) => parent.isProgram());
|
|
104
|
+
if (program?.isProgram()) applyCreateElementProps(path.node.arguments[1], program, state);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { weappReactNativeBabel as default };
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("./rolldown-runtime-D6vf50IK.cjs");
|
|
3
|
+
let postcss = require("postcss");
|
|
4
|
+
postcss = require_rolldown_runtime.__toESM(postcss, 1);
|
|
5
|
+
//#region src/compiler.ts
|
|
6
|
+
const CLASS_SELECTOR_RE = /\.((?:\\.|[^\s.#:[>+~])+)/g;
|
|
7
|
+
const COLOR_PROPERTIES = /* @__PURE__ */ new Set([
|
|
8
|
+
"color",
|
|
9
|
+
"backgroundColor",
|
|
10
|
+
"borderColor",
|
|
11
|
+
"borderTopColor",
|
|
12
|
+
"borderRightColor",
|
|
13
|
+
"borderBottomColor",
|
|
14
|
+
"borderLeftColor",
|
|
15
|
+
"shadowColor",
|
|
16
|
+
"textDecorationColor"
|
|
17
|
+
]);
|
|
18
|
+
const NUMERIC_PROPERTIES = /* @__PURE__ */ new Set([
|
|
19
|
+
"aspectRatio",
|
|
20
|
+
"borderBottomWidth",
|
|
21
|
+
"borderLeftWidth",
|
|
22
|
+
"borderRadius",
|
|
23
|
+
"borderRightWidth",
|
|
24
|
+
"borderTopLeftRadius",
|
|
25
|
+
"borderTopRightRadius",
|
|
26
|
+
"borderTopWidth",
|
|
27
|
+
"borderWidth",
|
|
28
|
+
"bottom",
|
|
29
|
+
"elevation",
|
|
30
|
+
"flex",
|
|
31
|
+
"flexBasis",
|
|
32
|
+
"flexGrow",
|
|
33
|
+
"flexShrink",
|
|
34
|
+
"fontSize",
|
|
35
|
+
"gap",
|
|
36
|
+
"height",
|
|
37
|
+
"left",
|
|
38
|
+
"letterSpacing",
|
|
39
|
+
"lineHeight",
|
|
40
|
+
"margin",
|
|
41
|
+
"marginBottom",
|
|
42
|
+
"marginHorizontal",
|
|
43
|
+
"marginLeft",
|
|
44
|
+
"marginRight",
|
|
45
|
+
"marginTop",
|
|
46
|
+
"marginVertical",
|
|
47
|
+
"maxHeight",
|
|
48
|
+
"maxWidth",
|
|
49
|
+
"minHeight",
|
|
50
|
+
"minWidth",
|
|
51
|
+
"opacity",
|
|
52
|
+
"padding",
|
|
53
|
+
"paddingBottom",
|
|
54
|
+
"paddingHorizontal",
|
|
55
|
+
"paddingLeft",
|
|
56
|
+
"paddingRight",
|
|
57
|
+
"paddingTop",
|
|
58
|
+
"paddingVertical",
|
|
59
|
+
"right",
|
|
60
|
+
"top",
|
|
61
|
+
"width",
|
|
62
|
+
"zIndex"
|
|
63
|
+
]);
|
|
64
|
+
const SHORTHANDS = {
|
|
65
|
+
margin: [
|
|
66
|
+
"marginTop",
|
|
67
|
+
"marginRight",
|
|
68
|
+
"marginBottom",
|
|
69
|
+
"marginLeft"
|
|
70
|
+
],
|
|
71
|
+
padding: [
|
|
72
|
+
"paddingTop",
|
|
73
|
+
"paddingRight",
|
|
74
|
+
"paddingBottom",
|
|
75
|
+
"paddingLeft"
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
const UNSUPPORTED_PROPERTIES = /* @__PURE__ */ new Set([
|
|
79
|
+
"filter",
|
|
80
|
+
"backdropFilter",
|
|
81
|
+
"animation",
|
|
82
|
+
"transition",
|
|
83
|
+
"textShadow",
|
|
84
|
+
"backgroundImage",
|
|
85
|
+
"appearance",
|
|
86
|
+
"content",
|
|
87
|
+
"cursor",
|
|
88
|
+
"userSelect",
|
|
89
|
+
"whiteSpace",
|
|
90
|
+
"objectFit",
|
|
91
|
+
"listStyleType",
|
|
92
|
+
"outline"
|
|
93
|
+
]);
|
|
94
|
+
function decodeCssIdentifier(value) {
|
|
95
|
+
return value.replace(/\\([0-9a-f]{1,6})\s?/gi, (_, code) => String.fromCodePoint(Number.parseInt(code, 16))).replace(/\\(.)/g, "$1");
|
|
96
|
+
}
|
|
97
|
+
function propertyName(property) {
|
|
98
|
+
return {
|
|
99
|
+
"padding-inline": "paddingHorizontal",
|
|
100
|
+
"padding-inline-start": "paddingLeft",
|
|
101
|
+
"padding-inline-end": "paddingRight",
|
|
102
|
+
"padding-block": "paddingVertical",
|
|
103
|
+
"padding-block-start": "paddingTop",
|
|
104
|
+
"padding-block-end": "paddingBottom",
|
|
105
|
+
"margin-inline": "marginHorizontal",
|
|
106
|
+
"margin-inline-start": "marginLeft",
|
|
107
|
+
"margin-inline-end": "marginRight",
|
|
108
|
+
"margin-block": "marginVertical",
|
|
109
|
+
"margin-block-start": "marginTop",
|
|
110
|
+
"margin-block-end": "marginBottom"
|
|
111
|
+
}[property] ?? property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
112
|
+
}
|
|
113
|
+
function splitValue(value) {
|
|
114
|
+
return value.trim().split(/\s+/).filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
function parseNumber(value, variables) {
|
|
117
|
+
const resolved = resolveVariables(value, variables);
|
|
118
|
+
const calculated = resolved.match(/^calc\(\s*(-?(?:\d+\.\d+|\d+|\.\d+))(px|rem|em|%)?\s*\*\s*(-?(?:\d+\.\d+|\d+|\.\d+))\s*\)$/);
|
|
119
|
+
if (calculated) {
|
|
120
|
+
const base = parseNumber(`${calculated[1]}${calculated[2] ?? ""}`, variables);
|
|
121
|
+
return typeof base === "number" ? base * Number(calculated[3]) : void 0;
|
|
122
|
+
}
|
|
123
|
+
const match = resolved.match(/^(-?(?:\d+\.\d+|\d+|\.\d+))(px|rem|em|%)?$/);
|
|
124
|
+
if (!match) return;
|
|
125
|
+
const number = Number(match[1]);
|
|
126
|
+
if (match[2] === "%") return `${number}%`;
|
|
127
|
+
if (match[2] === "rem" || match[2] === "em") return number * 16;
|
|
128
|
+
return number;
|
|
129
|
+
}
|
|
130
|
+
function resolveVariables(value, variables) {
|
|
131
|
+
return value.replace(/var\((--[\w-]+)(?:,\s*([^)]*))?\)/g, (_, key, fallback) => variables[key] ?? fallback ?? `var(${key})`);
|
|
132
|
+
}
|
|
133
|
+
function isColor(value) {
|
|
134
|
+
return /^(?:#[\da-f]{3,8}|(?:rgb|rgba|hsl|hsla|oklch|oklab)\([^)]*\)|[a-z]+)$/i.test(value);
|
|
135
|
+
}
|
|
136
|
+
function oklchToHex(value) {
|
|
137
|
+
const match = value.match(/^oklch\(\s*([\d.]+)%?\s+([\d.]+)\s+([\d.]+)(?:\s+\/\s*([\d.]+))?\s*\)$/i);
|
|
138
|
+
if (!match) return;
|
|
139
|
+
const lightness = Number(match[1]) > 1 ? Number(match[1]) / 100 : Number(match[1]);
|
|
140
|
+
const chroma = Number(match[2]);
|
|
141
|
+
const hue = Number(match[3]) * Math.PI / 180;
|
|
142
|
+
const alpha = match[4] === void 0 ? 1 : Number(match[4]);
|
|
143
|
+
const a = chroma * Math.cos(hue);
|
|
144
|
+
const b = chroma * Math.sin(hue);
|
|
145
|
+
const l = lightness + .3963377774 * a + .2158037573 * b;
|
|
146
|
+
const m = lightness - .1055613458 * a - .0638541728 * b;
|
|
147
|
+
const s = lightness - .0894841775 * a - 1.291485548 * b;
|
|
148
|
+
const linear = (channel) => channel ** 3;
|
|
149
|
+
const red = 4.0767416621 * linear(l) - 3.3077115913 * linear(m) + .2309699292 * linear(s);
|
|
150
|
+
const green = -1.2684380046 * linear(l) + 2.6097574011 * linear(m) - .3413193965 * linear(s);
|
|
151
|
+
const blue = -.0041960863 * linear(l) - .7034186147 * linear(m) + 1.707614701 * linear(s);
|
|
152
|
+
const toByte = (channel) => Math.round(Math.max(0, Math.min(1, channel <= .0031308 ? 12.92 * channel : 1.055 * channel ** (1 / 2.4) - .055)) * 255);
|
|
153
|
+
const hex = [
|
|
154
|
+
red,
|
|
155
|
+
green,
|
|
156
|
+
blue
|
|
157
|
+
].map((channel) => toByte(channel).toString(16).padStart(2, "0")).join("");
|
|
158
|
+
return alpha < 1 ? `#${hex}${Math.round(alpha * 255).toString(16).padStart(2, "0")}` : `#${hex}`;
|
|
159
|
+
}
|
|
160
|
+
function normalizeColor(value) {
|
|
161
|
+
return /^oklch\(/i.test(value) ? oklchToHex(value) : value;
|
|
162
|
+
}
|
|
163
|
+
function parseTransform(value, variables) {
|
|
164
|
+
const transform = [];
|
|
165
|
+
for (const match of value.matchAll(/(translateX|translateY|translate|scaleX|scaleY|scale|rotate|skewX|skewY)\(([^)]*)\)/g)) {
|
|
166
|
+
const name = match[1];
|
|
167
|
+
const values = splitValue(resolveVariables(match[2] ?? "", variables).replace(",", " "));
|
|
168
|
+
if (!name || !values.length) continue;
|
|
169
|
+
if (name.startsWith("scale")) {
|
|
170
|
+
const number = Number(values[0] ?? "");
|
|
171
|
+
if (Number.isFinite(number)) transform.push({ [name]: number });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (name === "translate") {
|
|
175
|
+
const x = parseNumber(values[0] ?? "", variables);
|
|
176
|
+
const y = parseNumber(values[1] ?? "0", variables);
|
|
177
|
+
if (x !== void 0 && y !== void 0) transform.push({ translateX: x }, { translateY: y });
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const property = name.replace(/^translate$/, "translateX");
|
|
181
|
+
const parsed = name.startsWith("rotate") || name.startsWith("skew") ? values[0] : parseNumber(values[0] ?? "", variables);
|
|
182
|
+
if (parsed !== void 0) transform.push({ [property]: parsed });
|
|
183
|
+
}
|
|
184
|
+
return transform.length ? transform : void 0;
|
|
185
|
+
}
|
|
186
|
+
function parseShadow(value, variables) {
|
|
187
|
+
const resolved = resolveVariables(value, variables);
|
|
188
|
+
const color = resolved.match(/(?:rgba?|hsla?)\([^)]*\)|#[\da-f]{3,8}/i)?.[0];
|
|
189
|
+
const parts = splitValue(color ? resolved.replace(color, "") : resolved);
|
|
190
|
+
const lengths = parts.filter((part) => parseNumber(part, variables) !== void 0).slice(0, 3);
|
|
191
|
+
if (lengths.length < 2) return;
|
|
192
|
+
const [x = "0", y = "0", blur = "0"] = lengths;
|
|
193
|
+
const shadowColor = normalizeColor(color ?? parts.find((part) => isColor(part)) ?? "#000000") ?? "#000000";
|
|
194
|
+
const opacity = /rgba\([^,]+,[^,]+,[^,]+,\s*([\d.]+)\)/i.exec(shadowColor)?.[1];
|
|
195
|
+
return {
|
|
196
|
+
shadowOffset: {
|
|
197
|
+
width: parseNumber(x, variables),
|
|
198
|
+
height: parseNumber(y, variables)
|
|
199
|
+
},
|
|
200
|
+
shadowRadius: parseNumber(blur, variables),
|
|
201
|
+
shadowColor,
|
|
202
|
+
...opacity ? { shadowOpacity: Number(opacity) } : {}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function parseValue(property, value, variables) {
|
|
206
|
+
const trimmed = value.trim();
|
|
207
|
+
if (UNSUPPORTED_PROPERTIES.has(property)) return;
|
|
208
|
+
if (trimmed.includes("linear-gradient(") || trimmed.includes("url(")) return;
|
|
209
|
+
if (COLOR_PROPERTIES.has(property)) {
|
|
210
|
+
const color = resolveVariables(trimmed, variables);
|
|
211
|
+
return isColor(color) ? normalizeColor(color) : void 0;
|
|
212
|
+
}
|
|
213
|
+
if (NUMERIC_PROPERTIES.has(property)) return parseNumber(trimmed, variables);
|
|
214
|
+
if (property === "transform") return parseTransform(trimmed, variables);
|
|
215
|
+
if (property === "boxShadow") return parseShadow(trimmed, variables);
|
|
216
|
+
if (property === "display" && !["flex", "none"].includes(trimmed)) return;
|
|
217
|
+
return trimmed;
|
|
218
|
+
}
|
|
219
|
+
function expandDeclaration(property, value, variables) {
|
|
220
|
+
if (property === "margin" || property === "padding") {
|
|
221
|
+
const parts = splitValue(value).map((item) => parseNumber(item, variables));
|
|
222
|
+
if (parts.includes(void 0)) return;
|
|
223
|
+
const [top, right = top, bottom = top, left = right] = parts;
|
|
224
|
+
return Object.fromEntries(SHORTHANDS[property].map((key, index) => [key, [
|
|
225
|
+
top,
|
|
226
|
+
right,
|
|
227
|
+
bottom,
|
|
228
|
+
left
|
|
229
|
+
][index]]));
|
|
230
|
+
}
|
|
231
|
+
if (property === "border") {
|
|
232
|
+
const width = splitValue(value).find((item) => /^\d/.test(item));
|
|
233
|
+
const color = splitValue(value).find((item) => isColor(item));
|
|
234
|
+
return {
|
|
235
|
+
...width ? { borderWidth: parseNumber(width, variables) } : {},
|
|
236
|
+
...color ? { borderColor: color } : {}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (property === "boxShadow") return parseShadow(value, variables);
|
|
240
|
+
const parsed = parseValue(property, value, variables);
|
|
241
|
+
return parsed === void 0 ? void 0 : { [property]: parsed };
|
|
242
|
+
}
|
|
243
|
+
function walkClasses(selector) {
|
|
244
|
+
const classes = [];
|
|
245
|
+
for (const match of selector.matchAll(CLASS_SELECTOR_RE)) {
|
|
246
|
+
const token = decodeCssIdentifier(match[1]);
|
|
247
|
+
if (token && !classes.includes(token)) classes.push(token);
|
|
248
|
+
}
|
|
249
|
+
return classes;
|
|
250
|
+
}
|
|
251
|
+
function variantForClass(className) {
|
|
252
|
+
const result = {};
|
|
253
|
+
for (const variant of className.split(":").slice(0, -1)) {
|
|
254
|
+
if (variant === "dark") result.colorScheme = "dark";
|
|
255
|
+
if (variant === "ios" || variant === "android" || variant === "native") result.platform = variant;
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
function atRuleVariant(node) {
|
|
260
|
+
if (!node) return {};
|
|
261
|
+
const params = node.params.toLowerCase();
|
|
262
|
+
if (params.includes("prefers-color-scheme") && params.includes("dark")) return { colorScheme: "dark" };
|
|
263
|
+
if (params.includes("platform") && params.includes("ios")) return { platform: "ios" };
|
|
264
|
+
if (params.includes("platform") && params.includes("android")) return { platform: "android" };
|
|
265
|
+
return {};
|
|
266
|
+
}
|
|
267
|
+
function addNativeVariantRules(manifest, candidates) {
|
|
268
|
+
for (const candidate of candidates) {
|
|
269
|
+
const parts = candidate.split(":");
|
|
270
|
+
if (parts.length < 2) continue;
|
|
271
|
+
const base = parts.at(-1);
|
|
272
|
+
if (!base || manifest.rules[candidate] || !manifest.rules[base]) continue;
|
|
273
|
+
const variant = variantForClass(candidate);
|
|
274
|
+
if (!variant.colorScheme && !variant.platform) continue;
|
|
275
|
+
manifest.rules[candidate] = manifest.rules[base].map((rule) => ({
|
|
276
|
+
...rule,
|
|
277
|
+
...variant,
|
|
278
|
+
style: { ...rule.style }
|
|
279
|
+
}));
|
|
280
|
+
manifest.classSet.push(candidate);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function ancestors(node) {
|
|
284
|
+
const result = [];
|
|
285
|
+
let current = node.parent;
|
|
286
|
+
while (current && current.type !== "root") {
|
|
287
|
+
if (current.type === "atrule") result.unshift(current);
|
|
288
|
+
current = current.parent;
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
function collectVariables(root) {
|
|
293
|
+
const variables = {};
|
|
294
|
+
root.walkDecls((decl) => {
|
|
295
|
+
if (decl.prop.startsWith("--")) variables[decl.prop] = decl.value.trim();
|
|
296
|
+
});
|
|
297
|
+
return variables;
|
|
298
|
+
}
|
|
299
|
+
function addWarning(warnings, warning) {
|
|
300
|
+
if (!warnings.some((item) => item.message === warning.message && item.property === warning.property && item.className === warning.className)) warnings.push(warning);
|
|
301
|
+
}
|
|
302
|
+
function compileRule(rule, className, variables, warnings, order) {
|
|
303
|
+
const styles = {
|
|
304
|
+
normal: {},
|
|
305
|
+
important: {}
|
|
306
|
+
};
|
|
307
|
+
rule.walkDecls((decl) => {
|
|
308
|
+
if (decl.prop.startsWith("--")) return;
|
|
309
|
+
const property = propertyName(decl.prop);
|
|
310
|
+
const important = decl.important || /!important\s*$/i.test(decl.value);
|
|
311
|
+
const value = decl.value.replace(/\s*!important\s*$/i, "");
|
|
312
|
+
const expanded = expandDeclaration(property, value, variables);
|
|
313
|
+
if (!expanded || Object.values(expanded).includes(void 0)) {
|
|
314
|
+
const variableReference = /var\((--[\w-]+)/.exec(value)?.[1];
|
|
315
|
+
if (!variableReference || !variables[variableReference]) addWarning(warnings, {
|
|
316
|
+
className,
|
|
317
|
+
property,
|
|
318
|
+
message: `不支持将 ${decl.prop}: ${value} 编译为 React Native style`
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
Object.assign(styles[important ? "important" : "normal"], expanded);
|
|
323
|
+
});
|
|
324
|
+
const variant = {
|
|
325
|
+
...variantForClass(className),
|
|
326
|
+
...ancestors(rule).reduce((result, node) => ({
|
|
327
|
+
...result,
|
|
328
|
+
...atRuleVariant(node)
|
|
329
|
+
}), {})
|
|
330
|
+
};
|
|
331
|
+
return ["normal", "important"].filter((kind) => Object.keys(styles[kind]).length > 0).map((kind) => ({
|
|
332
|
+
style: styles[kind],
|
|
333
|
+
...variant,
|
|
334
|
+
important: kind === "important" || void 0,
|
|
335
|
+
order
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
338
|
+
/** 为 manifest 生成稳定的 StyleSheet ID 和 Babel 静态 lookup。 */
|
|
339
|
+
function finalizeNativeManifest(manifest) {
|
|
340
|
+
const styleSheet = {};
|
|
341
|
+
const styleEntries = {};
|
|
342
|
+
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++}`;
|
|
346
|
+
rule.id = id;
|
|
347
|
+
styleSheet[id] = rule.style;
|
|
348
|
+
styleEntries[id] = rule;
|
|
349
|
+
(staticLookup[className] ??= []).push(id);
|
|
350
|
+
}
|
|
351
|
+
manifest.styleSheet = styleSheet;
|
|
352
|
+
manifest.styleEntries = styleEntries;
|
|
353
|
+
manifest.staticLookup = staticLookup;
|
|
354
|
+
manifest.classSet = Object.keys(manifest.rules);
|
|
355
|
+
return manifest;
|
|
356
|
+
}
|
|
357
|
+
function compileNativeStylesheet(css, options = {}) {
|
|
358
|
+
const root = postcss.default.parse(css);
|
|
359
|
+
const variables = collectVariables(root);
|
|
360
|
+
const allowed = options.classSet ? new Set(options.classSet) : void 0;
|
|
361
|
+
const rules = {};
|
|
362
|
+
const warnings = [];
|
|
363
|
+
let order = 0;
|
|
364
|
+
root.walkRules((rule) => {
|
|
365
|
+
if (options.ignorePreflight !== false && (rule.selector.includes(":root") || rule.selector.includes("*") || rule.selector.includes("::"))) return;
|
|
366
|
+
for (const selector of rule.selectors) for (const className of walkClasses(selector)) {
|
|
367
|
+
if (allowed && !allowed.has(className)) continue;
|
|
368
|
+
const compiled = compileRule(rule, className, variables, warnings, order++);
|
|
369
|
+
if (compiled.length) (rules[className] ??= []).push(...compiled);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
return finalizeNativeManifest({
|
|
373
|
+
version: 1,
|
|
374
|
+
classSet: Object.keys(rules),
|
|
375
|
+
rules,
|
|
376
|
+
variables,
|
|
377
|
+
warnings
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
exports.addNativeVariantRules = addNativeVariantRules;
|
|
382
|
+
exports.compileNativeStylesheet = compileNativeStylesheet;
|
|
383
|
+
exports.finalizeNativeManifest = finalizeNativeManifest;
|