From 96d123dc0c92e4eb9f1a8dafb83086756baf9386 Mon Sep 17 00:00:00 2001 From: Rodney Leviton Date: Tue, 17 Sep 2024 21:42:21 +1000 Subject: [PATCH] hurdy gurdy --- packages/lib/tsconfig.base.json | 4 ++-- packages/plugin/dist/index.js | 4 ++-- packages/plugin/dist/index.js.map | 2 +- packages/plugin/tsconfig.json | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/lib/tsconfig.base.json b/packages/lib/tsconfig.base.json index 4afbbac..70dc3d1 100644 --- a/packages/lib/tsconfig.base.json +++ b/packages/lib/tsconfig.base.json @@ -22,6 +22,6 @@ "types": ["node", "react", "vitest/globals", "@testing-library/jest-dom"], "useDefineForClassFields": true }, - "include": ["src", "./src/__tests__/setup.ts"], - "exclude": ["node_modules"] + "include": ["src", "src/__tests__/setup.ts"], + "exclude": ["src/**/*.test.ts", "node_modules"] } diff --git a/packages/plugin/dist/index.js b/packages/plugin/dist/index.js index 71f7c42..3e86a82 100644 --- a/packages/plugin/dist/index.js +++ b/packages/plugin/dist/index.js @@ -115,7 +115,7 @@ function addToSafelist(safelist, classes, prefix = "") { // src/index.ts import { glob } from "glob"; -import fs from "fs"; +import { readFileSync } from "fs"; var src_default = plugin(function({ addVariant, config }) { const safelist = /* @__PURE__ */ new Set(); const components = {}; @@ -131,7 +131,7 @@ var src_default = plugin(function({ addVariant, config }) { filePatterns.forEach((pattern) => { const files = glob.sync(pattern); files.forEach((file) => { - const content = fs.readFileSync(file, "utf8"); + const content = readFileSync(file, "utf8"); Object.assign(components, parseRecastComponents(content)); usages.push(...parseRecastUsages(content)); }); diff --git a/packages/plugin/dist/index.js.map b/packages/plugin/dist/index.js.map index 8497fd7..141617a 100644 --- a/packages/plugin/dist/index.js.map +++ b/packages/plugin/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/utils/index.ts"],"sourcesContent":["import plugin from \"tailwindcss/plugin\";\nimport type { Rule } from \"postcss\";\nimport {\n parseRecastComponents,\n parseRecastUsages,\n addToSafelist,\n RecastComponent,\n RecastUsage,\n getFilePatterns,\n} from \"./utils\";\nimport { glob } from \"glob\";\nimport fs from \"fs\";\n\n/**\n * Recast Tailwind Plugin\n *\n * This plugin extends Tailwind CSS functionality to support Recast components\n * and provides an 'unset' variant for more flexible styling control.\n *\n * @param {Object} helpers - Tailwind plugin helper functions\n * @param {Function} helpers.addVariant - Function to add a new variant\n * @param {Function} helpers.config - Function to access and modify Tailwind config\n */\nexport default plugin(function ({ addVariant, config }) {\n const safelist = new Set();\n const components: Record = {};\n const usages: RecastUsage[] = [];\n\n const contentConfig = config(\"content\");\n\n try {\n if (\n Array.isArray(contentConfig.files) &&\n contentConfig.files.length > 0 &&\n contentConfig.files[0].raw\n ) {\n // Test environment\n const content = contentConfig.files[0].raw;\n Object.assign(components, parseRecastComponents(content));\n usages.push(...parseRecastUsages(content));\n } else {\n // Handle real-world scenario\n const filePatterns = getFilePatterns(contentConfig);\n filePatterns.forEach((pattern) => {\n const files = glob.sync(pattern);\n files.forEach((file) => {\n const content = fs.readFileSync(file, \"utf8\");\n Object.assign(components, parseRecastComponents(content));\n usages.push(...parseRecastUsages(content));\n });\n });\n }\n } catch (error) {\n console.error(\"Error processing content:\", error);\n }\n\n usages.forEach((usage) => {\n const component = components[usage.componentName];\n if (!component) {\n return;\n }\n\n // Add base classes to safelist\n if (component.base) {\n addToSafelist(safelist, component.base);\n }\n\n Object.entries(usage.props).forEach(([propName, propValue]) => {\n const variantGroup = component.variants?.[propName];\n if (!variantGroup) {\n return;\n }\n\n if (typeof propValue === \"object\" && propValue !== null) {\n Object.entries(propValue).forEach(([breakpoint, value]) => {\n if (typeof value === \"string\") {\n const classes = variantGroup[value];\n if (classes) {\n addToSafelist(\n safelist,\n classes,\n breakpoint !== \"default\" ? breakpoint : \"\"\n );\n }\n }\n });\n } else if (typeof propValue === \"string\") {\n const classes = variantGroup[propValue];\n if (classes) {\n addToSafelist(safelist, classes);\n }\n }\n });\n });\n\n /**\n * Adds the 'unset' variant to Tailwind CSS\n *\n * This variant allows for easy \"unsetting\" of CSS properties at specific breakpoints,\n * which is particularly useful for responsive design and when working with design systems.\n *\n * Use cases:\n * 1. Removing styles at specific breakpoints\n * 2. Creating exceptions to inherited styles\n * 3. Simplifying responsive layouts by unsetting properties instead of overriding\n *\n * Example usage:\n *
\n * This text is bold by default, but font weight is unset on medium screens and above.\n *
\n *\n * Note: The 'unset' variant should be used after responsive prefixes (e.g., md:unset:font-bold)\n * to ensure it applies at the correct breakpoint.\n *\n * @param {Object} options - Options passed by Tailwind's plugin system\n * @param {Object} options.container - PostCSS container for rule manipulation\n */\n // @ts-expect-error - works as expected but unsure of typings\n addVariant(\"unset\", ({ container }) => {\n container.walkRules((rule: Rule) => {\n rule.selector = `.unset\\\\:${rule.selector.slice(1)}`;\n rule.walkDecls((decl) => {\n decl.value = \"unset\";\n });\n });\n });\n\n const finalSafelist = Array.from(safelist);\n config().safelist = finalSafelist;\n});\n","import { Parser } from \"acorn\";\nimport jsx from \"acorn-jsx\";\n\nconst JSXParser = Parser.extend(jsx());\n\nexport interface RecastComponent {\n base?: string | string[] | Record;\n variants?: Record<\n string,\n Record>\n >;\n}\n\nexport interface RecastUsage {\n componentName: string;\n props: Record;\n}\n\nexport function parseRecastComponents(\n content: string\n): Record {\n const componentRegex =\n /(?:export\\s+(?:const|default)|const)\\s+(\\w+)\\s*=\\s*recast\\s*\\(\\s*\\w+\\s*,\\s*({[\\s\\S]*?})\\s*\\)/g;\n const components: Record = {};\n\n let match;\n while ((match = componentRegex.exec(content)) !== null) {\n const [, componentName, componentDef] = match;\n try {\n const processedDef = componentDef\n .replace(/(['\"])?([a-zA-Z0-9_]+)(['\"])?:/g, '\"$2\": ')\n .replace(/'/g, '\"')\n .replace(/,\\s*}/g, \"}\")\n .replace(/\\n/g, \" \")\n .trim();\n components[componentName] = JSON.parse(processedDef);\n } catch (e) {\n console.error(`Error parsing component ${componentName}:`, e);\n }\n }\n\n const defaultExportRegex = /export\\s+default\\s+(\\w+)/;\n const defaultExportMatch = content.match(defaultExportRegex);\n if (defaultExportMatch) {\n const componentName = defaultExportMatch[1];\n if (components[componentName]) {\n components[\"default\"] = components[componentName];\n }\n }\n\n return components;\n}\n\nexport function parseRecastUsages(content: string): RecastUsage[] {\n const usageRegex = /<(\\w+)([^>]*)>/g;\n\n return Array.from(content.matchAll(usageRegex)).map(\n ([, componentName, propsString]) => {\n const cleanedPropsString = propsString.replace(/\\s*\\/$/, \"\").trim();\n const props = parseJSXExpression(cleanedPropsString);\n\n return { componentName, props };\n }\n );\n}\n\nfunction parseJSXExpression(str: string): any {\n try {\n const wrappedStr = ``;\n const ast = JSXParser.parse(wrappedStr, { ecmaVersion: 2020 }) as any;\n\n const jsxOpeningElement = ast.body[0].expression.openingElement;\n\n const props: Record = {};\n for (const attr of jsxOpeningElement.attributes) {\n if (attr.type === \"JSXAttribute\") {\n const key = attr.name.name;\n const value = parseJSXAttributeValue(attr.value);\n props[key] = value;\n }\n }\n\n return props;\n } catch (error) {\n return {};\n }\n}\n\nfunction parseJSXAttributeValue(value: any): any {\n if (!value) return true;\n if (value.type === \"Literal\") return value.value;\n if (value.type === \"JSXExpressionContainer\") {\n return parseJSXExpressionValue(value.expression);\n }\n return null;\n}\n\nfunction parseJSXExpressionValue(expression: any): any {\n if (expression.type === \"ObjectExpression\") {\n const obj: Record = {};\n for (const prop of expression.properties) {\n obj[prop.key.name] = parseJSXExpressionValue(prop.value);\n }\n return obj;\n }\n if (expression.type === \"ArrayExpression\") {\n return expression.elements.map(parseJSXExpressionValue);\n }\n if (expression.type === \"Literal\") return expression.value;\n if (expression.type === \"Identifier\") return expression.name;\n return null;\n}\n\nexport function parseProps(propsString: string): Record {\n try {\n // Wrap the props in a dummy JSX element\n const wrappedJSX = ``;\n\n const ast = JSXParser.parse(wrappedJSX, {\n ecmaVersion: 2020,\n sourceType: \"module\",\n }) as any;\n\n // The parsed props will be in the attributes of the JSXOpeningElement\n const jsxElement = ast.body[0].expression;\n const attributes = jsxElement.openingElement.attributes;\n\n return parseJSXAttributes(attributes);\n } catch (error) {\n console.error(\"Error parsing props:\", error);\n return {};\n }\n}\n\nfunction parseJSXAttributes(attributes: any[]): Record {\n const result: Record = {};\n\n for (const attr of attributes) {\n if (attr.type === \"JSXAttribute\") {\n const key = attr.name.name;\n const value = parseJSXAttributeValue(attr.value);\n result[key] = value;\n }\n }\n\n return result;\n}\n\nfunction parseObjectExpression(objectExpression: any): Record {\n const result: Record = {};\n\n for (const property of objectExpression.properties) {\n const key = property.key.name || property.key.value;\n const value = parseJSXExpressionValue(property.value);\n result[key] = value;\n }\n\n return result;\n}\n\nfunction parseExpression(expression: any): any {\n switch (expression.type) {\n case \"Literal\":\n return expression.value;\n case \"ObjectExpression\":\n return parseObjectExpression(expression);\n case \"ArrayExpression\":\n return expression.elements.map(parseExpression);\n case \"JSXElement\":\n // Handle JSX elements if needed\n return parseJSXElement(expression);\n case \"Identifier\":\n // For identifiers like `true`, `false`, `null`\n if (expression.name === \"true\") return true;\n if (expression.name === \"false\") return false;\n if (expression.name === \"null\") return null;\n return expression.name;\n default:\n console.warn(`Unhandled expression type: ${expression.type}`);\n return null;\n }\n}\n\nfunction parseJSXElement(element: any): any {\n // Implement JSX parsing if needed\n // This is a placeholder implementation\n return `<${element.openingElement.name.name} />`;\n}\n\nexport function getFilePatterns(contentConfig: any): string[] {\n if (typeof contentConfig === \"string\") return [contentConfig];\n if (Array.isArray(contentConfig))\n return contentConfig.flatMap(getFilePatterns);\n if (typeof contentConfig === \"object\" && contentConfig !== null) {\n return getFilePatterns(contentConfig.files || []);\n }\n return [];\n}\n\nexport function addToSafelist(\n safelist: Set,\n classes: string | string[] | Record,\n prefix: string = \"\"\n): void {\n const addClassWithPrefix = (cls: string) => {\n const safelistItem = prefix ? `${prefix}:${cls}` : cls;\n safelist.add(safelistItem);\n };\n\n if (typeof classes === \"string\") {\n classes.split(/\\s+/).forEach(addClassWithPrefix);\n } else if (Array.isArray(classes)) {\n classes.forEach(addClassWithPrefix);\n } else if (typeof classes === \"object\" && classes !== null) {\n Object.values(classes).forEach((value) => {\n if (typeof value === \"string\") {\n addClassWithPrefix(value);\n } else if (Array.isArray(value)) {\n value.forEach(addClassWithPrefix);\n }\n });\n }\n}\n"],"mappings":"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","names":[]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/utils/index.ts"],"sourcesContent":["import plugin from \"tailwindcss/plugin\";\nimport type { Rule } from \"postcss\";\nimport {\n parseRecastComponents,\n parseRecastUsages,\n addToSafelist,\n RecastComponent,\n RecastUsage,\n getFilePatterns,\n} from \"./utils\";\nimport { glob } from \"glob\";\nimport { readFileSync } from \"fs\";\n\n/**\n * Recast Tailwind Plugin\n *\n * This plugin extends Tailwind CSS functionality to support Recast components\n * and provides an 'unset' variant for more flexible styling control.\n *\n * @param {Object} helpers - Tailwind plugin helper functions\n * @param {Function} helpers.addVariant - Function to add a new variant\n * @param {Function} helpers.config - Function to access and modify Tailwind config\n */\nexport default plugin(function ({ addVariant, config }) {\n const safelist = new Set();\n const components: Record = {};\n const usages: RecastUsage[] = [];\n\n const contentConfig = config(\"content\");\n\n try {\n if (\n Array.isArray(contentConfig.files) &&\n contentConfig.files.length > 0 &&\n contentConfig.files[0].raw\n ) {\n // Test environment\n const content = contentConfig.files[0].raw;\n Object.assign(components, parseRecastComponents(content));\n usages.push(...parseRecastUsages(content));\n } else {\n // Handle real-world scenario\n const filePatterns = getFilePatterns(contentConfig);\n filePatterns.forEach((pattern) => {\n const files = glob.sync(pattern);\n files.forEach((file) => {\n const content = readFileSync(file, \"utf8\");\n Object.assign(components, parseRecastComponents(content));\n usages.push(...parseRecastUsages(content));\n });\n });\n }\n } catch (error) {\n console.error(\"Error processing content:\", error);\n }\n\n usages.forEach((usage) => {\n const component = components[usage.componentName];\n if (!component) {\n return;\n }\n\n // Add base classes to safelist\n if (component.base) {\n addToSafelist(safelist, component.base);\n }\n\n Object.entries(usage.props).forEach(([propName, propValue]) => {\n const variantGroup = component.variants?.[propName];\n if (!variantGroup) {\n return;\n }\n\n if (typeof propValue === \"object\" && propValue !== null) {\n Object.entries(propValue).forEach(([breakpoint, value]) => {\n if (typeof value === \"string\") {\n const classes = variantGroup[value];\n if (classes) {\n addToSafelist(\n safelist,\n classes,\n breakpoint !== \"default\" ? breakpoint : \"\"\n );\n }\n }\n });\n } else if (typeof propValue === \"string\") {\n const classes = variantGroup[propValue];\n if (classes) {\n addToSafelist(safelist, classes);\n }\n }\n });\n });\n\n /**\n * Adds the 'unset' variant to Tailwind CSS\n *\n * This variant allows for easy \"unsetting\" of CSS properties at specific breakpoints,\n * which is particularly useful for responsive design and when working with design systems.\n *\n * Use cases:\n * 1. Removing styles at specific breakpoints\n * 2. Creating exceptions to inherited styles\n * 3. Simplifying responsive layouts by unsetting properties instead of overriding\n *\n * Example usage:\n *
\n * This text is bold by default, but font weight is unset on medium screens and above.\n *
\n *\n * Note: The 'unset' variant should be used after responsive prefixes (e.g., md:unset:font-bold)\n * to ensure it applies at the correct breakpoint.\n *\n * @param {Object} options - Options passed by Tailwind's plugin system\n * @param {Object} options.container - PostCSS container for rule manipulation\n */\n // @ts-expect-error - works as expected but unsure of typings\n addVariant(\"unset\", ({ container }) => {\n container.walkRules((rule: Rule) => {\n rule.selector = `.unset\\\\:${rule.selector.slice(1)}`;\n rule.walkDecls((decl) => {\n decl.value = \"unset\";\n });\n });\n });\n\n const finalSafelist = Array.from(safelist);\n config().safelist = finalSafelist;\n});\n","import { Parser } from \"acorn\";\nimport jsx from \"acorn-jsx\";\n\nconst JSXParser = Parser.extend(jsx());\n\nexport interface RecastComponent {\n base?: string | string[] | Record;\n variants?: Record<\n string,\n Record>\n >;\n}\n\nexport interface RecastUsage {\n componentName: string;\n props: Record;\n}\n\nexport function parseRecastComponents(\n content: string\n): Record {\n const componentRegex =\n /(?:export\\s+(?:const|default)|const)\\s+(\\w+)\\s*=\\s*recast\\s*\\(\\s*\\w+\\s*,\\s*({[\\s\\S]*?})\\s*\\)/g;\n const components: Record = {};\n\n let match;\n while ((match = componentRegex.exec(content)) !== null) {\n const [, componentName, componentDef] = match;\n try {\n const processedDef = componentDef\n .replace(/(['\"])?([a-zA-Z0-9_]+)(['\"])?:/g, '\"$2\": ')\n .replace(/'/g, '\"')\n .replace(/,\\s*}/g, \"}\")\n .replace(/\\n/g, \" \")\n .trim();\n components[componentName] = JSON.parse(processedDef);\n } catch (e) {\n console.error(`Error parsing component ${componentName}:`, e);\n }\n }\n\n const defaultExportRegex = /export\\s+default\\s+(\\w+)/;\n const defaultExportMatch = content.match(defaultExportRegex);\n if (defaultExportMatch) {\n const componentName = defaultExportMatch[1];\n if (components[componentName]) {\n components[\"default\"] = components[componentName];\n }\n }\n\n return components;\n}\n\nexport function parseRecastUsages(content: string): RecastUsage[] {\n const usageRegex = /<(\\w+)([^>]*)>/g;\n\n return Array.from(content.matchAll(usageRegex)).map(\n ([, componentName, propsString]) => {\n const cleanedPropsString = propsString.replace(/\\s*\\/$/, \"\").trim();\n const props = parseJSXExpression(cleanedPropsString);\n\n return { componentName, props };\n }\n );\n}\n\nfunction parseJSXExpression(str: string): any {\n try {\n const wrappedStr = ``;\n const ast = JSXParser.parse(wrappedStr, { ecmaVersion: 2020 }) as any;\n\n const jsxOpeningElement = ast.body[0].expression.openingElement;\n\n const props: Record = {};\n for (const attr of jsxOpeningElement.attributes) {\n if (attr.type === \"JSXAttribute\") {\n const key = attr.name.name;\n const value = parseJSXAttributeValue(attr.value);\n props[key] = value;\n }\n }\n\n return props;\n } catch (error) {\n return {};\n }\n}\n\nfunction parseJSXAttributeValue(value: any): any {\n if (!value) return true;\n if (value.type === \"Literal\") return value.value;\n if (value.type === \"JSXExpressionContainer\") {\n return parseJSXExpressionValue(value.expression);\n }\n return null;\n}\n\nfunction parseJSXExpressionValue(expression: any): any {\n if (expression.type === \"ObjectExpression\") {\n const obj: Record = {};\n for (const prop of expression.properties) {\n obj[prop.key.name] = parseJSXExpressionValue(prop.value);\n }\n return obj;\n }\n if (expression.type === \"ArrayExpression\") {\n return expression.elements.map(parseJSXExpressionValue);\n }\n if (expression.type === \"Literal\") return expression.value;\n if (expression.type === \"Identifier\") return expression.name;\n return null;\n}\n\nexport function parseProps(propsString: string): Record {\n try {\n // Wrap the props in a dummy JSX element\n const wrappedJSX = ``;\n\n const ast = JSXParser.parse(wrappedJSX, {\n ecmaVersion: 2020,\n sourceType: \"module\",\n }) as any;\n\n // The parsed props will be in the attributes of the JSXOpeningElement\n const jsxElement = ast.body[0].expression;\n const attributes = jsxElement.openingElement.attributes;\n\n return parseJSXAttributes(attributes);\n } catch (error) {\n console.error(\"Error parsing props:\", error);\n return {};\n }\n}\n\nfunction parseJSXAttributes(attributes: any[]): Record {\n const result: Record = {};\n\n for (const attr of attributes) {\n if (attr.type === \"JSXAttribute\") {\n const key = attr.name.name;\n const value = parseJSXAttributeValue(attr.value);\n result[key] = value;\n }\n }\n\n return result;\n}\n\nfunction parseObjectExpression(objectExpression: any): Record {\n const result: Record = {};\n\n for (const property of objectExpression.properties) {\n const key = property.key.name || property.key.value;\n const value = parseJSXExpressionValue(property.value);\n result[key] = value;\n }\n\n return result;\n}\n\nfunction parseExpression(expression: any): any {\n switch (expression.type) {\n case \"Literal\":\n return expression.value;\n case \"ObjectExpression\":\n return parseObjectExpression(expression);\n case \"ArrayExpression\":\n return expression.elements.map(parseExpression);\n case \"JSXElement\":\n // Handle JSX elements if needed\n return parseJSXElement(expression);\n case \"Identifier\":\n // For identifiers like `true`, `false`, `null`\n if (expression.name === \"true\") return true;\n if (expression.name === \"false\") return false;\n if (expression.name === \"null\") return null;\n return expression.name;\n default:\n console.warn(`Unhandled expression type: ${expression.type}`);\n return null;\n }\n}\n\nfunction parseJSXElement(element: any): any {\n // Implement JSX parsing if needed\n // This is a placeholder implementation\n return `<${element.openingElement.name.name} />`;\n}\n\nexport function getFilePatterns(contentConfig: any): string[] {\n if (typeof contentConfig === \"string\") return [contentConfig];\n if (Array.isArray(contentConfig))\n return contentConfig.flatMap(getFilePatterns);\n if (typeof contentConfig === \"object\" && contentConfig !== null) {\n return getFilePatterns(contentConfig.files || []);\n }\n return [];\n}\n\nexport function addToSafelist(\n safelist: Set,\n classes: string | string[] | Record,\n prefix: string = \"\"\n): void {\n const addClassWithPrefix = (cls: string) => {\n const safelistItem = prefix ? `${prefix}:${cls}` : cls;\n safelist.add(safelistItem);\n };\n\n if (typeof classes === \"string\") {\n classes.split(/\\s+/).forEach(addClassWithPrefix);\n } else if (Array.isArray(classes)) {\n classes.forEach(addClassWithPrefix);\n } else if (typeof classes === \"object\" && classes !== null) {\n Object.values(classes).forEach((value) => {\n if (typeof value === \"string\") {\n addClassWithPrefix(value);\n } else if (Array.isArray(value)) {\n value.forEach(addClassWithPrefix);\n }\n });\n }\n}\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/packages/plugin/tsconfig.json b/packages/plugin/tsconfig.json index c3b364c..69a9277 100644 --- a/packages/plugin/tsconfig.json +++ b/packages/plugin/tsconfig.json @@ -9,6 +9,7 @@ "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, "declaration": true, "outDir": "./dist", "rootDir": "./src"