diff --git a/README.md b/README.md
index 61aa405..ef50f2c 100644
--- a/README.md
+++ b/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)
diff --git a/__tests__/fixtures.spec.js b/__tests__/fixtures.spec.ts
similarity index 72%
rename from __tests__/fixtures.spec.js
rename to __tests__/fixtures.spec.ts
index 3d3881e..764afee 100644
--- a/__tests__/fixtures.spec.js
+++ b/__tests__/fixtures.spec.ts
@@ -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,
});
diff --git a/__tests__/native-context-fixtures/basic/code.js b/__tests__/native-context-fixtures/basic/code.ts
similarity index 100%
rename from __tests__/native-context-fixtures/basic/code.js
rename to __tests__/native-context-fixtures/basic/code.ts
diff --git a/__tests__/native-context-fixtures/basic/output.js b/__tests__/native-context-fixtures/basic/output.js
deleted file mode 100644
index 9a0440f..0000000
--- a/__tests__/native-context-fixtures/basic/output.js
+++ /dev/null
@@ -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 = {};
diff --git a/__tests__/native-context-fixtures/basic/output.ts b/__tests__/native-context-fixtures/basic/output.ts
new file mode 100644
index 0000000..e877aeb
--- /dev/null
+++ b/__tests__/native-context-fixtures/basic/output.ts
@@ -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 (
+
+ Hello world!
+
+ );
+}
+
+const __tailwindStyles = StyleSheet.create({
+ "font-bold": {
+ fontWeight: "700",
+ },
+});
+
+const __tailwindMedia = {};
diff --git a/__tests__/native-inline-fixtures/basic/code.js b/__tests__/native-inline-fixtures/basic/code.ts
similarity index 100%
rename from __tests__/native-inline-fixtures/basic/code.js
rename to __tests__/native-inline-fixtures/basic/code.ts
diff --git a/__tests__/native-inline-fixtures/basic/options.json b/__tests__/native-inline-fixtures/basic/options.json
index 308d14a..05821a5 100644
--- a/__tests__/native-inline-fixtures/basic/options.json
+++ b/__tests__/native-inline-fixtures/basic/options.json
@@ -1,4 +1,3 @@
{
- "tailwindConfigPath": "./__tests__/native-inline-fixtures/basic/tailwind.config.js",
"platform": "native-inline"
}
diff --git a/__tests__/native-inline-fixtures/basic/output.js b/__tests__/native-inline-fixtures/basic/output.js
deleted file mode 100644
index 30c88c8..0000000
--- a/__tests__/native-inline-fixtures/basic/output.js
+++ /dev/null
@@ -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 = {};
diff --git a/__tests__/native-inline-fixtures/basic/output.ts b/__tests__/native-inline-fixtures/basic/output.ts
new file mode 100644
index 0000000..8cfbf25
--- /dev/null
+++ b/__tests__/native-inline-fixtures/basic/output.ts
@@ -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 (
+
+
+ Hello world!
+
+
+ );
+}
+
+const __tailwindStyles = StyleSheet.create({
+ "font-bold": {
+ fontWeight: "700",
+ },
+});
+
+const __tailwindMedia = {};
diff --git a/__tests__/native-inline-fixtures/basic/tailwind.config.js b/__tests__/native-inline-fixtures/basic/tailwind.config.js
deleted file mode 100644
index 4b4d7a3..0000000
--- a/__tests__/native-inline-fixtures/basic/tailwind.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
-};
diff --git a/__tests__/native-inline-fixtures/conditionals/code.js b/__tests__/native-inline-fixtures/conditionals/code.ts
similarity index 100%
rename from __tests__/native-inline-fixtures/conditionals/code.js
rename to __tests__/native-inline-fixtures/conditionals/code.ts
diff --git a/__tests__/native-inline-fixtures/conditionals/options.json b/__tests__/native-inline-fixtures/conditionals/options.json
index 67a5eaf..05821a5 100644
--- a/__tests__/native-inline-fixtures/conditionals/options.json
+++ b/__tests__/native-inline-fixtures/conditionals/options.json
@@ -1,4 +1,3 @@
{
- "tailwindConfigPath": "./__tests__/native-inline-fixtures/conditionals/tailwind.config.js",
"platform": "native-inline"
}
diff --git a/__tests__/native-inline-fixtures/conditionals/output.js b/__tests__/native-inline-fixtures/conditionals/output.js
deleted file mode 100644
index c1f6640..0000000
--- a/__tests__/native-inline-fixtures/conditionals/output.js
+++ /dev/null
@@ -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 = {};
diff --git a/__tests__/native-inline-fixtures/conditionals/output.ts b/__tests__/native-inline-fixtures/conditionals/output.ts
new file mode 100644
index 0000000..067f59a
--- /dev/null
+++ b/__tests__/native-inline-fixtures/conditionals/output.ts
@@ -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 (
+
+
+ Hello world!
+
+
+ );
+}
+
+const __tailwindStyles = StyleSheet.create({
+ "font-bold": {
+ fontWeight: "700",
+ },
+ underline: {
+ textDecorationLine: "underline",
+ },
+});
+
+const __tailwindMedia = {};
diff --git a/__tests__/native-inline-fixtures/conditionals/tailwind.config.js b/__tests__/native-inline-fixtures/conditionals/tailwind.config.js
deleted file mode 100644
index 4b4d7a3..0000000
--- a/__tests__/native-inline-fixtures/conditionals/tailwind.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
-};
diff --git a/__tests__/native-inline-fixtures/empty-className/code.js b/__tests__/native-inline-fixtures/empty-className/code.ts
similarity index 100%
rename from __tests__/native-inline-fixtures/empty-className/code.js
rename to __tests__/native-inline-fixtures/empty-className/code.ts
diff --git a/__tests__/native-inline-fixtures/empty-className/options.json b/__tests__/native-inline-fixtures/empty-className/options.json
index a4d3376..05821a5 100644
--- a/__tests__/native-inline-fixtures/empty-className/options.json
+++ b/__tests__/native-inline-fixtures/empty-className/options.json
@@ -1,4 +1,3 @@
{
- "tailwindConfigPath": "./__tests__/native-inline-fixtures/empty-className/tailwind.config.js",
"platform": "native-inline"
}
diff --git a/__tests__/native-inline-fixtures/empty-className/output.js b/__tests__/native-inline-fixtures/empty-className/output.js
deleted file mode 100644
index 7963a65..0000000
--- a/__tests__/native-inline-fixtures/empty-className/output.js
+++ /dev/null
@@ -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 = {};
diff --git a/__tests__/native-inline-fixtures/empty-className/output.ts b/__tests__/native-inline-fixtures/empty-className/output.ts
new file mode 100644
index 0000000..5cf11d8
--- /dev/null
+++ b/__tests__/native-inline-fixtures/empty-className/output.ts
@@ -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 (
+
+
+ Hello world!
+
+
+ );
+}
+
+const __tailwindStyles = StyleSheet.create({});
+
+const __tailwindMedia = {};
diff --git a/__tests__/native-inline-fixtures/empty-className/tailwind.config.js b/__tests__/native-inline-fixtures/empty-className/tailwind.config.js
deleted file mode 100644
index 4b4d7a3..0000000
--- a/__tests__/native-inline-fixtures/empty-className/tailwind.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
-};
diff --git a/__tests__/native-inline-fixtures/existing-style/code.js b/__tests__/native-inline-fixtures/existing-style/code.ts
similarity index 100%
rename from __tests__/native-inline-fixtures/existing-style/code.js
rename to __tests__/native-inline-fixtures/existing-style/code.ts
diff --git a/__tests__/native-inline-fixtures/existing-style/options.json b/__tests__/native-inline-fixtures/existing-style/options.json
index 8ba6e0e..05821a5 100644
--- a/__tests__/native-inline-fixtures/existing-style/options.json
+++ b/__tests__/native-inline-fixtures/existing-style/options.json
@@ -1,4 +1,3 @@
{
- "tailwindConfigPath": "./__tests__/native-inline-fixtures/existing-style/tailwind.config.js",
"platform": "native-inline"
}
diff --git a/__tests__/native-inline-fixtures/existing-style/output.js b/__tests__/native-inline-fixtures/existing-style/output.js
deleted file mode 100644
index 4406873..0000000
--- a/__tests__/native-inline-fixtures/existing-style/output.js
+++ /dev/null
@@ -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 = {};
diff --git a/__tests__/native-inline-fixtures/existing-style/output.ts b/__tests__/native-inline-fixtures/existing-style/output.ts
new file mode 100644
index 0000000..64abb82
--- /dev/null
+++ b/__tests__/native-inline-fixtures/existing-style/output.ts
@@ -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 (
+
+
+ Hello world!
+
+
+ );
+}
+const styles = StyleSheet.create({
+ test: {
+ color: "blue",
+ },
+});
+
+const __tailwindStyles = StyleSheet.create({
+ "font-bold": {
+ fontWeight: "700",
+ },
+});
+
+const __tailwindMedia = {};
diff --git a/__tests__/native-inline-fixtures/existing-style/tailwind.config.js b/__tests__/native-inline-fixtures/existing-style/tailwind.config.js
deleted file mode 100644
index 4b4d7a3..0000000
--- a/__tests__/native-inline-fixtures/existing-style/tailwind.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
-};
diff --git a/__tests__/native-inline-fixtures/existing-styles/code.js b/__tests__/native-inline-fixtures/existing-styles/code.ts
similarity index 92%
rename from __tests__/native-inline-fixtures/existing-styles/code.js
rename to __tests__/native-inline-fixtures/existing-styles/code.ts
index e0edad3..8c96383 100644
--- a/__tests__/native-inline-fixtures/existing-styles/code.js
+++ b/__tests__/native-inline-fixtures/existing-styles/code.ts
@@ -13,5 +13,5 @@ export function Test() {
const styles = StyleSheet.create({
test: { color: "blue" },
- test2: { color: "blue" },
+ test2: { color: "red" },
});
diff --git a/__tests__/native-inline-fixtures/existing-styles/options.json b/__tests__/native-inline-fixtures/existing-styles/options.json
index 255a233..05821a5 100644
--- a/__tests__/native-inline-fixtures/existing-styles/options.json
+++ b/__tests__/native-inline-fixtures/existing-styles/options.json
@@ -1,4 +1,3 @@
{
- "tailwindConfigPath": "./__tests__/native-inline-fixtures/existing-styles/tailwind.config.js",
"platform": "native-inline"
}
diff --git a/__tests__/native-inline-fixtures/existing-styles/output.js b/__tests__/native-inline-fixtures/existing-styles/output.js
deleted file mode 100644
index 4d915ee..0000000
--- a/__tests__/native-inline-fixtures/existing-styles/output.js
+++ /dev/null
@@ -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 = {};
diff --git a/__tests__/native-inline-fixtures/existing-styles/output.ts b/__tests__/native-inline-fixtures/existing-styles/output.ts
new file mode 100644
index 0000000..9961958
--- /dev/null
+++ b/__tests__/native-inline-fixtures/existing-styles/output.ts
@@ -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 (
+
+
+ Hello world!
+
+
+ );
+}
+const styles = StyleSheet.create({
+ test: {
+ color: "blue",
+ },
+ test2: {
+ color: "red",
+ },
+});
+
+const __tailwindStyles = StyleSheet.create({
+ "font-bold": {
+ fontWeight: "700",
+ },
+});
+
+const __tailwindMedia = {};
diff --git a/__tests__/native-inline-fixtures/existing-styles/tailwind.config.js b/__tests__/native-inline-fixtures/existing-styles/tailwind.config.js
deleted file mode 100644
index 4b4d7a3..0000000
--- a/__tests__/native-inline-fixtures/existing-styles/tailwind.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
-};
diff --git a/__tests__/native-inline-fixtures/ignores-invalid-classes/options.json b/__tests__/native-inline-fixtures/ignores-invalid-classes/options.json
deleted file mode 100644
index 48a27ac..0000000
--- a/__tests__/native-inline-fixtures/ignores-invalid-classes/options.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "tailwindConfigPath": "./__tests__/native-inline-fixtures/ignores-invalid-classes/tailwind.config.js",
- "platform": "native-inline"
-}
diff --git a/__tests__/native-inline-fixtures/ignores-invalid-classes/output.js b/__tests__/native-inline-fixtures/ignores-invalid-classes/output.js
deleted file mode 100644
index d20abc5..0000000
--- a/__tests__/native-inline-fixtures/ignores-invalid-classes/output.js
+++ /dev/null
@@ -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 = {};
diff --git a/__tests__/native-inline-fixtures/ignores-invalid-classes/tailwind.config.js b/__tests__/native-inline-fixtures/ignores-invalid-classes/tailwind.config.js
deleted file mode 100644
index 4b4d7a3..0000000
--- a/__tests__/native-inline-fixtures/ignores-invalid-classes/tailwind.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- content: [`${__dirname}/*.{js,ts,jsx,tsx}`],
-};
diff --git a/__tests__/web-fixtures/basic/code.js b/__tests__/web-fixtures/basic/code.ts
similarity index 100%
rename from __tests__/web-fixtures/basic/code.js
rename to __tests__/web-fixtures/basic/code.ts
diff --git a/__tests__/web-fixtures/basic/output.js b/__tests__/web-fixtures/basic/output.js
deleted file mode 100644
index 9eeafa9..0000000
--- a/__tests__/web-fixtures/basic/output.js
+++ /dev/null
@@ -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!",
- }),
- });
-}
diff --git a/__tests__/native-inline-fixtures/ignores-invalid-classes/code.js b/__tests__/web-fixtures/basic/output.ts
similarity index 53%
rename from __tests__/native-inline-fixtures/ignores-invalid-classes/code.js
rename to __tests__/web-fixtures/basic/output.ts
index 01238c5..c5ecb61 100644
--- a/__tests__/native-inline-fixtures/ignores-invalid-classes/code.js
+++ b/__tests__/web-fixtures/basic/output.ts
@@ -1,10 +1,16 @@
import { Text } from "react-native";
import { TailwindProvider } from "../../../src";
-
export function Test() {
return (
- Hello world!
+
+ Hello world!
+
);
}
diff --git a/__tests__/web-fixtures/ignores-native-element/output.js b/__tests__/web-fixtures/ignores-native-element/output.js
index 79a35a8..5277647 100644
--- a/__tests__/web-fixtures/ignores-native-element/output.js
+++ b/__tests__/web-fixtures/ignores-native-element/output.js
@@ -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 (
+
+
+ Hello world!
+
+ Should be untransformed
+
+ );
}
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index 66327f5..0000000
--- a/babel.config.js
+++ /dev/null
@@ -1,16 +0,0 @@
-module.exports = {
- presets: [
- [
- "module:metro-react-native-babel-preset",
- { useTransformReactJSXExperimental: true },
- ],
- ],
- plugins: [
- [
- "@babel/plugin-transform-react-jsx",
- {
- runtime: "automatic",
- },
- ],
- ],
-};
diff --git a/babel.js b/babel.js
new file mode 100644
index 0000000..e42d8d2
--- /dev/null
+++ b/babel.js
@@ -0,0 +1 @@
+module.exports = require("./dist/babel").default;
diff --git a/babel/index.js b/babel/index.js
deleted file mode 100644
index cdc8a7f..0000000
--- a/babel/index.js
+++ /dev/null
@@ -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;
diff --git a/babel/native/context.js b/babel/native/context.js
deleted file mode 100644
index f2fbea3..0000000
--- a/babel/native/context.js
+++ /dev/null
@@ -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;
diff --git a/babel/native/index.js b/babel/native/index.js
deleted file mode 100644
index be6d034..0000000
--- a/babel/native/index.js
+++ /dev/null
@@ -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;
diff --git a/babel/native/inline.js b/babel/native/inline.js
deleted file mode 100644
index 2bbc424..0000000
--- a/babel/native/inline.js
+++ /dev/null
@@ -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: ``
- * Out:
- *
- * ```
- * import { __useParseTailwind } from 'tailwind-react-native'
- * import { StyleSheet } from 'react-native'
- *
- * const __tailwindStyles = StyleSheet.create()
- * const __tailwindMedia =
- * ```
- */
-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;
diff --git a/babel/native/utils/babel-imports.js b/babel/native/utils/babel-imports.js
deleted file mode 100644
index 9d7f780..0000000
--- a/babel/native/utils/babel-imports.js
+++ /dev/null
@@ -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,
-}
diff --git a/babel/native/utils/babel-variables.js b/babel/native/utils/babel-variables.js
deleted file mode 100644
index 00de43d..0000000
--- a/babel/native/utils/babel-variables.js
+++ /dev/null
@@ -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
diff --git a/babel/native/utils/flatten-rules.js b/babel/native/utils/flatten-rules.js
deleted file mode 100644
index 117a932..0000000
--- a/babel/native/utils/flatten-rules.js
+++ /dev/null
@@ -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 | undefined} media
- * @returns {Array}
- */
-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} */
- const selectors = (cssRule.selectors || []).map((selectorDirty) => {
- const selector = normaliseSelector(selectorDirty, options);
-
- return {
- selector,
- media,
- rules,
- };
- });
-
- return selectors;
- } else {
- return [];
- }
- });
-}
-
-module.exports = flattenRules;
diff --git a/babel/native/utils/transform-class-names.js b/babel/native/utils/transform-class-names.js
deleted file mode 100644
index 940bb7f..0000000
--- a/babel/native/utils/transform-class-names.js
+++ /dev/null
@@ -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;
diff --git a/babel/web.js b/babel/web.js
deleted file mode 100644
index 587aa84..0000000
--- a/babel/web.js
+++ /dev/null
@@ -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;
diff --git a/docs/library-comparision.md b/docs/library-comparision.md
index 00450b8..6e41f95 100644
--- a/docs/library-comparision.md
+++ b/docs/library-comparision.md
@@ -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`
diff --git a/docs/platforms.md b/docs/platforms.md
index c384829..6d969a7 100644
--- a/docs/platforms.md
+++ b/docs/platforms.md
@@ -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"
diff --git a/jest.config.js b/jest.config.js
index 10db84f..cbd76b8 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -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/",
diff --git a/package-lock.json b/package-lock.json
index cbcd03b..aa0a7c6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index c5138bb..8166ba7 100644
--- a/package.json
+++ b/package.json
@@ -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": {
diff --git a/src/babel/index.ts b/src/babel/index.ts
new file mode 100644
index 0000000..859fa68
--- /dev/null
+++ b/src/babel/index.ts
@@ -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
+ );
+}
diff --git a/src/babel/native-context.ts b/src/babel/native-context.ts
new file mode 100644
index 0000000..b12148e
--- /dev/null
+++ b/src/babel/native-context.ts
@@ -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, 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 = {
+ 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"))
+ )
+ );
+ }
+ },
+};
diff --git a/src/babel/native-inline.ts b/src/babel/native-inline.ts
new file mode 100644
index 0000000..de3b916
--- /dev/null
+++ b/src/babel/native-inline.ts
@@ -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, 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);
+ },
+ },
+ },
+ };
+}
diff --git a/src/babel/native-visitor.ts b/src/babel/native-visitor.ts
new file mode 100644
index 0000000..d5a4e77
--- /dev/null
+++ b/src/babel/native-visitor.ts
@@ -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;
+ hasUseParseTailwind: boolean;
+ hasStyleSheetImport: boolean;
+ hasClassNames: boolean;
+ hasProvider: boolean;
+ visitor?: Visitor;
+ 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 = {
+ 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);
+ }
+ },
+};
diff --git a/src/babel/types.d.ts b/src/babel/types.d.ts
new file mode 100644
index 0000000..ee26988
--- /dev/null
+++ b/src/babel/types.d.ts
@@ -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;
+ deniedImports?: Array;
+}
+
+export type State = {
+ get: (name: string) => any;
+ set: (name: string, value: any) => any;
+ opts: TailwindReactNativeOptions;
+ file: BabelCore.BabelFile;
+ filename: string;
+};
diff --git a/src/babel/utils/flatten-rules.ts b/src/babel/utils/flatten-rules.ts
new file mode 100644
index 0000000..3da09c1
--- /dev/null
+++ b/src/babel/utils/flatten-rules.ts
@@ -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";
+}
diff --git a/babel/native/utils/get-tailwind-config.js b/src/babel/utils/get-tailwind-config.ts
similarity index 54%
rename from babel/native/utils/get-tailwind-config.js
rename to src/babel/utils/get-tailwind-config.ts
index af563d5..d0650bf 100644
--- a/babel/native/utils/get-tailwind-config.js
+++ b/src/babel/utils/get-tailwind-config.ts
@@ -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;
diff --git a/src/babel/utils/imports.ts b/src/babel/utils/imports.ts
new file mode 100644
index 0000000..39cc931
--- /dev/null
+++ b/src/babel/utils/imports.ts
@@ -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,
+ 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,
+ 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;
+ });
+}
diff --git a/babel/native/utils/is-valid-style.js b/src/babel/utils/is-valid-style.ts
similarity index 90%
rename from babel/native/utils/is-valid-style.js
rename to src/babel/utils/is-valid-style.ts
index a6f4508..d3f4a73 100644
--- a/babel/native/utils/is-valid-style.js
+++ b/src/babel/utils/is-valid-style.ts
@@ -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;
diff --git a/src/babel/utils/jsx.ts b/src/babel/utils/jsx.ts
new file mode 100644
index 0000000..4efc844
--- /dev/null
+++ b/src/babel/utils/jsx.ts
@@ -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:
+ *
+ *
+ *
+ *
+ *
+ * ..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
+) {
+ let style: JSXEmptyExpression | Expression | undefined;
+ let styleAttribute: NodePath | undefined;
+ let className: StringLiteral | Expression | undefined;
+ let classNameAttribute:
+ | NodePath
+ | undefined;
+
+ /**
+ * Loop over all the attributes
+ *
+ * While silly, this is valid JSX
+ *
+ *
+ *
+ * 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;
+}
diff --git a/src/babel/utils/native-variables.ts b/src/babel/utils/native-variables.ts
new file mode 100644
index 0000000..7f32257
--- /dev/null
+++ b/src/babel/utils/native-variables.ts
@@ -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),
+ ])
+ );
+}
diff --git a/babel/native/utils/process-styles.js b/src/babel/utils/process-styles.ts
similarity index 52%
rename from babel/native/utils/process-styles.js
rename to src/babel/utils/process-styles.ts
index 3e9f2de..606cd27 100644
--- a/babel/native/utils/process-styles.js
+++ b/src/babel/utils/process-styles.ts
@@ -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} */
- const styles = {};
- /** @type {Record} */
- const mediaRules = {};
+ const styles: Record = {};
+ 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;
diff --git a/src/babel/utils/transform-class-names.ts b/src/babel/utils/transform-class-names.ts
new file mode 100644
index 0000000..d3bfc69
--- /dev/null
+++ b/src/babel/utils/transform-class-names.ts
@@ -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,
+ { 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 = 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;
+}
diff --git a/src/babel/web-visitor.ts b/src/babel/web-visitor.ts
new file mode 100644
index 0000000..ea39130
--- /dev/null
+++ b/src/babel/web-visitor.ts
@@ -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 = {
+ 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;
+ }
+ },
+};
diff --git a/src/babel/web.ts b/src/babel/web.ts
new file mode 100644
index 0000000..b1e73ec
--- /dev/null
+++ b/src/babel/web.ts
@@ -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, 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);
+ },
+ },
+ },
+ };
+}
diff --git a/src/context.ts b/src/context.ts
index 798fae6..676d994 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -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
+export type StyleRecord = Record;
export type MediaRules = Record<
string,
Array<{ media: string[]; suffix: number }>
->
+>;
export const TailwindStyleContext = createContext({
styles: {},
media: {},
-})
+});
export const TailwindColorSchemeContext = createContext(
Appearance.getColorScheme()
-)
+);
export const TailwindSetColorSchemeContext = createContext<
(colorScheme: ColorSchemeName) => void
->(() => {})
+>(() => {});
diff --git a/src/index.ts b/src/index.ts
index 67dc56c..048880b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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";
diff --git a/src/shared/selector.js b/src/shared/selector.js
deleted file mode 100644
index 55477b3..0000000
--- a/src/shared/selector.js
+++ /dev/null
@@ -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;
diff --git a/src/shared/selector.ts b/src/shared/selector.ts
new file mode 100644
index 0000000..9b2b6a4
--- /dev/null
+++ b/src/shared/selector.ts
@@ -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 = {}
+) {
+ const leadingDots = "^\\.";
+
+ const regex =
+ typeof important === "string"
+ ? new RegExp(`^${important}|${leadingDots}`)
+ : new RegExp(leadingDots);
+
+ return selector.replace(regex, "").replace(/\s/g, "_");
+}
diff --git a/src/types/node-modules.d.ts b/src/types/node-modules.d.ts
new file mode 100644
index 0000000..09a98af
--- /dev/null
+++ b/src/types/node-modules.d.ts
@@ -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;
+}
diff --git a/src/types/react-native-web.d.ts b/src/types/react-native-web.d.ts
deleted file mode 100644
index 969de0d..0000000
--- a/src/types/react-native-web.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-declare module "react-native-web" {
- export const Appearance = any;
-}
diff --git a/src/use-parse-tailwind.ts b/src/use-parse-tailwind.ts
index ea5e285..035ddbe 100644
--- a/src/use-parse-tailwind.ts
+++ b/src/use-parse-tailwind.ts
@@ -1,5 +1,5 @@
import { useContext } from "react";
-import normaliseSelector from "./shared/selector";
+import { normaliseSelector } from "./shared/selector";
import {
MediaRules,
StyleRecord,