diff --git a/__tests__/tailwindcss/container.spec.ts b/__tests__/tailwindcss/container.spec.ts new file mode 100644 index 0000000..51d7eda --- /dev/null +++ b/__tests__/tailwindcss/container.spec.ts @@ -0,0 +1,68 @@ +import { tailwindRunner, Case } from "./runner"; + +const cases: Array = [ + [ + "Layout - Container", + [ + [ + "container", + { + styles: { + container: { width: "100%" }, + container1: { maxWidth: 640 }, + container2: { maxWidth: 768 }, + container3: { maxWidth: 1024 }, + container4: { maxWidth: 1280 }, + container5: { maxWidth: 1536 }, + }, + media: { + container: [ + { media: ["(min-width: 640px)"], suffix: 1 }, + { media: ["(min-width: 768px)"], suffix: 2 }, + { media: ["(min-width: 1024px)"], suffix: 3 }, + { media: ["(min-width: 1280px)"], suffix: 4 }, + { media: ["(min-width: 1536px)"], suffix: 5 }, + ], + }, + }, + ], + [ + "sm:container", + { + styles: { + "sm\\:container0": { width: "100%" }, + "sm\\:container1": { maxWidth: 640 }, + "sm\\:container2": { maxWidth: 768 }, + "sm\\:container3": { maxWidth: 1024 }, + "sm\\:container4": { maxWidth: 1280 }, + "sm\\:container5": { maxWidth: 1536 }, + }, + media: { + "sm\\:container": [ + { media: ["(min-width: 640px)"], suffix: 0 }, + { media: ["(min-width: 640px)"], suffix: 1 }, + { + media: ["(min-width: 640px)", "(min-width: 768px)"], + suffix: 2, + }, + { + media: ["(min-width: 640px)", "(min-width: 1024px)"], + suffix: 3, + }, + { + media: ["(min-width: 640px)", "(min-width: 1280px)"], + suffix: 4, + }, + { + media: ["(min-width: 640px)", "(min-width: 1536px)"], + suffix: 5, + }, + ], + }, + }, + ], + ], + ], +]; + +tailwindRunner(cases); diff --git a/__tests__/tailwindcss/runner.ts b/__tests__/tailwindcss/runner.ts new file mode 100644 index 0000000..14fa564 --- /dev/null +++ b/__tests__/tailwindcss/runner.ts @@ -0,0 +1,30 @@ +import { MediaRecord, StyleRecord } from "../../src/babel/types"; +import { processStyles } from "../../src/babel/utils/process-styles"; + +export type Case = [string, Array]; +export type Test = [string, Expected]; + +export interface Expected { + styles: StyleRecord; + media?: MediaRecord; +} + +export function tailwindRunner(cases: Case[]) { + describe.each(cases)("%s", (_, testCases) => { + test.each(testCases)( + "%s", + (css, { styles: expectedStyles, media: expectedMedia }) => { + const { styles, media } = processStyles({ + theme: {}, + content: [{ raw: `
`, extension: "html" } as any], + }); + + expect(styles).toEqual(expectedStyles); + + if (expectedMedia) { + expect(media).toEqual(expectedMedia); + } + } + ); + }); +} diff --git a/jest.config.js b/jest.config.js index cbd76b8..21c9ffd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,5 +7,6 @@ module.exports = { "/__tests__/native-context-fixtures/", "/__tests__/native-inline-fixtures/", "/__tests__/web-fixtures/", + "/__tests__/tailwindcss/runner.ts", ], }; diff --git a/src/babel/native-context.ts b/src/babel/native-context.ts index bf8af8a..6918794 100644 --- a/src/babel/native-context.ts +++ b/src/babel/native-context.ts @@ -15,7 +15,7 @@ export default function ( cwd: string ) { const tailwindConfig = getTailwindConfig(cwd, options); - const { styles, media } = processStyles(babel, tailwindConfig); + const { styles, media } = processStyles(tailwindConfig); return { visitor: { diff --git a/src/babel/native-inline.ts b/src/babel/native-inline.ts index 60510a8..f4e95a4 100644 --- a/src/babel/native-inline.ts +++ b/src/babel/native-inline.ts @@ -71,7 +71,7 @@ export default function ( /** * Override tailwind to only process the classnames in this file */ - const { styles, media } = processStyles(babel, { + const { styles, media } = processStyles({ ...tailwindConfig, // Make sure its relative to the tailwind.config.js content: [relative(rootDir, filename)], diff --git a/src/babel/types.d.ts b/src/babel/types.d.ts index ee26988..459ec0f 100644 --- a/src/babel/types.d.ts +++ b/src/babel/types.d.ts @@ -1,9 +1,15 @@ 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 type Style = ViewStyle | TextStyle | ImageStyle; +export type StyleRecord = Record; +export type MediaRecord = Record< + string, + Array<{ media: string[]; suffix: number }> +>; + export interface TailwindReactNativeOptions { tailwindConfigPath?: string; platform?: "web" | "native" | "native-context" | "native-inline"; diff --git a/src/babel/utils/flatten-rules.ts b/src/babel/utils/flatten-rules.ts index 3da09c1..28e09e0 100644 --- a/src/babel/utils/flatten-rules.ts +++ b/src/babel/utils/flatten-rules.ts @@ -3,7 +3,7 @@ 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 { Style } from "../types"; import { isValidStyle } from "./is-valid-style"; interface CssRule { @@ -19,14 +19,13 @@ interface CssRule { * - 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, [ + return flattenRules(cssRule.rules ?? [], tailwindConfig, [ ...new Set(cssRule.media ? [...media, cssRule.media] : media), ]); } else if (isRule(cssRule)) { diff --git a/src/babel/utils/native-variables.ts b/src/babel/utils/native-variables.ts index 7f32257..e639e27 100644 --- a/src/babel/utils/native-variables.ts +++ b/src/babel/utils/native-variables.ts @@ -1,11 +1,12 @@ -import { Expression, Statement } from "@babel/types"; -import { Babel } from "../types"; +import { Statement } from "@babel/types"; +import serialize from "babel-literal-to-ast"; +import { Babel, MediaRecord, StyleRecord } from "../types"; export function appendVariables( babel: Babel, body: Statement[], - styles: Expression, - media: Expression + styles: StyleRecord, + media: MediaRecord ) { const { types: t } = babel; @@ -18,7 +19,7 @@ export function appendVariables( t.identifier("StyleSheet"), t.identifier("create") ), - [styles] + [serialize(styles)] ) ), ]) @@ -26,7 +27,7 @@ export function appendVariables( body.push( t.variableDeclaration("const", [ - t.variableDeclarator(t.identifier("__tailwindMedia"), media), + t.variableDeclarator(t.identifier("__tailwindMedia"), serialize(media)), ]) ); } diff --git a/src/babel/utils/process-styles.ts b/src/babel/utils/process-styles.ts index 606cd27..c34b0d6 100644 --- a/src/babel/utils/process-styles.ts +++ b/src/babel/utils/process-styles.ts @@ -1,4 +1,4 @@ -import { Babel, Style } from "../types"; +import { MediaRecord, Style, StyleRecord } from "../types"; import css from "css"; import postcss from "postcss"; @@ -6,15 +6,15 @@ 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"; 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"; - +export function processStyles( + tailwindConfig: TailwindConfig, + cssInput: string = "@tailwind components;@tailwind utilities;" +) { const processedCss = postcss([ tailwind(tailwindConfig), postcssCssvariables(), @@ -36,13 +36,10 @@ export function processStyles(babel: Babel, tailwindConfig: TailwindConfig) { const cssRules = css.parse(processedCss).stylesheet?.rules ?? []; - const parsedRules = flattenRules(babel, cssRules, tailwindConfig); + const parsedRules = flattenRules(cssRules, tailwindConfig); - const styles: Record = {}; - const mediaRules: Record< - string, - Array<{ media: string[]; suffix: number }> - > = {}; + const styles: StyleRecord = {}; + const mediaRules: MediaRecord = {}; for (const [suffix, parsedRule] of parsedRules.entries()) { const { selector, media, rules } = parsedRule; @@ -66,7 +63,7 @@ export function processStyles(babel: Babel, tailwindConfig: TailwindConfig) { } return { - styles: serialize(styles), - media: serialize(mediaRules), + styles, + media: mediaRules, }; }