mirror of
https://github.com/zhigang1992/nativewind.git
synced 2026-06-11 08:03:37 +08:00
feat: options to control what is transformed
refactor: convert everything to typescript
This commit is contained in:
42
README.md
42
README.md
@@ -4,12 +4,12 @@ Use [Tailwindcss](https://tailwindcss.com/) in your cross platform [React Native
|
||||
|
||||
> This library is currently under active development.
|
||||
|
||||
* :sparkles: native support for multiple platforms
|
||||
* :sparkles: respects tailwind.config.js
|
||||
* :sparkles: fast refresh compatible
|
||||
* :sparkles: supports dark mode / media queries / arbitrary classes
|
||||
* :sparkles: compatible with existing styles
|
||||
* :sparkles: Server Side Rendering (SSR) on Web (including responsive styles)
|
||||
- :sparkles: native support for multiple platforms
|
||||
- :sparkles: respects tailwind.config.js
|
||||
- :sparkles: fast refresh compatible
|
||||
- :sparkles: supports dark mode / media queries / arbitrary classes
|
||||
- :sparkles: compatible with existing styles
|
||||
- :sparkles: Server Side Rendering (SSR) on Web (including responsive styles)
|
||||
|
||||
Already using another RN library for Tailwind? [Find out why you should switch.](https://github.com/marklawlor/tailwindcss-react-native/blob/main/docs/library-comparision.md)
|
||||
|
||||
@@ -19,15 +19,13 @@ Install the library
|
||||
|
||||
`npm install tailwindcss-react-native` or `yarn add tailwindcss-react-native`
|
||||
|
||||
Add `tailwindcss-react-native/babel` to your babel plugins
|
||||
Add `tailwindcss-react-native/babel` to your babel plugins
|
||||
|
||||
```js
|
||||
// babel.config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
'tailwindcss-react-native/babel'
|
||||
],
|
||||
}
|
||||
plugins: ["tailwindcss-react-native/babel"],
|
||||
};
|
||||
```
|
||||
|
||||
Add the `TailwindProvider` to your application
|
||||
@@ -51,7 +49,7 @@ This package has a peerDependency of `tailwindcss@3.x.x`. You can install it wit
|
||||
Create a file (eg. `src/tailwindcss-react-native.d.ts`) and paste this line
|
||||
|
||||
```js
|
||||
import "tailwindcss-react-native/types.d"
|
||||
import "tailwindcss-react-native/types.d";
|
||||
```
|
||||
|
||||
#### Web only
|
||||
@@ -60,8 +58,6 @@ import "tailwindcss-react-native/types.d"
|
||||
|
||||
If using `{ platform: 'web' }` you will need to follow the follow the [TailwindCSS installation steps](https://tailwindcss.com/docs/installation) to include it's styles in the application.
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Simply add a `className` attribute to your existing `react-native` components
|
||||
@@ -91,7 +87,6 @@ export function Test({ isBold, isUnderline }) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Options
|
||||
|
||||
Options can be provided via the babel config
|
||||
@@ -99,17 +94,14 @@ Options can be provided via the babel config
|
||||
```js
|
||||
// babel.config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
['tailwindcss-react-native/babel', { platform: 'native' }]
|
||||
],
|
||||
}
|
||||
plugins: [["tailwindcss-react-native/babel", { platform: "native" }]],
|
||||
};
|
||||
```
|
||||
|
||||
Prop | Values | Default | Description
|
||||
----------|----------------------|----------|----------------------
|
||||
platform | `native`, `web`, `native-inline`, `native-context` | `native` | Specifies how the className is transformed (see [platforms](https://github.com/marklawlor/tailwindcss-react-native/blob/main/docs/platforms.md)
|
||||
tailwindConfig | Path relative to `cwd` | `tailwind.config.js` | Provide a custom `tailwind.config.js`. Useful for setting different settings per platform.
|
||||
|
||||
| Prop | Values | Default | Description |
|
||||
| -------------- | -------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| platform | `native`, `web`, `native-inline`, `native-context` | `native` | Specifies how the className is transformed (see [platforms](https://github.com/marklawlor/tailwindcss-react-native/blob/main/docs/platforms.md) |
|
||||
| tailwindConfig | Path relative to `cwd` | `tailwind.config.js` | Provide a custom `tailwind.config.js`. Useful for setting different settings per platform. |
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -120,4 +112,4 @@ Under the hood, `tailwindcss-react-native` performs these general steps
|
||||
1. Remove the `className` attribute and replace/merge it with the `style` attribute
|
||||
1. Utilises a `react` hook for matching media queries.
|
||||
|
||||
See [the platforms documentation](https://github.com/marklawlor/tailwindcss-react-native/blob/main/docs/platforms.md) for a more detailed explaination)
|
||||
See [the platforms documentation](https://github.com/marklawlor/tailwindcss-react-native/blob/main/docs/platforms.md) for a more detailed explaination)
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import path from "path";
|
||||
import pluginTester from "babel-plugin-tester";
|
||||
import testPlugin from "../babel";
|
||||
import testPlugin from "../src/babel";
|
||||
|
||||
const babelOptions = {
|
||||
plugins: ["@babel/plugin-syntax-jsx"],
|
||||
};
|
||||
|
||||
pluginTester({
|
||||
pluginName: "native-inline",
|
||||
plugin: testPlugin,
|
||||
fixtures: path.join(__dirname, "native-inline-fixtures"),
|
||||
babelOptions: require("../babel.config.js"),
|
||||
babelOptions,
|
||||
});
|
||||
|
||||
pluginTester({
|
||||
pluginName: "native-context",
|
||||
plugin: testPlugin,
|
||||
fixtures: path.join(__dirname, "native-context-fixtures"),
|
||||
babelOptions: require("../babel.config.js"),
|
||||
babelOptions,
|
||||
});
|
||||
|
||||
pluginTester({
|
||||
pluginName: "web",
|
||||
plugin: testPlugin,
|
||||
fixtures: path.join(__dirname, "web-fixtures"),
|
||||
babelOptions: require("../babel.config.js"),
|
||||
babelOptions,
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _reactNative = require("react-native");
|
||||
var _tailwindcssReactNative = require("tailwindcss-react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: (0, _tailwindcssReactNative.__useParseTailwind)("font-bold"),
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
const __tailwindStyles = _reactNative.StyleSheet.create({
|
||||
"font-bold": { fontWeight: "700" },
|
||||
});
|
||||
const __tailwindMedia = {};
|
||||
19
__tests__/native-context-fixtures/basic/output.ts
Normal file
19
__tests__/native-context-fixtures/basic/output.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import { __useParseTailwind } from "tailwindcss-react-native";
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
export function Test() {
|
||||
return (
|
||||
<TailwindProvider styles={__tailwindStyles} media={__tailwindMedia}>
|
||||
<Text style={__useParseTailwind("font-bold")}>Hello world!</Text>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const __tailwindStyles = StyleSheet.create({
|
||||
"font-bold": {
|
||||
fontWeight: "700",
|
||||
},
|
||||
});
|
||||
|
||||
const __tailwindMedia = {};
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"tailwindConfigPath": "./__tests__/native-inline-fixtures/basic/tailwind.config.js",
|
||||
"platform": "native-inline"
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _tailwindcssReactNative = require("tailwindcss-react-native");
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: (0, _tailwindcssReactNative.__useParseTailwind)("font-bold", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
}),
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
const __tailwindStyles = _reactNative.StyleSheet.create({
|
||||
"font-bold": { fontWeight: "700" },
|
||||
});
|
||||
const __tailwindMedia = {};
|
||||
26
__tests__/native-inline-fixtures/basic/output.ts
Normal file
26
__tests__/native-inline-fixtures/basic/output.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import { __useParseTailwind } from "tailwindcss-react-native";
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
export function Test() {
|
||||
return (
|
||||
<TailwindProvider>
|
||||
<Text
|
||||
style={__useParseTailwind("font-bold", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
})}
|
||||
>
|
||||
Hello world!
|
||||
</Text>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const __tailwindStyles = StyleSheet.create({
|
||||
"font-bold": {
|
||||
fontWeight: "700",
|
||||
},
|
||||
});
|
||||
|
||||
const __tailwindMedia = {};
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"tailwindConfigPath": "./__tests__/native-inline-fixtures/conditionals/tailwind.config.js",
|
||||
"platform": "native-inline"
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _tailwindcssReactNative = require("tailwindcss-react-native");
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test(_ref) {
|
||||
var isBold = _ref.isBold,
|
||||
isUnderline = _ref.isUnderline;
|
||||
var classNames = [];
|
||||
if (isBold) classNames.push("font-bold");
|
||||
if (isUnderline) classNames.push("underline");
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: (0, _tailwindcssReactNative.__useParseTailwind)(
|
||||
classNames.join(" "),
|
||||
{ styles: __tailwindStyles, media: __tailwindMedia }
|
||||
),
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
const __tailwindStyles = _reactNative.StyleSheet.create({
|
||||
"font-bold": { fontWeight: "700" },
|
||||
underline: { textDecorationLine: "underline" },
|
||||
});
|
||||
const __tailwindMedia = {};
|
||||
32
__tests__/native-inline-fixtures/conditionals/output.ts
Normal file
32
__tests__/native-inline-fixtures/conditionals/output.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import { __useParseTailwind } from "tailwindcss-react-native";
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
export function Test({ isBold, isUnderline }) {
|
||||
const classNames = [];
|
||||
if (isBold) classNames.push("font-bold");
|
||||
if (isUnderline) classNames.push("underline");
|
||||
return (
|
||||
<TailwindProvider>
|
||||
<Text
|
||||
style={__useParseTailwind(classNames.join(" "), {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
})}
|
||||
>
|
||||
Hello world!
|
||||
</Text>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const __tailwindStyles = StyleSheet.create({
|
||||
"font-bold": {
|
||||
fontWeight: "700",
|
||||
},
|
||||
underline: {
|
||||
textDecorationLine: "underline",
|
||||
},
|
||||
});
|
||||
|
||||
const __tailwindMedia = {};
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"tailwindConfigPath": "./__tests__/native-inline-fixtures/empty-className/tailwind.config.js",
|
||||
"platform": "native-inline"
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _tailwindcssReactNative = require("tailwindcss-react-native");
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: (0, _tailwindcssReactNative.__useParseTailwind)("", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
}),
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
const __tailwindStyles = _reactNative.StyleSheet.create({});
|
||||
const __tailwindMedia = {};
|
||||
22
__tests__/native-inline-fixtures/empty-className/output.ts
Normal file
22
__tests__/native-inline-fixtures/empty-className/output.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import { __useParseTailwind } from "tailwindcss-react-native";
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
export function Test() {
|
||||
return (
|
||||
<TailwindProvider>
|
||||
<Text
|
||||
style={__useParseTailwind("", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
})}
|
||||
>
|
||||
Hello world!
|
||||
</Text>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const __tailwindStyles = StyleSheet.create({});
|
||||
|
||||
const __tailwindMedia = {};
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"tailwindConfigPath": "./__tests__/native-inline-fixtures/existing-style/tailwind.config.js",
|
||||
"platform": "native-inline"
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _tailwindcssReactNative = require("tailwindcss-react-native");
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: [
|
||||
(0, _tailwindcssReactNative.__useParseTailwind)("font-bold", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
}),
|
||||
styles.test,
|
||||
],
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
var styles = _reactNative.StyleSheet.create({ test: { color: "blue" } });
|
||||
const __tailwindStyles = _reactNative.StyleSheet.create({
|
||||
"font-bold": { fontWeight: "700" },
|
||||
});
|
||||
const __tailwindMedia = {};
|
||||
34
__tests__/native-inline-fixtures/existing-style/output.ts
Normal file
34
__tests__/native-inline-fixtures/existing-style/output.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import { __useParseTailwind } from "tailwindcss-react-native";
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
export function Test() {
|
||||
return (
|
||||
<TailwindProvider>
|
||||
<Text
|
||||
style={[
|
||||
__useParseTailwind("font-bold", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
}),
|
||||
styles.test,
|
||||
]}
|
||||
>
|
||||
Hello world!
|
||||
</Text>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
test: {
|
||||
color: "blue",
|
||||
},
|
||||
});
|
||||
|
||||
const __tailwindStyles = StyleSheet.create({
|
||||
"font-bold": {
|
||||
fontWeight: "700",
|
||||
},
|
||||
});
|
||||
|
||||
const __tailwindMedia = {};
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
|
||||
};
|
||||
@@ -13,5 +13,5 @@ export function Test() {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
test: { color: "blue" },
|
||||
test2: { color: "blue" },
|
||||
test2: { color: "red" },
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"tailwindConfigPath": "./__tests__/native-inline-fixtures/existing-styles/tailwind.config.js",
|
||||
"platform": "native-inline"
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _tailwindcssReactNative = require("tailwindcss-react-native");
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: [
|
||||
(0, _tailwindcssReactNative.__useParseTailwind)("font-bold", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
}),
|
||||
styles.test,
|
||||
styles.test2,
|
||||
],
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
var styles = _reactNative.StyleSheet.create({
|
||||
test: { color: "blue" },
|
||||
test2: { color: "blue" },
|
||||
});
|
||||
const __tailwindStyles = _reactNative.StyleSheet.create({
|
||||
"font-bold": { fontWeight: "700" },
|
||||
});
|
||||
const __tailwindMedia = {};
|
||||
38
__tests__/native-inline-fixtures/existing-styles/output.ts
Normal file
38
__tests__/native-inline-fixtures/existing-styles/output.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
import { __useParseTailwind } from "tailwindcss-react-native";
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
export function Test() {
|
||||
return (
|
||||
<TailwindProvider>
|
||||
<Text
|
||||
style={[
|
||||
__useParseTailwind("font-bold", {
|
||||
styles: __tailwindStyles,
|
||||
media: __tailwindMedia,
|
||||
}),
|
||||
styles.test,
|
||||
styles.test2,
|
||||
]}
|
||||
>
|
||||
Hello world!
|
||||
</Text>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
const styles = StyleSheet.create({
|
||||
test: {
|
||||
color: "blue",
|
||||
},
|
||||
test2: {
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
const __tailwindStyles = StyleSheet.create({
|
||||
"font-bold": {
|
||||
fontWeight: "700",
|
||||
},
|
||||
});
|
||||
|
||||
const __tailwindMedia = {};
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"tailwindConfigPath": "./__tests__/native-inline-fixtures/ignores-invalid-classes/tailwind.config.js",
|
||||
"platform": "native-inline"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _tailwindcssReactNative = require("tailwindcss-react-native");
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: (0, _tailwindcssReactNative.__useParseTailwind)(
|
||||
"grid grid-cols-3",
|
||||
{ styles: __tailwindStyles, media: __tailwindMedia }
|
||||
),
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
const __tailwindStyles = _reactNative.StyleSheet.create({});
|
||||
const __tailwindMedia = {};
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsx)(_src.TailwindProvider, {
|
||||
children: (0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: { $$css: true, tailwindcssReactNative: "font-bold" },
|
||||
children: "Hello world!",
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
|
||||
export function Test() {
|
||||
return (
|
||||
<TailwindProvider>
|
||||
<Text className="grid grid-cols-3">Hello world!</Text>
|
||||
<Text
|
||||
style={{
|
||||
$$css: true,
|
||||
tailwindcssReactNative: "font-bold",
|
||||
}}
|
||||
>
|
||||
Hello world!
|
||||
</Text>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Test = Test;
|
||||
var _reactNative = require("react-native");
|
||||
var _src = require("../../../src");
|
||||
var _jsxRuntime = require("react/jsx-runtime");
|
||||
function Test() {
|
||||
return (0, _jsxRuntime.jsxs)(_src.TailwindProvider, {
|
||||
children: [
|
||||
(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
||||
style: { $$css: true, tailwindcssReactNative: "font-bold" },
|
||||
children: "Hello world!",
|
||||
}),
|
||||
(0, _jsxRuntime.jsx)("div", {
|
||||
className: "text-white",
|
||||
children: "Should be untransformed",
|
||||
}),
|
||||
],
|
||||
});
|
||||
import { Text } from "react-native";
|
||||
import { TailwindProvider } from "../../../src";
|
||||
export function Test() {
|
||||
return (
|
||||
<TailwindProvider>
|
||||
<Text
|
||||
style={{
|
||||
$$css: true,
|
||||
tailwindcssReactNative: "font-bold",
|
||||
}}
|
||||
>
|
||||
Hello world!
|
||||
</Text>
|
||||
<div className="text-white">Should be untransformed</div>
|
||||
</TailwindProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
"module:metro-react-native-babel-preset",
|
||||
{ useTransformReactJSXExperimental: true },
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"@babel/plugin-transform-react-jsx",
|
||||
{
|
||||
runtime: "automatic",
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
const web = require("./web");
|
||||
const native = require("./native");
|
||||
|
||||
const platformPlugins = {
|
||||
web,
|
||||
native,
|
||||
"native-context": native,
|
||||
"native-inline": native,
|
||||
};
|
||||
|
||||
const convertClassNameIntoTailwindStyles = (...args) => {
|
||||
const [, { platform = "native" }] = args;
|
||||
return platformPlugins[platform](...args);
|
||||
};
|
||||
|
||||
module.exports = convertClassNameIntoTailwindStyles;
|
||||
@@ -1,100 +0,0 @@
|
||||
const getTailwindConfig = require("./utils/get-tailwind-config");
|
||||
const transformClassNames = require("./utils/transform-class-names");
|
||||
const processStyles = require("./utils/process-styles");
|
||||
const appendVariables = require("./utils/babel-variables");
|
||||
const { appendImport, hasImport } = require("./utils/babel-imports");
|
||||
|
||||
const convertClassNameIntoTailwindStyles = (
|
||||
babelConfig,
|
||||
{ tailwindConfigPath },
|
||||
cwd
|
||||
) => {
|
||||
const { types: t } = babelConfig;
|
||||
const tailwindConfig = getTailwindConfig(cwd, tailwindConfigPath);
|
||||
const { styles, media } = processStyles(babelConfig, tailwindConfig);
|
||||
|
||||
const hasClassNames = new Set();
|
||||
const hasUseParseTailwind = new Set();
|
||||
const hasStyleSheetImport = new Set();
|
||||
const hasProvider = new Set();
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
ImportDeclaration(path, state) {
|
||||
const { filename } = state.file.opts;
|
||||
|
||||
if (hasImport(path, "__useParseTailwind", "tailwindcss-react-native")) {
|
||||
hasUseParseTailwind.add(filename);
|
||||
}
|
||||
|
||||
if (hasImport(path, "StyleSheet", "react-native")) {
|
||||
hasStyleSheetImport.add(filename);
|
||||
}
|
||||
},
|
||||
JSXOpeningElement(path, state) {
|
||||
const { filename } = state.file.opts;
|
||||
|
||||
classNames = transformClassNames(babelConfig, path, {
|
||||
inlineStyles: false,
|
||||
});
|
||||
|
||||
if (classNames) {
|
||||
hasClassNames.add(filename);
|
||||
}
|
||||
|
||||
if (path.node.name.name === "TailwindProvider") {
|
||||
hasProvider.add(filename);
|
||||
|
||||
path.node.attributes.push(
|
||||
t.JSXAttribute(
|
||||
t.JSXIdentifier("styles"),
|
||||
t.JSXExpressionContainer(t.identifier("__tailwindStyles"))
|
||||
)
|
||||
);
|
||||
|
||||
path.node.attributes.push(
|
||||
t.JSXAttribute(
|
||||
t.JSXIdentifier("media"),
|
||||
t.JSXExpressionContainer(t.identifier("__tailwindMedia"))
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
Program: {
|
||||
exit(path, state) {
|
||||
const {
|
||||
node: { body },
|
||||
} = path;
|
||||
|
||||
const { filename } = state.file.opts;
|
||||
|
||||
// If there are classNames, but there is no import - add the import
|
||||
if (
|
||||
hasClassNames.has(filename) &&
|
||||
!hasUseParseTailwind.has(filename)
|
||||
) {
|
||||
appendImport(
|
||||
t,
|
||||
body,
|
||||
"__useParseTailwind",
|
||||
"tailwindcss-react-native"
|
||||
);
|
||||
}
|
||||
|
||||
// If there is no provider - then our work here is done
|
||||
if (!hasProvider.has(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasStyleSheetImport.has(filename)) {
|
||||
appendImport(t, body, "StyleSheet", "react-native");
|
||||
}
|
||||
|
||||
appendVariables(body, styles, media);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = convertClassNameIntoTailwindStyles;
|
||||
@@ -1,11 +0,0 @@
|
||||
const contextPlugin = require("./context");
|
||||
const inlinePlugin = require("./inline");
|
||||
|
||||
const convertClassNameIntoTailwindStyles = (babelConfig, pluginConfig, cwd) => {
|
||||
const { platform } = pluginConfig;
|
||||
return platform === "native-context" || process.env.NODE_ENV === "production"
|
||||
? contextPlugin(babelConfig, pluginConfig, cwd)
|
||||
: inlinePlugin(babelConfig, pluginConfig, cwd);
|
||||
};
|
||||
|
||||
module.exports = convertClassNameIntoTailwindStyles;
|
||||
@@ -1,101 +0,0 @@
|
||||
const { relative } = require("path");
|
||||
const transformClassNames = require("./utils/transform-class-names");
|
||||
const processStyles = require("./utils/process-styles");
|
||||
const getTailwindConfig = require("./utils/get-tailwind-config");
|
||||
const appendVariables = require("./utils/babel-variables");
|
||||
const { appendImport, hasImport } = require("./utils/babel-imports");
|
||||
|
||||
/**
|
||||
* While in development mode, this plugin applies this transformation
|
||||
*
|
||||
* In: `<Component className="text-dark" styles={styles.myStyles}>`
|
||||
* Out:
|
||||
*
|
||||
* ```
|
||||
* import { __useParseTailwind } from 'tailwind-react-native'
|
||||
* import { StyleSheet } from 'react-native'
|
||||
* <Component styles={[__useParseTailwind("text-dark", { styles: __tailwindStyles, media: __tailwindMedia), styles.myStyles]}>
|
||||
* const __tailwindStyles = StyleSheet.create(<Compiled styles>)
|
||||
* const __tailwindMedia = <Compiled media rules>
|
||||
* ```
|
||||
*/
|
||||
const convertClassNameIntoTailwindStyles = (
|
||||
babelConfig,
|
||||
{ tailwindConfigPath },
|
||||
cwd
|
||||
) => {
|
||||
const { types: t } = babelConfig;
|
||||
|
||||
const tailwindConfig = getTailwindConfig(cwd, tailwindConfigPath);
|
||||
|
||||
const hasClassNames = new Set();
|
||||
const hasUseParseTailwind = new Set();
|
||||
const hasStyleSheetImport = new Set();
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
ImportDeclaration(path, state) {
|
||||
const { filename } = state.file.opts;
|
||||
|
||||
if (hasImport(path, "__useParseTailwind", "tailwindcss-react-native")) {
|
||||
hasUseParseTailwind.add(filename);
|
||||
}
|
||||
|
||||
if (hasImport(path, "StyleSheet", "react-native")) {
|
||||
hasStyleSheetImport.add(filename);
|
||||
}
|
||||
},
|
||||
JSXOpeningElement(path, state) {
|
||||
const { filename } = state.file.opts;
|
||||
|
||||
const classNames = transformClassNames(babelConfig, path, {
|
||||
inlineStyles: true,
|
||||
});
|
||||
|
||||
if (classNames) {
|
||||
hasClassNames.add(filename);
|
||||
}
|
||||
},
|
||||
Program: {
|
||||
exit(path, state) {
|
||||
const {
|
||||
node: { body },
|
||||
} = path;
|
||||
|
||||
const { filename, root } = state.file.opts;
|
||||
|
||||
// There are no classNames so skip this file
|
||||
if (!hasClassNames.has(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasStyleSheetImport.has(filename)) {
|
||||
appendImport(t, body, "StyleSheet", "react-native");
|
||||
}
|
||||
|
||||
if (!hasUseParseTailwind.has(filename)) {
|
||||
appendImport(
|
||||
t,
|
||||
body,
|
||||
"__useParseTailwind",
|
||||
"tailwindcss-react-native"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override tailwind to only process the classnames in this file
|
||||
*/
|
||||
const { styles, media } = processStyles(babelConfig, {
|
||||
...tailwindConfig,
|
||||
// Make sure its relative to the tailwind.config.js
|
||||
content: [relative(root, filename)],
|
||||
});
|
||||
|
||||
appendVariables(body, styles, media);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = convertClassNameIntoTailwindStyles;
|
||||
@@ -1,28 +0,0 @@
|
||||
function appendImport(t, body, variable, source) {
|
||||
body.unshift(
|
||||
t.importDeclaration(
|
||||
[t.importSpecifier(t.identifier(variable), t.identifier(variable))],
|
||||
t.stringLiteral(source)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function hasImport(path, variable, source) {
|
||||
let match = false
|
||||
|
||||
if (path.node.source.value === source) {
|
||||
path.traverse({
|
||||
Identifier(path) {
|
||||
if (path.key === 'local' && path.node.name === variable) {
|
||||
match = true
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
appendImport,
|
||||
hasImport,
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const template = require('@babel/template').default
|
||||
|
||||
const variableAst = template(`
|
||||
const __tailwindStyles = StyleSheet.create(STYLES);
|
||||
const __tailwindMedia = MEDIA;
|
||||
`)
|
||||
|
||||
function appendVariables(body, styles, media) {
|
||||
variableAst({
|
||||
STYLES: styles,
|
||||
MEDIA: media,
|
||||
}).forEach((ast) => {
|
||||
body.push(ast)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = appendVariables
|
||||
@@ -1,89 +0,0 @@
|
||||
const cssToReactNative = require("css-to-react-native").default;
|
||||
const normaliseSelector = require("../../../dist/shared/selector");
|
||||
const isValidStyle = require("./is-valid-style");
|
||||
|
||||
/** @typedef {import('react-native').ViewStyle | import('react-native').TextStyle | import('react-native').ImageStyle} Style */
|
||||
/** @typedef {{ selector: string, media: string[], rules: Style, rulesAst: any }} CssRule */
|
||||
|
||||
/**
|
||||
* Flattens StyleRules from the 'css' package
|
||||
* - spreads selectors into multiple entries
|
||||
* - flattens media rules so they are not nested
|
||||
* - flattens styles to be react-native style objects
|
||||
* @param {import('@types/css').StyleRules["rules"]} cssRules
|
||||
* @param {{ important?: string }} options
|
||||
* @param {Array<string> | undefined} media
|
||||
* @returns {Array<CssRule>}
|
||||
*/
|
||||
function flattenRules(t, cssRules, options, media = []) {
|
||||
return cssRules.flatMap((cssRule) => {
|
||||
if (cssRule.type === "media") {
|
||||
return flattenRules(t, cssRule.rules, options, [
|
||||
...new Set([...media, cssRule.media]),
|
||||
]);
|
||||
} else if (cssRule.type === "rule") {
|
||||
const declarationRuleTuples = [];
|
||||
const invalidStyleProps = [];
|
||||
|
||||
for (const { type, property, value } of cssRule.declarations || []) {
|
||||
if (type !== "declaration") {
|
||||
continue;
|
||||
}
|
||||
|
||||
declarationRuleTuples.push([property, value]);
|
||||
}
|
||||
|
||||
if (declarationRuleTuples.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rules = Object.fromEntries(
|
||||
Object.entries(cssToReactNative(declarationRuleTuples)).filter(
|
||||
([prop, value]) => {
|
||||
if (isValidStyle(prop, value)) {
|
||||
return true;
|
||||
} else {
|
||||
invalidStyleProps.push(prop);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV !== "development" &&
|
||||
process.env.NODE_ENV !== "test"
|
||||
) {
|
||||
if (invalidStyleProps.length > 0) {
|
||||
console.warn(
|
||||
`Selectors ${cssRule.selectors} use invalid styles ${invalidStyleProps}`
|
||||
);
|
||||
console.warn(
|
||||
`Either remove these selectors or change to file to be platform specific (eg compoent.web.js)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(rules).length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @type {Array<CssRule>} */
|
||||
const selectors = (cssRule.selectors || []).map((selectorDirty) => {
|
||||
const selector = normaliseSelector(selectorDirty, options);
|
||||
|
||||
return {
|
||||
selector,
|
||||
media,
|
||||
rules,
|
||||
};
|
||||
});
|
||||
|
||||
return selectors;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = flattenRules;
|
||||
@@ -1,91 +0,0 @@
|
||||
function transformClassNames({ types: t }, path, { inlineStyles }) {
|
||||
try {
|
||||
let classNames;
|
||||
let existingStyles;
|
||||
|
||||
/**
|
||||
* Find the className attribute
|
||||
* - Save its value
|
||||
* - Remove it as react-native components will ignore it
|
||||
*/
|
||||
for (const attribute of path.get("attributes")) {
|
||||
if (attribute.node.name?.name === "className") {
|
||||
if (t.isStringLiteral(attribute.node.value)) {
|
||||
classNames = attribute.node.value;
|
||||
} else {
|
||||
classNames = attribute.node.value.expression;
|
||||
}
|
||||
attribute.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If classNames is empty or does not exist then exit
|
||||
*/
|
||||
if (!classNames) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the styles attribute
|
||||
* - Save its value
|
||||
* - Remove it, as we're going to reconstruct it
|
||||
*/
|
||||
for (const attribute of path.get("attributes")) {
|
||||
if (attribute.node.name?.name === "style") {
|
||||
existingStyles = attribute.node.value.expression;
|
||||
attribute.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there are existing styles we need to merge them
|
||||
*
|
||||
* Classnames have lower specificity than inline styles
|
||||
* so they should always be first
|
||||
*/
|
||||
const callExpressionArguments = inlineStyles
|
||||
? [
|
||||
classNames,
|
||||
t.objectExpression([
|
||||
t.ObjectProperty(
|
||||
t.Identifier("styles"),
|
||||
t.Identifier("__tailwindStyles")
|
||||
),
|
||||
t.ObjectProperty(
|
||||
t.Identifier("media"),
|
||||
t.Identifier("__tailwindMedia")
|
||||
),
|
||||
]),
|
||||
]
|
||||
: [classNames];
|
||||
|
||||
const callExpression = t.callExpression(
|
||||
t.identifier("__useParseTailwind"),
|
||||
callExpressionArguments
|
||||
);
|
||||
|
||||
let newStyles;
|
||||
if (t.isMemberExpression(existingStyles)) {
|
||||
newStyles = t.JSXExpressionContainer(
|
||||
t.arrayExpression([callExpression, existingStyles])
|
||||
);
|
||||
} else if (t.isArrayExpression(existingStyles)) {
|
||||
existingStyles.elements.unshift(callExpression);
|
||||
newStyles = t.JSXExpressionContainer(existingStyles);
|
||||
} else {
|
||||
newStyles = t.JSXExpressionContainer(callExpression);
|
||||
}
|
||||
|
||||
path.node.attributes.push(
|
||||
t.JSXAttribute(t.JSXIdentifier("style"), newStyles)
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
module.exports = transformClassNames;
|
||||
97
babel/web.js
97
babel/web.js
@@ -1,97 +0,0 @@
|
||||
const convertClassNameIntoTailwindStyles = ({ types: t }) => {
|
||||
return {
|
||||
visitor: {
|
||||
JSXOpeningElement(path) {
|
||||
try {
|
||||
let classNames;
|
||||
let existingStyles;
|
||||
|
||||
const name = path.node.name.name || path.node.name.object?.name;
|
||||
const firstCharOfName = name[0];
|
||||
|
||||
// Ignore elements that start in lower case
|
||||
if (
|
||||
firstCharOfName &&
|
||||
firstCharOfName === firstCharOfName.toLowerCase()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the className attribute
|
||||
* - Save its value
|
||||
* - Remove it as react-native components will ignore it
|
||||
*/
|
||||
for (const attribute of path.get("attributes")) {
|
||||
if (attribute.node.name?.name === "className") {
|
||||
if (t.isStringLiteral(attribute.node.value)) {
|
||||
classNames = attribute.node.value;
|
||||
} else {
|
||||
classNames = attribute.node.value.expression;
|
||||
}
|
||||
attribute.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If classNames is empty or does not exist then exit
|
||||
*/
|
||||
if (!classNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the styles attribute
|
||||
* - Save its value
|
||||
* - Remove it, as we're going to reconstruct it
|
||||
*/
|
||||
for (const attribute of path.get("attributes")) {
|
||||
if (attribute.node.name?.name === "style") {
|
||||
existingStyles = attribute.node.value.expression;
|
||||
attribute.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the classNames into a compiled style object
|
||||
*/
|
||||
let newStyles = t.objectExpression([
|
||||
t.ObjectProperty(t.Identifier("$$css"), t.BooleanLiteral(true)),
|
||||
t.ObjectProperty(
|
||||
t.Identifier("tailwindcssReactNative"),
|
||||
classNames
|
||||
),
|
||||
]);
|
||||
|
||||
/**
|
||||
* If there are existing styles we need to merge them
|
||||
*
|
||||
* Classnames have lower specificity than inline styles
|
||||
* so they should always be first
|
||||
*/
|
||||
|
||||
if (t.isObjectExpression(existingStyles)) {
|
||||
newStyles = t.JSXExpressionContainer(
|
||||
t.arrayExpression([newStyles, existingStyles])
|
||||
);
|
||||
} else if (t.isArrayExpression(existingStyles)) {
|
||||
existingStyles.elements.unshift(newStyles);
|
||||
newStyles = t.JSXExpressionContainer(existingStyles);
|
||||
} else {
|
||||
newStyles = t.JSXExpressionContainer(newStyles);
|
||||
}
|
||||
|
||||
path.node.attributes.push(
|
||||
t.JSXAttribute(t.JSXIdentifier("style"), newStyles)
|
||||
);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = convertClassNameIntoTailwindStyles;
|
||||
@@ -1,25 +1,25 @@
|
||||
## Overview
|
||||
|
||||
Ultimately all these libraries achieve mostly the same result. The difference is either philosophical or implementation details.
|
||||
Ultimately all these libraries achieve mostly the same result. The difference is either philosophical or implementation details.
|
||||
|
||||
These are my notes I made before I created `tailwindcss-react-native` but they also help explain some of the differences.
|
||||
|
||||
## tailwind-rn
|
||||
## tailwind-rn
|
||||
|
||||
https://github.com/vadimdemedes/tailwind-rn
|
||||
|
||||
`tailwind-rn` requires you to manually run two extra processes while developing, `tailwind-cli` and `tailwind-rn`. These processes the styles and stores them via React context. This method has a couple of flaws
|
||||
`tailwind-rn` requires you to manually run two extra processes while developing, `tailwind-cli` and `tailwind-rn`. These processes the styles and stores them via React context. This method has a couple of flaws
|
||||
|
||||
* The processes may run slower than your web application causing warnings/delays https://github.com/vadimdemedes/tailwind-rn/issues/154
|
||||
* Requires custom setup of editor/IDE plugins
|
||||
* Rerenders all components when a style has changed
|
||||
* Does not support responsive SSR for web (cannot apply varients until hydration)
|
||||
- The processes may run slower than your web application causing warnings/delays https://github.com/vadimdemedes/tailwind-rn/issues/154
|
||||
- Requires custom setup of editor/IDE plugins
|
||||
- Rerenders all components when a style has changed
|
||||
- Does not support responsive SSR for web (cannot apply varients until hydration)
|
||||
|
||||
## react-native-tailwindcss
|
||||
|
||||
https://github.com/TVke/react-native-tailwindcss
|
||||
|
||||
* Same issues as `tailwind-rn`
|
||||
- Same issues as `tailwind-rn`
|
||||
|
||||
## react-native-styled.macro
|
||||
|
||||
@@ -27,37 +27,37 @@ https://github.com/z0al/react-native-styled.macro
|
||||
|
||||
Uses babel macros to compile Tailwind selectors to RN Styles.
|
||||
|
||||
* Does not use Tailwind to compile styles
|
||||
* Introduces a new API. Doesn't provide out of box support for varient values
|
||||
* Uses a custom config file
|
||||
- Does not use Tailwind to compile styles
|
||||
- Introduces a new API. Doesn't provide out of box support for varient values
|
||||
- Uses a custom config file
|
||||
|
||||
## react-native-tailwind
|
||||
|
||||
https://github.com/MythicalFish/react-native-tailwind
|
||||
|
||||
* Only works with it's exported components
|
||||
- Only works with it's exported components
|
||||
|
||||
## tailwind-react-native
|
||||
|
||||
https://github.com/ajsmth/tailwind-react-native
|
||||
|
||||
* Same issues as `tailwind-rn`
|
||||
* Introduces a new API. Doesn't provide out of box support for varient values
|
||||
- Same issues as `tailwind-rn`
|
||||
- Introduces a new API. Doesn't provide out of box support for varient values
|
||||
|
||||
## react-native-tailwind-classnames
|
||||
|
||||
https://github.com/leobauza/react-native-tailwind-classnames
|
||||
|
||||
* Only works with StyledComponents
|
||||
- Only works with StyledComponents
|
||||
|
||||
## react-native-tailwind-style
|
||||
|
||||
https://github.com/etc-tiago/react-native-tailwind-style
|
||||
|
||||
* Same issues as `tailwind-rn`
|
||||
- Same issues as `tailwind-rn`
|
||||
|
||||
## tailwind-react-native-classnames
|
||||
|
||||
https://github.com/jaredh159/tailwind-react-native-classnames
|
||||
|
||||
* Same issues as `tailwind-rn`
|
||||
- Same issues as `tailwind-rn`
|
||||
|
||||
@@ -8,12 +8,11 @@ The `native` platform switches platform based upon the `NODE_ENV` environment va
|
||||
|
||||
The platform best suited for production environments. It compiles the entire applications styles and produces the smallest possible output. It shares these styles to components via a React context.
|
||||
|
||||
Feature | Included
|
||||
------ | -------------
|
||||
Small output | :heavy_check_mark:
|
||||
Hot module reload | :x:
|
||||
Requires external tooling | :x:
|
||||
|
||||
| Feature | Included |
|
||||
| ------------------------- | ------------------ |
|
||||
| Small output | :heavy_check_mark: |
|
||||
| Hot module reload | :x: |
|
||||
| Requires external tooling | :x: |
|
||||
|
||||
```diff
|
||||
- import { Text } from "react-native"
|
||||
@@ -39,11 +38,11 @@ export function Test() {
|
||||
|
||||
The platform best suited for development environments. Produces larger output but works with hot-reload. Each file will generate it's own styles and provide them inline.
|
||||
|
||||
Feature | Included
|
||||
------ | -------------
|
||||
Small output | :x:
|
||||
Hot module reload | :heavy_check_mark:
|
||||
Requires external tooling | :x:
|
||||
| Feature | Included |
|
||||
| ------------------------- | ------------------ |
|
||||
| Small output | :x: |
|
||||
| Hot module reload | :heavy_check_mark: |
|
||||
| Requires external tooling | :x: |
|
||||
|
||||
```diff
|
||||
- import { Text } from "react-native"
|
||||
@@ -69,11 +68,11 @@ The platform to use when using `react-native-web`. It leaves the className attri
|
||||
|
||||
Relies on external tooling for production minification.
|
||||
|
||||
Feature | Included
|
||||
------ | -------------
|
||||
Small output | :heavy_check_mark:
|
||||
Hot module reload | :heavy_check_mark:
|
||||
Requires external tooling | :heavy_check_mark:
|
||||
| Feature | Included |
|
||||
| ------------------------- | ------------------ |
|
||||
| Small output | :heavy_check_mark: |
|
||||
| Hot module reload | :heavy_check_mark: |
|
||||
| Requires external tooling | :heavy_check_mark: |
|
||||
|
||||
```diff
|
||||
import { Text } from "react-native"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: "react-native",
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "node",
|
||||
testPathIgnorePatterns: [
|
||||
"/node_modules/",
|
||||
"/__tests__/native-context-fixtures/",
|
||||
|
||||
542
package-lock.json
generated
542
package-lock.json
generated
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "tailwindcss-react-native",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.17",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tailwindcss-react-native",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.17",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.16.7",
|
||||
"@react-native-community/hooks": "^2.8.1",
|
||||
"babel-literal-to-ast": "^2.1.0",
|
||||
"css": "^3.0.0",
|
||||
@@ -20,7 +19,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.8",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/plugin-syntax-jsx": "^7.16.7",
|
||||
"@babel/types": "^7.17.0",
|
||||
"@types/css": "^0.0.33",
|
||||
"@types/css-mediaquery": "^0.1.1",
|
||||
"@types/css-to-react-native": "^3.0.0",
|
||||
@@ -30,12 +30,11 @@
|
||||
"@types/tailwindcss": "^3.0.10",
|
||||
"babel-plugin-tester": "^10.1.0",
|
||||
"jest": "^27.5.1",
|
||||
"metro-react-native-babel-preset": "^0.70.0",
|
||||
"postcss": "^8.4.12",
|
||||
"prettier": "^2.6.1",
|
||||
"react-native": "^0.68.0",
|
||||
"react-native-web": "^0.17.7",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -527,23 +526,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-async-generator-functions": {
|
||||
"version": "7.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz",
|
||||
"integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.16.7",
|
||||
"@babel/helper-remap-async-to-generator": "^7.16.8",
|
||||
"@babel/plugin-syntax-async-generators": "^7.8.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-class-properties": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz",
|
||||
@@ -3758,15 +3740,6 @@
|
||||
"integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/array-find-index": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
|
||||
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/array-map": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
|
||||
@@ -4327,6 +4300,18 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/bs-logger": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
|
||||
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-json-stable-stringify": "2.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/bser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
|
||||
@@ -4921,25 +4906,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/create-react-class": {
|
||||
"version": "15.7.0",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
|
||||
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.3.1",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
@@ -4983,16 +4949,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-in-js-utils": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
|
||||
"integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hyphenate-style-name": "^1.0.2",
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/css-mediaquery": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz",
|
||||
@@ -5797,36 +5753,6 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fbjs": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz",
|
||||
"integrity": "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-fetch": "^3.1.5",
|
||||
"fbjs-css-vars": "^1.0.0",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.30"
|
||||
}
|
||||
},
|
||||
"node_modules/fbjs-css-vars": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fbjs/node_modules/promise": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@@ -6366,12 +6292,6 @@
|
||||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hyphenate-style-name": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
|
||||
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -6484,15 +6404,6 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/inline-style-prefixer": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.1.tgz",
|
||||
"integrity": "sha512-AsqazZ8KcRzJ9YPN1wMH2aNM7lkWQ8tSPrW5uDk1ziYwiAPWSZnUsC7lfZq+BDqLqz0B4Pho5wscWcJzVvRzDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"css-in-js-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@@ -10018,6 +9929,12 @@
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.mergewith": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
|
||||
@@ -10102,6 +10019,12 @@
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/makeerror": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
|
||||
@@ -10314,55 +10237,6 @@
|
||||
"uglify-es": "^3.1.9"
|
||||
}
|
||||
},
|
||||
"node_modules/metro-react-native-babel-preset": {
|
||||
"version": "0.70.0",
|
||||
"resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.0.tgz",
|
||||
"integrity": "sha512-MoOK5/rdDE2bABA+KpbXV6w0Q96sZeZiE9Ct89NYp9nPwXIaY7ylulLsjW3+rT6BdecKuNPUVwvtO0vYIQwvRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-catch-binding": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-syntax-flow": "^7.2.0",
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.0.0",
|
||||
"@babel/plugin-transform-block-scoping": "^7.0.0",
|
||||
"@babel/plugin-transform-classes": "^7.0.0",
|
||||
"@babel/plugin-transform-computed-properties": "^7.0.0",
|
||||
"@babel/plugin-transform-destructuring": "^7.0.0",
|
||||
"@babel/plugin-transform-exponentiation-operator": "^7.0.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.0.0",
|
||||
"@babel/plugin-transform-function-name": "^7.0.0",
|
||||
"@babel/plugin-transform-literals": "^7.0.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.0.0",
|
||||
"@babel/plugin-transform-parameters": "^7.0.0",
|
||||
"@babel/plugin-transform-react-display-name": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.0.0",
|
||||
"@babel/plugin-transform-spread": "^7.0.0",
|
||||
"@babel/plugin-transform-sticky-regex": "^7.0.0",
|
||||
"@babel/plugin-transform-template-literals": "^7.0.0",
|
||||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"react-refresh": "^0.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/metro-react-native-babel-transformer": {
|
||||
"version": "0.67.0",
|
||||
"resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.67.0.tgz",
|
||||
@@ -10958,12 +10832,6 @@
|
||||
"url": "https://github.com/sponsors/antelle"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-css-color": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/normalize-css-color/-/normalize-css-color-1.0.2.tgz",
|
||||
"integrity": "sha1-Apkel8zOxmI/5XOvu/Deah8+n40=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
@@ -12103,25 +11971,6 @@
|
||||
"react-native-codegen": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-web": {
|
||||
"version": "0.17.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.17.7.tgz",
|
||||
"integrity": "sha512-4OOU/QjyRySOXyHfTvljEMS4VXKn42Qs3y9uHDPMwaCUFjwg0oasR/j706OaVgan9kF4Ipa2vJ3F6Z/Xqy8KeQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-find-index": "^1.0.2",
|
||||
"create-react-class": "^15.7.0",
|
||||
"fbjs": "^3.0.0",
|
||||
"hyphenate-style-name": "^1.0.4",
|
||||
"inline-style-prefixer": "^6.0.0",
|
||||
"normalize-css-color": "^1.0.2",
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0.1",
|
||||
"react-dom": ">=17.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||
@@ -12632,12 +12481,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@@ -13699,6 +13542,82 @@
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ts-jest": {
|
||||
"version": "27.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.4.tgz",
|
||||
"integrity": "sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bs-logger": "0.x",
|
||||
"fast-json-stable-stringify": "2.x",
|
||||
"jest-util": "^27.0.0",
|
||||
"json5": "2.x",
|
||||
"lodash.memoize": "4.x",
|
||||
"make-error": "1.x",
|
||||
"semver": "7.x",
|
||||
"yargs-parser": "20.x"
|
||||
},
|
||||
"bin": {
|
||||
"ts-jest": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": ">=7.0.0-beta.0 <8",
|
||||
"@types/jest": "^27.0.0",
|
||||
"babel-jest": ">=27.0.0 <28",
|
||||
"jest": "^27.0.0",
|
||||
"typescript": ">=3.8 <5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@babel/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/jest": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-jest": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest/node_modules/lru-cache": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz",
|
||||
"integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest/node_modules/semver": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz",
|
||||
"integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.0.0 || ^12.0.0 || ^14.0.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest/node_modules/yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
@@ -13757,25 +13676,6 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
||||
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/uglify-es": {
|
||||
"version": "3.3.9",
|
||||
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
|
||||
@@ -14683,17 +14583,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz",
|
||||
"integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ=="
|
||||
},
|
||||
"@babel/plugin-proposal-async-generator-functions": {
|
||||
"version": "7.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz",
|
||||
"integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.16.7",
|
||||
"@babel/helper-remap-async-to-generator": "^7.16.8",
|
||||
"@babel/plugin-syntax-async-generators": "^7.8.4"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-class-properties": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz",
|
||||
@@ -17121,12 +17010,6 @@
|
||||
"integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
|
||||
"dev": true
|
||||
},
|
||||
"array-find-index": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
|
||||
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
|
||||
"dev": true
|
||||
},
|
||||
"array-map": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
|
||||
@@ -17557,6 +17440,15 @@
|
||||
"picocolors": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"bs-logger": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
|
||||
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-json-stable-stringify": "2.x"
|
||||
}
|
||||
},
|
||||
"bser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
|
||||
@@ -18018,25 +17910,6 @@
|
||||
"parse-json": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"create-react-class": {
|
||||
"version": "15.7.0",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
|
||||
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.3.1",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
@@ -18073,16 +17946,6 @@
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
|
||||
},
|
||||
"css-in-js-utils": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz",
|
||||
"integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hyphenate-style-name": "^1.0.2",
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"css-mediaquery": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz",
|
||||
@@ -18726,38 +18589,6 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"fbjs": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.4.tgz",
|
||||
"integrity": "sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-fetch": "^3.1.5",
|
||||
"fbjs-css-vars": "^1.0.0",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.30"
|
||||
},
|
||||
"dependencies": {
|
||||
"promise": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fbjs-css-vars": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@@ -19171,12 +19002,6 @@
|
||||
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
|
||||
"dev": true
|
||||
},
|
||||
"hyphenate-style-name": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
|
||||
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==",
|
||||
"dev": true
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -19250,15 +19075,6 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"inline-style-prefixer": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.1.tgz",
|
||||
"integrity": "sha512-AsqazZ8KcRzJ9YPN1wMH2aNM7lkWQ8tSPrW5uDk1ziYwiAPWSZnUsC7lfZq+BDqLqz0B4Pho5wscWcJzVvRzDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css-in-js-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@@ -21894,6 +21710,12 @@
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
|
||||
@@ -21962,6 +21784,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"makeerror": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
|
||||
@@ -22286,52 +22114,6 @@
|
||||
"uglify-es": "^3.1.9"
|
||||
}
|
||||
},
|
||||
"metro-react-native-babel-preset": {
|
||||
"version": "0.70.0",
|
||||
"resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.0.tgz",
|
||||
"integrity": "sha512-MoOK5/rdDE2bABA+KpbXV6w0Q96sZeZiE9Ct89NYp9nPwXIaY7ylulLsjW3+rT6BdecKuNPUVwvtO0vYIQwvRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-catch-binding": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-syntax-flow": "^7.2.0",
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.0.0",
|
||||
"@babel/plugin-transform-block-scoping": "^7.0.0",
|
||||
"@babel/plugin-transform-classes": "^7.0.0",
|
||||
"@babel/plugin-transform-computed-properties": "^7.0.0",
|
||||
"@babel/plugin-transform-destructuring": "^7.0.0",
|
||||
"@babel/plugin-transform-exponentiation-operator": "^7.0.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.0.0",
|
||||
"@babel/plugin-transform-function-name": "^7.0.0",
|
||||
"@babel/plugin-transform-literals": "^7.0.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.0.0",
|
||||
"@babel/plugin-transform-parameters": "^7.0.0",
|
||||
"@babel/plugin-transform-react-display-name": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.0.0",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.0.0",
|
||||
"@babel/plugin-transform-spread": "^7.0.0",
|
||||
"@babel/plugin-transform-sticky-regex": "^7.0.0",
|
||||
"@babel/plugin-transform-template-literals": "^7.0.0",
|
||||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"react-refresh": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"metro-react-native-babel-transformer": {
|
||||
"version": "0.67.0",
|
||||
"resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.67.0.tgz",
|
||||
@@ -22666,12 +22448,6 @@
|
||||
"integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-css-color": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/normalize-css-color/-/normalize-css-color-1.0.2.tgz",
|
||||
"integrity": "sha1-Apkel8zOxmI/5XOvu/Deah8+n40=",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
@@ -23513,21 +23289,6 @@
|
||||
"react-native-codegen": "*"
|
||||
}
|
||||
},
|
||||
"react-native-web": {
|
||||
"version": "0.17.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.17.7.tgz",
|
||||
"integrity": "sha512-4OOU/QjyRySOXyHfTvljEMS4VXKn42Qs3y9uHDPMwaCUFjwg0oasR/j706OaVgan9kF4Ipa2vJ3F6Z/Xqy8KeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-find-index": "^1.0.2",
|
||||
"create-react-class": "^15.7.0",
|
||||
"fbjs": "^3.0.0",
|
||||
"hyphenate-style-name": "^1.0.4",
|
||||
"inline-style-prefixer": "^6.0.0",
|
||||
"normalize-css-color": "^1.0.2",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||
@@ -23928,12 +23689,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||
"dev": true
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@@ -24767,6 +24522,45 @@
|
||||
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
|
||||
"dev": true
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "27.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.4.tgz",
|
||||
"integrity": "sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bs-logger": "0.x",
|
||||
"fast-json-stable-stringify": "2.x",
|
||||
"jest-util": "^27.0.0",
|
||||
"json5": "2.x",
|
||||
"lodash.memoize": "4.x",
|
||||
"make-error": "1.x",
|
||||
"semver": "7.x",
|
||||
"yargs-parser": "20.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.7.3.tgz",
|
||||
"integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.6",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz",
|
||||
"integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^7.4.0"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
@@ -24809,12 +24603,6 @@
|
||||
"integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
|
||||
"dev": true
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.31",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
||||
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-es": {
|
||||
"version": "3.3.9",
|
||||
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "tsc"
|
||||
"build": "rm -rf dist && tsc"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
@@ -13,7 +13,6 @@
|
||||
"types.d.ts"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.16.7",
|
||||
"@react-native-community/hooks": "^2.8.1",
|
||||
"babel-literal-to-ast": "^2.1.0",
|
||||
"css": "^3.0.0",
|
||||
@@ -25,7 +24,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.8",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/plugin-syntax-jsx": "^7.16.7",
|
||||
"@babel/types": "^7.17.0",
|
||||
"@types/css": "^0.0.33",
|
||||
"@types/css-mediaquery": "^0.1.1",
|
||||
"@types/css-to-react-native": "^3.0.0",
|
||||
@@ -35,12 +35,11 @@
|
||||
"@types/tailwindcss": "^3.0.10",
|
||||
"babel-plugin-tester": "^10.1.0",
|
||||
"jest": "^27.5.1",
|
||||
"metro-react-native-babel-preset": "^0.70.0",
|
||||
"postcss": "^8.4.12",
|
||||
"prettier": "^2.6.1",
|
||||
"react-native": "^0.68.0",
|
||||
"react-native-web": "^0.17.7",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
33
src/babel/index.ts
Normal file
33
src/babel/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Babel, TailwindReactNativeOptions } from "./types";
|
||||
|
||||
import web from "./web";
|
||||
import nativeInline from "./native-inline";
|
||||
import nativeContext from "./native-context";
|
||||
|
||||
const platformPlugins = {
|
||||
web,
|
||||
"native-context": nativeContext,
|
||||
"native-inline": nativeInline,
|
||||
};
|
||||
|
||||
export default function (
|
||||
babel: Babel,
|
||||
options: TailwindReactNativeOptions,
|
||||
cwd: string
|
||||
) {
|
||||
let { platform = "native" } = options ?? {};
|
||||
|
||||
if (platform === "native" && process.env.NODE_ENV === "production") {
|
||||
platform = "native-inline";
|
||||
} else if (platform === "native") {
|
||||
platform = "native-context";
|
||||
} else if (!(platform in platformPlugins)) {
|
||||
throw new Error(`Unknown platform ${platform}`);
|
||||
}
|
||||
|
||||
return platformPlugins[platform as keyof typeof platformPlugins](
|
||||
babel,
|
||||
options,
|
||||
cwd
|
||||
);
|
||||
}
|
||||
112
src/babel/native-context.ts
Normal file
112
src/babel/native-context.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { NodePath, Visitor } from "@babel/core";
|
||||
import { Program } from "@babel/types";
|
||||
import { Babel, State, TailwindReactNativeOptions } from "./types";
|
||||
import { nativeVisitor, NativeVisitorState } from "./native-visitor";
|
||||
|
||||
import { getTailwindConfig } from "./utils/get-tailwind-config";
|
||||
import { processStyles } from "./utils/process-styles";
|
||||
import { appendVariables } from "./utils/native-variables";
|
||||
import { appendImport } from "./utils/imports";
|
||||
import { getJSXElementName } from "./utils/jsx";
|
||||
|
||||
export default function (
|
||||
babel: Babel,
|
||||
options: TailwindReactNativeOptions,
|
||||
cwd: string
|
||||
) {
|
||||
const tailwindConfig = getTailwindConfig(cwd, options);
|
||||
const { styles, media } = processStyles(babel, tailwindConfig);
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
Program: {
|
||||
enter(path: NodePath<Program>, state: State) {
|
||||
/* Dirty check the file for
|
||||
* - className attribute
|
||||
* - TailwindProvider
|
||||
*/
|
||||
if (
|
||||
!state.file.code.includes("className=") &&
|
||||
!state.file.code.includes("TailwindProvider")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nativeVisitorState: NativeVisitorState = {
|
||||
...state,
|
||||
babel,
|
||||
tailwindConfig,
|
||||
blackedListedComponents: new Set(),
|
||||
hasUseParseTailwind: false,
|
||||
hasStyleSheetImport: false,
|
||||
hasClassNames: false,
|
||||
hasProvider: false,
|
||||
transformClassNameOptions: { inlineStyles: false },
|
||||
visitor: nativeContextVisitor,
|
||||
};
|
||||
|
||||
// Traverse the file
|
||||
path.traverse(nativeVisitor, nativeVisitorState);
|
||||
|
||||
const {
|
||||
hasClassNames,
|
||||
hasStyleSheetImport,
|
||||
hasUseParseTailwind,
|
||||
hasProvider,
|
||||
} = nativeVisitorState;
|
||||
|
||||
const bodyNode = path.node.body;
|
||||
|
||||
// Add the __useParseTailwind import if it is missing
|
||||
if (hasClassNames && !hasUseParseTailwind) {
|
||||
appendImport(
|
||||
babel,
|
||||
bodyNode,
|
||||
"__useParseTailwind",
|
||||
"tailwindcss-react-native"
|
||||
);
|
||||
}
|
||||
|
||||
// If there is no TailwindProvider in this file, then we can finish early
|
||||
if (!hasProvider) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the StyleSheet import if it is missing
|
||||
if (!hasStyleSheetImport) {
|
||||
appendImport(babel, bodyNode, "StyleSheet", "react-native");
|
||||
}
|
||||
|
||||
appendVariables(babel, bodyNode, styles, media);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const nativeContextVisitor: Visitor<NativeVisitorState> = {
|
||||
JSXOpeningElement(path, state) {
|
||||
const { types: t } = state.babel;
|
||||
|
||||
const elementIsTailwindProvider =
|
||||
getJSXElementName(path.node) === "TailwindProvider";
|
||||
|
||||
state.hasProvider ||= elementIsTailwindProvider;
|
||||
|
||||
if (elementIsTailwindProvider) {
|
||||
path.node.attributes.push(
|
||||
t.jSXAttribute(
|
||||
t.jSXIdentifier("styles"),
|
||||
t.jSXExpressionContainer(t.identifier("__tailwindStyles"))
|
||||
)
|
||||
);
|
||||
|
||||
path.node.attributes.push(
|
||||
t.jSXAttribute(
|
||||
t.jSXIdentifier("media"),
|
||||
t.jSXExpressionContainer(t.identifier("__tailwindMedia"))
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
85
src/babel/native-inline.ts
Normal file
85
src/babel/native-inline.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { relative } from "path";
|
||||
import { Program } from "@babel/types";
|
||||
import { NodePath } from "@babel/traverse";
|
||||
|
||||
import { processStyles } from "./utils/process-styles";
|
||||
import { getTailwindConfig } from "./utils/get-tailwind-config";
|
||||
import { appendVariables } from "./utils/native-variables";
|
||||
import { appendImport } from "./utils/imports";
|
||||
import { NativeVisitorState, nativeVisitor } from "./native-visitor";
|
||||
import { TailwindReactNativeOptions, State, Babel } from "./types";
|
||||
|
||||
export default function (
|
||||
babel: Babel,
|
||||
options: TailwindReactNativeOptions,
|
||||
cwd: string
|
||||
) {
|
||||
const tailwindConfig = getTailwindConfig(cwd, options);
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
Program: {
|
||||
enter(path: NodePath<Program>, state: State) {
|
||||
// Dirty check the file for the className attribute
|
||||
if (!state.file.code.includes("className=")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nativeVisitorState: NativeVisitorState = {
|
||||
...state,
|
||||
babel,
|
||||
tailwindConfig,
|
||||
blackedListedComponents: new Set(),
|
||||
hasUseParseTailwind: false,
|
||||
hasStyleSheetImport: false,
|
||||
hasClassNames: false,
|
||||
hasProvider: false,
|
||||
transformClassNameOptions: { inlineStyles: true },
|
||||
};
|
||||
|
||||
// Traverse the file
|
||||
path.traverse(nativeVisitor, nativeVisitorState);
|
||||
|
||||
const {
|
||||
filename,
|
||||
hasClassNames,
|
||||
hasStyleSheetImport,
|
||||
hasUseParseTailwind,
|
||||
} = nativeVisitorState;
|
||||
|
||||
// There are no classNames so skip this file
|
||||
if (!hasClassNames) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootDir = state.file.opts.root ?? ".";
|
||||
const bodyNode = path.node.body;
|
||||
|
||||
if (!hasUseParseTailwind) {
|
||||
appendImport(
|
||||
babel,
|
||||
bodyNode,
|
||||
"__useParseTailwind",
|
||||
"tailwindcss-react-native"
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasStyleSheetImport) {
|
||||
appendImport(babel, bodyNode, "StyleSheet", "react-native");
|
||||
}
|
||||
|
||||
/**
|
||||
* Override tailwind to only process the classnames in this file
|
||||
*/
|
||||
const { styles, media } = processStyles(babel, {
|
||||
...tailwindConfig,
|
||||
// Make sure its relative to the tailwind.config.js
|
||||
content: [relative(rootDir, filename)],
|
||||
});
|
||||
|
||||
appendVariables(babel, bodyNode, styles, media);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
80
src/babel/native-visitor.ts
Normal file
80
src/babel/native-visitor.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { Visitor } from "@babel/traverse";
|
||||
import { TailwindConfig } from "tailwindcss/tailwind-config";
|
||||
|
||||
import { Babel, State } from "./types";
|
||||
import { getJSXElementName } from "./utils/jsx";
|
||||
import { getBlackedListedComponents, hasNamedImport } from "./utils/imports";
|
||||
import {
|
||||
transformClassName,
|
||||
TransformClassNameOptions,
|
||||
} from "./utils/transform-class-names";
|
||||
|
||||
export interface NativeVisitorState extends State {
|
||||
babel: Babel;
|
||||
tailwindConfig: TailwindConfig;
|
||||
blackedListedComponents: Set<string>;
|
||||
hasUseParseTailwind: boolean;
|
||||
hasStyleSheetImport: boolean;
|
||||
hasClassNames: boolean;
|
||||
hasProvider: boolean;
|
||||
visitor?: Visitor<NativeVisitorState>;
|
||||
transformClassNameOptions: TransformClassNameOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor that preforms common operations betweent the native platforms
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* If a platform needs to provide bespoke functionality, they can provide a Visitor object
|
||||
* on the state. (note: This Visitor is not valided by Babel so use with caution)
|
||||
*
|
||||
* @privateRemarks
|
||||
*
|
||||
* This should only focus on common functionality, eg:
|
||||
* - Detecting imports
|
||||
* - Detecting if a JSXElement should be processed
|
||||
*
|
||||
* The only exception to this is the className -> style transform
|
||||
* as that is also used to detect the className attribute should be processed
|
||||
*
|
||||
*/
|
||||
export const nativeVisitor: Visitor<NativeVisitorState> = {
|
||||
ImportDeclaration(path, state) {
|
||||
state.blackedListedComponents = new Set(
|
||||
getBlackedListedComponents(path, state)
|
||||
);
|
||||
|
||||
// We only need to check named imports.
|
||||
// THe code will still work if they are using a Namespace Specifier
|
||||
state.hasStyleSheetImport = hasNamedImport(
|
||||
path,
|
||||
"StyleSheet",
|
||||
"react-native"
|
||||
);
|
||||
state.hasUseParseTailwind = hasNamedImport(
|
||||
path,
|
||||
"__useParseTailwind",
|
||||
"tailwindcss-react-native"
|
||||
);
|
||||
},
|
||||
JSXOpeningElement(path, state) {
|
||||
const name = getJSXElementName(path.node);
|
||||
|
||||
if (state.blackedListedComponents.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasClassNames = transformClassName(
|
||||
state.babel,
|
||||
path,
|
||||
state.transformClassNameOptions
|
||||
);
|
||||
|
||||
state.hasClassNames ||= hasClassNames;
|
||||
|
||||
if (typeof state.visitor?.JSXOpeningElement === "function") {
|
||||
state.visitor.JSXOpeningElement.bind(this)(path, state);
|
||||
}
|
||||
},
|
||||
};
|
||||
20
src/babel/types.d.ts
vendored
Normal file
20
src/babel/types.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ViewStyle, TextStyle, ImageStyle } from "react-native";
|
||||
import * as BabelCore from "@babel/core";
|
||||
|
||||
export type Style = ViewStyle | TextStyle | ImageStyle;
|
||||
export type Babel = typeof BabelCore;
|
||||
|
||||
export interface TailwindReactNativeOptions {
|
||||
tailwindConfigPath?: string;
|
||||
platform?: "web" | "native" | "native-context" | "native-inline";
|
||||
allowedImports?: "*" | Array<string | RegExp>;
|
||||
deniedImports?: Array<string | RegExp>;
|
||||
}
|
||||
|
||||
export type State = {
|
||||
get: (name: string) => any;
|
||||
set: (name: string, value: any) => any;
|
||||
opts: TailwindReactNativeOptions;
|
||||
file: BabelCore.BabelFile;
|
||||
filename: string;
|
||||
};
|
||||
114
src/babel/utils/flatten-rules.ts
Normal file
114
src/babel/utils/flatten-rules.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import cssToReactNative, { StyleTuple } from "css-to-react-native";
|
||||
import { TailwindConfig } from "tailwindcss/tailwind-config";
|
||||
import { AtRule, Comment, Media, Rule, StyleRules } from "css";
|
||||
|
||||
import { normaliseSelector } from "../../shared/selector";
|
||||
import { Babel, Style } from "../types";
|
||||
import { isValidStyle } from "./is-valid-style";
|
||||
|
||||
interface CssRule {
|
||||
selector: string;
|
||||
media: string[];
|
||||
rules: Style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens StyleRules from the 'css' package
|
||||
* - spreads selectors into multiple entries
|
||||
* - flattens media rules so they are not nested
|
||||
* - flattens styles to be react-native style objects
|
||||
*/
|
||||
export function flattenRules(
|
||||
babel: Babel,
|
||||
cssRules: StyleRules["rules"],
|
||||
tailwindConfig: TailwindConfig,
|
||||
media: string[] = []
|
||||
): CssRule[] {
|
||||
return cssRules.flatMap((cssRule) => {
|
||||
if (isMedia(cssRule)) {
|
||||
return flattenRules(babel, cssRule.rules ?? [], tailwindConfig, [
|
||||
...new Set(cssRule.media ? [...media, cssRule.media] : media),
|
||||
]);
|
||||
} else if (isRule(cssRule)) {
|
||||
const declarationRuleTuples: StyleTuple[] = [];
|
||||
const invalidStyleProps: string[] = [];
|
||||
|
||||
for (const declaration of cssRule.declarations || []) {
|
||||
if (isComment(declaration)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { property, value } = declaration;
|
||||
|
||||
if (property === undefined || value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
declarationRuleTuples.push([property, value]);
|
||||
}
|
||||
|
||||
if (declarationRuleTuples.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rules = Object.fromEntries(
|
||||
Object.entries(cssToReactNative(declarationRuleTuples)).filter(
|
||||
([prop, value]) => {
|
||||
if (isValidStyle(prop, value)) {
|
||||
return true;
|
||||
} else {
|
||||
invalidStyleProps.push(prop);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV !== "development" &&
|
||||
process.env.NODE_ENV !== "test"
|
||||
) {
|
||||
if (invalidStyleProps.length > 0) {
|
||||
console.warn(
|
||||
`Selectors ${cssRule.selectors} use invalid styles ${invalidStyleProps}`
|
||||
);
|
||||
console.warn(
|
||||
`Either remove these selectors or change to file to be platform specific (eg compoent.web.js)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(rules).length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const selectors: CssRule[] = (cssRule.selectors || []).map(
|
||||
(selectorDirty) => {
|
||||
const selector = normaliseSelector(selectorDirty, tailwindConfig);
|
||||
|
||||
return {
|
||||
selector,
|
||||
media,
|
||||
rules,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return selectors;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isRule(rule: Rule | Comment | AtRule): rule is Rule {
|
||||
return rule.type === "rule";
|
||||
}
|
||||
|
||||
function isComment(rule: Rule | Comment | AtRule): rule is Comment {
|
||||
return rule.type === "comment";
|
||||
}
|
||||
|
||||
function isMedia(rule: Rule | Comment | AtRule): rule is Media {
|
||||
return rule.type === "media";
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
const { join } = require("path");
|
||||
const { existsSync } = require("fs");
|
||||
const plugin = require("tailwindcss/plugin");
|
||||
const resolveTailwindConfig = require("tailwindcss/resolveConfig");
|
||||
import { join } from "path";
|
||||
import { existsSync } from "fs";
|
||||
import plugin from "tailwindcss/plugin";
|
||||
import resolveTailwindConfig from "tailwindcss/resolveConfig";
|
||||
import { TailwindReactNativeOptions } from "../types";
|
||||
|
||||
/**
|
||||
* Resolves the Tailwind config based upon the cwd
|
||||
* @param {string} cwd
|
||||
* @returns {import('tailwindcss/tailwind-config').TailwindConfig}
|
||||
*/
|
||||
function getTailwindConfig(cwd, configPath) {
|
||||
const fullConfigPath = join(cwd, configPath || "./tailwind.config.js");
|
||||
export function getTailwindConfig(
|
||||
cwd: string,
|
||||
{ tailwindConfigPath }: TailwindReactNativeOptions
|
||||
): import("tailwindcss/tailwind-config").TailwindConfig {
|
||||
const fullConfigPath = join(
|
||||
cwd,
|
||||
tailwindConfigPath || "./tailwind.config.js"
|
||||
);
|
||||
|
||||
// Get the config. Throw an error if configPath was set but we were unable to find it
|
||||
let projectTailwindConfig;
|
||||
if (existsSync(fullConfigPath)) {
|
||||
projectTailwindConfig = require(fullConfigPath);
|
||||
} else if (configPath) {
|
||||
} else if (tailwindConfigPath) {
|
||||
throw new Error(`Unable to find config ${fullConfigPath}`);
|
||||
} else {
|
||||
projectTailwindConfig = {};
|
||||
@@ -33,5 +35,3 @@ function getTailwindConfig(cwd, configPath) {
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = getTailwindConfig;
|
||||
87
src/babel/utils/imports.ts
Normal file
87
src/babel/utils/imports.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { NodePath } from "@babel/core";
|
||||
import * as t from "@babel/types";
|
||||
import { Statement } from "@babel/types";
|
||||
import { Babel, State } from "../types";
|
||||
|
||||
export function appendImport(
|
||||
{ types: t }: Babel,
|
||||
body: Statement[],
|
||||
variable: string,
|
||||
source: string
|
||||
) {
|
||||
body.unshift(
|
||||
t.importDeclaration(
|
||||
[t.importSpecifier(t.identifier(variable), t.identifier(variable))],
|
||||
t.stringLiteral(source)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function hasNamedImport(
|
||||
path: NodePath<t.ImportDeclaration>,
|
||||
variable: string,
|
||||
source: string
|
||||
) {
|
||||
if (path.node.source.value === source) {
|
||||
return path.node.specifiers.some((specifier) => {
|
||||
if (!t.isImportSpecifier(specifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (t.isStringLiteral(specifier.imported)) {
|
||||
return specifier.imported.value === variable;
|
||||
} else {
|
||||
return specifier.imported.name === variable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getBlackedListedComponents(
|
||||
path: NodePath<t.ImportDeclaration>,
|
||||
state: State
|
||||
) {
|
||||
const { allowedImports = "*", deniedImports = [] } = state.opts;
|
||||
|
||||
if (allowedImports === "*" && deniedImports.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const module = path.node.source.value;
|
||||
|
||||
if (allowedImports !== "*") {
|
||||
const isAllowed = allowedImports.every((allowed) => {
|
||||
if (allowed instanceof RegExp) {
|
||||
return allowed.test(module);
|
||||
} else {
|
||||
return module.startsWith(allowed);
|
||||
}
|
||||
});
|
||||
|
||||
if (isAllowed) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (deniedImports.length > 0) {
|
||||
const isDenied = deniedImports.every((deny) => {
|
||||
if (deny instanceof RegExp) {
|
||||
return deny.test(module);
|
||||
} else {
|
||||
return module.startsWith(deny);
|
||||
}
|
||||
});
|
||||
|
||||
if (!isDenied) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// If we have made it to here, either the module was
|
||||
// not allowed or was denied.
|
||||
return path.node.specifiers.map((specifier) => {
|
||||
return specifier.local.name;
|
||||
});
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
function isValidStyle(prop, value) {
|
||||
export function isValidStyle(prop: string, _value: unknown) {
|
||||
if (!validProps.has(prop)) {
|
||||
return false;
|
||||
} else if (prop === "display" && !displayValues.has(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const displayValues = new Set(["none", "flex", undefined]);
|
||||
|
||||
const validProps = new Set([
|
||||
"alignContent",
|
||||
"alignItems",
|
||||
@@ -120,5 +116,3 @@ const validProps = new Set([
|
||||
"writingDirection",
|
||||
"zIndex",
|
||||
]);
|
||||
|
||||
module.exports = isValidStyle;
|
||||
160
src/babel/utils/jsx.ts
Normal file
160
src/babel/utils/jsx.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { NodePath } from "@babel/core";
|
||||
import {
|
||||
Expression,
|
||||
isJSXIdentifier,
|
||||
isJSXNamespacedName,
|
||||
JSXAttribute,
|
||||
JSXEmptyExpression,
|
||||
JSXExpressionContainer,
|
||||
JSXIdentifier,
|
||||
JSXMemberExpression,
|
||||
JSXNamespacedName,
|
||||
JSXOpeningElement,
|
||||
JSXSpreadAttribute,
|
||||
StringLiteral,
|
||||
} from "@babel/types";
|
||||
import { Babel } from "../types";
|
||||
|
||||
/**
|
||||
* Get the name of a JXS Element
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Valid JSX:
|
||||
*
|
||||
* <MyComponent />
|
||||
* <A.MyComponent />
|
||||
* <B.A.MyComponent />
|
||||
* <C.B.A.MyComponent />
|
||||
* ..etc
|
||||
*
|
||||
* @privateRemarks
|
||||
*
|
||||
* This was abstracted as it needs to be recursive
|
||||
*
|
||||
*/
|
||||
export function getJSXElementName(node: JSXOpeningElement) {
|
||||
return getElementName(node.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive helper function for getJSXElementName
|
||||
*/
|
||||
function getElementName(
|
||||
node: JSXIdentifier | JSXNamespacedName | JSXMemberExpression
|
||||
): string {
|
||||
if (isJSXIdentifier(node)) {
|
||||
return node.name;
|
||||
} else if (isJSXNamespacedName(node)) {
|
||||
return node.name.name;
|
||||
} else {
|
||||
return getElementName(node.object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expression and attribute of the 'className' & 'style'
|
||||
*
|
||||
* @privateRemarks
|
||||
*
|
||||
* This was abstracted into a function due to the number of code branches
|
||||
*
|
||||
*/
|
||||
export function getStyleAttributesAndValues(
|
||||
{ types: t }: Babel,
|
||||
nodePath: NodePath<JSXOpeningElement>
|
||||
) {
|
||||
let style: JSXEmptyExpression | Expression | undefined;
|
||||
let styleAttribute: NodePath<JSXAttribute | JSXSpreadAttribute> | undefined;
|
||||
let className: StringLiteral | Expression | undefined;
|
||||
let classNameAttribute:
|
||||
| NodePath<JSXAttribute | JSXSpreadAttribute>
|
||||
| undefined;
|
||||
|
||||
/**
|
||||
* Loop over all the attributes
|
||||
*
|
||||
* While silly, this is valid JSX
|
||||
*
|
||||
* <View style={myStyles} style={actualStyles} />
|
||||
*
|
||||
* So we need to loop over all attributes and just take the last ones
|
||||
*
|
||||
* We could reverse the array and break early, but most elements have <10 attributes
|
||||
*/
|
||||
for (const attribute of nodePath.get("attributes")) {
|
||||
// Ignore spreads, we cannot process them
|
||||
if (t.isJSXSpreadAttribute(attribute.node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { name, value } = attribute.node;
|
||||
|
||||
if (t.isJSXIdentifier(name) && name.name === "className") {
|
||||
classNameAttribute = attribute;
|
||||
|
||||
if (t.isStringLiteral(value)) {
|
||||
// className="font-bold"
|
||||
className = value;
|
||||
} else if (t.isJSXExpressionContainer(value)) {
|
||||
// className={myClassNames}
|
||||
|
||||
if (t.isJSXEmptyExpression(value.expression)) {
|
||||
// Do nothing if the expression is empty
|
||||
} else {
|
||||
className = value.expression;
|
||||
}
|
||||
} else {
|
||||
throw nodePath.buildCodeFrameError(
|
||||
"Unable to parse className attribute"
|
||||
);
|
||||
}
|
||||
} else if (t.isJSXIdentifier(name) && name.name === "style") {
|
||||
styleAttribute = attribute;
|
||||
|
||||
/*
|
||||
* Get the style.
|
||||
**/
|
||||
if (t.isJSXExpressionContainer(value)) {
|
||||
// style={styles.myStyles}
|
||||
style = value.expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
className,
|
||||
classNameAttribute,
|
||||
style,
|
||||
styleAttribute,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSXExpressionContainer for a style attribute containing __useParseTailwind().
|
||||
* It will merge with existing styles if provided.
|
||||
*
|
||||
* @privateRemarks
|
||||
*
|
||||
* This was moved into a helper function to increase readbility
|
||||
*/
|
||||
export function createMergedStylesExpressionContainer(
|
||||
{ types: t }: Babel,
|
||||
newExpression: Expression,
|
||||
existingExpression: JSXEmptyExpression | Expression | undefined
|
||||
) {
|
||||
let expressionContainer: JSXExpressionContainer;
|
||||
|
||||
if (t.isMemberExpression(existingExpression)) {
|
||||
expressionContainer = t.jSXExpressionContainer(
|
||||
t.arrayExpression([newExpression, existingExpression])
|
||||
);
|
||||
} else if (t.isArrayExpression(existingExpression)) {
|
||||
existingExpression.elements.unshift(newExpression);
|
||||
expressionContainer = t.jSXExpressionContainer(existingExpression);
|
||||
} else {
|
||||
expressionContainer = t.jSXExpressionContainer(newExpression);
|
||||
}
|
||||
|
||||
return expressionContainer;
|
||||
}
|
||||
32
src/babel/utils/native-variables.ts
Normal file
32
src/babel/utils/native-variables.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Expression, Statement } from "@babel/types";
|
||||
import { Babel } from "../types";
|
||||
|
||||
export function appendVariables(
|
||||
babel: Babel,
|
||||
body: Statement[],
|
||||
styles: Expression,
|
||||
media: Expression
|
||||
) {
|
||||
const { types: t } = babel;
|
||||
|
||||
body.push(
|
||||
t.variableDeclaration("const", [
|
||||
t.variableDeclarator(
|
||||
t.identifier("__tailwindStyles"),
|
||||
t.callExpression(
|
||||
t.memberExpression(
|
||||
t.identifier("StyleSheet"),
|
||||
t.identifier("create")
|
||||
),
|
||||
[styles]
|
||||
)
|
||||
),
|
||||
])
|
||||
);
|
||||
|
||||
body.push(
|
||||
t.variableDeclaration("const", [
|
||||
t.variableDeclarator(t.identifier("__tailwindMedia"), media),
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
const css = require("css");
|
||||
const postcss = require("postcss");
|
||||
const tailwind = require("tailwindcss");
|
||||
const postcssCssvariables = require("postcss-css-variables");
|
||||
const postcssColorRBG = require("postcss-color-rgb");
|
||||
const postcssRemToPixel = require("postcss-rem-to-pixel");
|
||||
const serialize = require("babel-literal-to-ast");
|
||||
const flattenRules = require("./flatten-rules");
|
||||
const normaliseSelector = require("../../../dist/shared/selector");
|
||||
import { Babel, Style } from "../types";
|
||||
|
||||
/** @typedef {import('react-native').ViewStyle | import('react-native').TextStyle | import('react-native').ImageStyle} Style */
|
||||
import css from "css";
|
||||
import postcss from "postcss";
|
||||
import tailwind from "tailwindcss";
|
||||
import postcssCssvariables from "postcss-css-variables";
|
||||
import postcssColorRBG from "postcss-color-rgb";
|
||||
import postcssRemToPixel from "postcss-rem-to-pixel";
|
||||
import serialize from "babel-literal-to-ast";
|
||||
|
||||
function processStyles({ types: t }, tailwindConfig) {
|
||||
import { flattenRules } from "./flatten-rules";
|
||||
import { normaliseSelector } from "../../shared/selector";
|
||||
import { TailwindConfig } from "tailwindcss/tailwind-config";
|
||||
|
||||
export function processStyles(babel: Babel, tailwindConfig: TailwindConfig) {
|
||||
const cssInput = "@tailwind utilities";
|
||||
|
||||
/** @type {string} */
|
||||
const processedCss = postcss([
|
||||
tailwind(tailwindConfig),
|
||||
postcssCssvariables(),
|
||||
@@ -33,19 +34,19 @@ function processStyles({ types: t }, tailwindConfig) {
|
||||
}),
|
||||
]).process(cssInput).css;
|
||||
|
||||
const cssRules = css.parse(processedCss).stylesheet.rules;
|
||||
const cssRules = css.parse(processedCss).stylesheet?.rules ?? [];
|
||||
|
||||
const parsedRules = flattenRules(t, cssRules, tailwindConfig);
|
||||
const parsedRules = flattenRules(babel, cssRules, tailwindConfig);
|
||||
|
||||
/** @type {Record<string, Style>} */
|
||||
const styles = {};
|
||||
/** @type {Record<string, Array<{ media: string, suffix: number>} */
|
||||
const mediaRules = {};
|
||||
const styles: Record<string, Style> = {};
|
||||
const mediaRules: Record<
|
||||
string,
|
||||
Array<{ media: string[]; suffix: number }>
|
||||
> = {};
|
||||
|
||||
for (const [suffix, parsedRule] of parsedRules.entries()) {
|
||||
const { selector, media, rules } = parsedRule;
|
||||
|
||||
for (const [
|
||||
suffix,
|
||||
{ selector, media, rules } = {},
|
||||
] of parsedRules.entries()) {
|
||||
const normalisedSelector = normaliseSelector(selector, tailwindConfig);
|
||||
|
||||
if (media.length > 0) {
|
||||
@@ -69,5 +70,3 @@ function processStyles({ types: t }, tailwindConfig) {
|
||||
media: serialize(mediaRules),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = processStyles;
|
||||
78
src/babel/utils/transform-class-names.ts
Normal file
78
src/babel/utils/transform-class-names.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { NodePath } from "@babel/core";
|
||||
import { Expression, JSXOpeningElement } from "@babel/types";
|
||||
import { Babel } from "../types";
|
||||
|
||||
import {
|
||||
createMergedStylesExpressionContainer,
|
||||
getStyleAttributesAndValues,
|
||||
} from "./jsx";
|
||||
|
||||
export interface TransformClassNameOptions {
|
||||
inlineStyles: boolean;
|
||||
}
|
||||
|
||||
export function transformClassName(
|
||||
babel: Babel,
|
||||
path: NodePath<JSXOpeningElement>,
|
||||
{ inlineStyles }: TransformClassNameOptions
|
||||
): boolean {
|
||||
const {
|
||||
className: existingClassName,
|
||||
classNameAttribute: existingClassNameAttribute,
|
||||
styleAttribute: existingStyleAttribute,
|
||||
style: existingStyle,
|
||||
} = getStyleAttributesAndValues(babel, path);
|
||||
|
||||
/**
|
||||
* If we didn't find any classNames, return early
|
||||
*/
|
||||
if (!existingClassName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the existing attributes, we are going to add a new ones
|
||||
*/
|
||||
existingClassNameAttribute?.remove();
|
||||
existingStyleAttribute?.remove();
|
||||
|
||||
/**
|
||||
* If there are existing styles we need to merge them
|
||||
*
|
||||
* Classnames have lower specificity than inline styles
|
||||
* so they should always be first
|
||||
*/
|
||||
const { types: t } = babel;
|
||||
const callExpressionArguments: Array<Expression> = inlineStyles
|
||||
? [
|
||||
existingClassName,
|
||||
t.objectExpression([
|
||||
t.objectProperty(
|
||||
t.identifier("styles"),
|
||||
t.identifier("__tailwindStyles")
|
||||
),
|
||||
t.objectProperty(
|
||||
t.identifier("media"),
|
||||
t.identifier("__tailwindMedia")
|
||||
),
|
||||
]),
|
||||
]
|
||||
: [existingClassName];
|
||||
|
||||
const hookExpression = t.callExpression(
|
||||
t.identifier("__useParseTailwind"),
|
||||
callExpressionArguments
|
||||
);
|
||||
|
||||
const newStyleExpression = createMergedStylesExpressionContainer(
|
||||
babel,
|
||||
hookExpression,
|
||||
existingStyle
|
||||
);
|
||||
|
||||
path.node.attributes.push(
|
||||
t.jSXAttribute(t.jSXIdentifier("style"), newStyleExpression)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
73
src/babel/web-visitor.ts
Normal file
73
src/babel/web-visitor.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { Visitor } from "@babel/traverse";
|
||||
import { Babel, State } from "./types";
|
||||
import {
|
||||
createMergedStylesExpressionContainer,
|
||||
getJSXElementName,
|
||||
getStyleAttributesAndValues,
|
||||
} from "./utils/jsx";
|
||||
|
||||
export interface WebVisitorState extends State {
|
||||
babel: Babel;
|
||||
}
|
||||
|
||||
export const webVisitor: Visitor<WebVisitorState> = {
|
||||
JSXOpeningElement(path, state) {
|
||||
try {
|
||||
const { types: t } = state.babel;
|
||||
|
||||
const name = getJSXElementName(path.node);
|
||||
const firstCharOfName = name[0];
|
||||
|
||||
// Ignore elements that start in lower case
|
||||
if (
|
||||
firstCharOfName &&
|
||||
firstCharOfName === firstCharOfName.toLowerCase()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
className: existingClassName,
|
||||
classNameAttribute: existingClassNameAttribute,
|
||||
styleAttribute: existingStyleAttribute,
|
||||
style: existingStyle,
|
||||
} = getStyleAttributesAndValues(state.babel, path);
|
||||
|
||||
/**
|
||||
* If we didn't find any classNames, return early
|
||||
*/
|
||||
if (!existingClassName) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the existing attributes, we are going to add a new ones
|
||||
*/
|
||||
existingClassNameAttribute?.remove();
|
||||
existingStyleAttribute?.remove();
|
||||
|
||||
/**
|
||||
* Convert the classNames into a compiled style object
|
||||
*/
|
||||
let newStyle = t.objectExpression([
|
||||
t.objectProperty(t.identifier("$$css"), t.booleanLiteral(true)),
|
||||
t.objectProperty(
|
||||
t.identifier("tailwindcssReactNative"),
|
||||
existingClassName
|
||||
),
|
||||
]);
|
||||
|
||||
const newStyleExpression = createMergedStylesExpressionContainer(
|
||||
state.babel,
|
||||
newStyle,
|
||||
existingStyle
|
||||
);
|
||||
|
||||
path.node.attributes.push(
|
||||
t.jSXAttribute(t.jSXIdentifier("style"), newStyleExpression)
|
||||
);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
27
src/babel/web.ts
Normal file
27
src/babel/web.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NodePath } from "@babel/core";
|
||||
import { Program } from "@babel/types";
|
||||
import { Babel, State } from "./types";
|
||||
import { webVisitor, WebVisitorState } from "./web-visitor";
|
||||
|
||||
export default function (babel: Babel) {
|
||||
return {
|
||||
visitor: {
|
||||
Program: {
|
||||
enter(path: NodePath<Program>, state: State) {
|
||||
// Dirty check the file for the className attribute
|
||||
if (!state.file.code.includes("className=")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const webVisitorState: WebVisitorState = {
|
||||
...state,
|
||||
babel,
|
||||
};
|
||||
|
||||
// Traverse the file
|
||||
path.traverse(webVisitor, webVisitorState);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
import { createContext } from 'react'
|
||||
import { createContext } from "react";
|
||||
import {
|
||||
Appearance,
|
||||
ColorSchemeName,
|
||||
ImageStyle,
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
} from "react-native";
|
||||
|
||||
export interface TailwindStyleContext {
|
||||
styles: StyleRecord
|
||||
media: MediaRules
|
||||
styles: StyleRecord;
|
||||
media: MediaRules;
|
||||
}
|
||||
|
||||
export type StyleRecord = Record<string, ViewStyle | TextStyle | ImageStyle>
|
||||
export type StyleRecord = Record<string, ViewStyle | TextStyle | ImageStyle>;
|
||||
|
||||
export type MediaRules = Record<
|
||||
string,
|
||||
Array<{ media: string[]; suffix: number }>
|
||||
>
|
||||
>;
|
||||
|
||||
export const TailwindStyleContext = createContext<TailwindStyleContext>({
|
||||
styles: {},
|
||||
media: {},
|
||||
})
|
||||
});
|
||||
|
||||
export const TailwindColorSchemeContext = createContext<ColorSchemeName>(
|
||||
Appearance.getColorScheme()
|
||||
)
|
||||
);
|
||||
export const TailwindSetColorSchemeContext = createContext<
|
||||
(colorScheme: ColorSchemeName) => void
|
||||
>(() => {})
|
||||
>(() => {});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './provider'
|
||||
export * from './context'
|
||||
export * from './use-parse-tailwind'
|
||||
export * from "./provider";
|
||||
export * from "./context";
|
||||
export * from "./use-parse-tailwind";
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/** @typedef {{ important?: string }} NormaliseSelectorOptions */
|
||||
|
||||
/**
|
||||
* Normalise a css selector/rule into a standard format
|
||||
* @param {string} selector
|
||||
* @param {NormaliseSelectorOptions} options
|
||||
* @returns {string}
|
||||
*/
|
||||
function normaliseSelector(selector, options = {}) {
|
||||
const { important } = options;
|
||||
|
||||
const s = important
|
||||
? selector.replace(new RegExp(`^${important}`), "")
|
||||
: selector;
|
||||
|
||||
// prettier-ignore
|
||||
return s
|
||||
.split(" ").join("")
|
||||
.split("\\").join("")
|
||||
.split(":").join("_")
|
||||
.replace(/^\./, "");
|
||||
}
|
||||
|
||||
module.exports = normaliseSelector;
|
||||
18
src/shared/selector.ts
Normal file
18
src/shared/selector.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { TailwindConfig } from "tailwindcss/tailwind-config";
|
||||
|
||||
/**
|
||||
* Normalise a selector so it can be used as an object key
|
||||
*/
|
||||
export function normaliseSelector(
|
||||
selector: string,
|
||||
{ important }: Partial<TailwindConfig> = {}
|
||||
) {
|
||||
const leadingDots = "^\\.";
|
||||
|
||||
const regex =
|
||||
typeof important === "string"
|
||||
? new RegExp(`^${important}|${leadingDots}`)
|
||||
: new RegExp(leadingDots);
|
||||
|
||||
return selector.replace(regex, "").replace(/\s/g, "_");
|
||||
}
|
||||
15
src/types/node-modules.d.ts
vendored
Normal file
15
src/types/node-modules.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
declare module "postcss-css-variables" {
|
||||
export default unknown;
|
||||
}
|
||||
|
||||
declare module "postcss-color-rgb" {
|
||||
export default unknown;
|
||||
}
|
||||
|
||||
declare module "postcss-rem-to-pixel" {
|
||||
export default unknown;
|
||||
}
|
||||
|
||||
declare module "babel-literal-to-ast" {
|
||||
export default unknown;
|
||||
}
|
||||
3
src/types/react-native-web.d.ts
vendored
3
src/types/react-native-web.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
declare module "react-native-web" {
|
||||
export const Appearance = any;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext } from "react";
|
||||
import normaliseSelector from "./shared/selector";
|
||||
import { normaliseSelector } from "./shared/selector";
|
||||
import {
|
||||
MediaRules,
|
||||
StyleRecord,
|
||||
|
||||
Reference in New Issue
Block a user