diff --git a/eslint.config.mjs b/eslint.config.mjs index fb8fafe..f4b7c6d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -25,4 +25,5 @@ export default createConfigForNuxt({ '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-vars': 'off', + 'regexp/no-super-linear-backtracking': 'off', }) diff --git a/package.json b/package.json index 0b0dedb..42927f8 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@nuxtjs/tailwindcss": "^6.12.2", "defu": "^6.1.4", "pathe": "^1.1.2", + "scule": "^1.3.0", "tailwind-merge": "^2.5.4", "tailwindcss": "^3.4.15" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7da953d..03bd892 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: pathe: specifier: ^1.1.2 version: 1.1.2 + scule: + specifier: ^1.3.0 + version: 1.3.0 tailwind-merge: specifier: ^2.5.4 version: 2.5.4 diff --git a/src/runtime/utils/colors.ts b/src/runtime/utils/colors.ts index 609c8f9..01a1031 100644 --- a/src/runtime/utils/colors.ts +++ b/src/runtime/utils/colors.ts @@ -1,5 +1,6 @@ import type { Config as TwConfig } from 'tailwindcss' import defaultColors from 'tailwindcss/colors.js' +import { camelCase, upperFirst } from 'scule' import { omit } from './objectUtils' const colorsToRegex = (colors: string[]): string => colors.join('|') @@ -139,3 +140,76 @@ export const generateSafelist = (colors: string[], globalColors: string[]) => { return [...safelist] } + +type SafelistFn = Exclude< + NonNullable['extract']>, + Record +> +export const customSafelistExtractor = ( + prefix: string, + content: string, + colors: string[], + safelistColors: string[], +): ReturnType => { + const classes: string[] = [] + + const regex + = /<([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z][A-Za-z0-9]*)*)\s+(?![^>]*:color\b)[^>]*\bcolor=["']([^"']+)["'][^>]*>/g + + const matches = content.matchAll(regex) + + const components = Object.keys(safelistForComponent).map( + component => + `${prefix}${component.charAt(0).toUpperCase() + component.slice(1)}`, + ) + + for (const match of matches) { + const [, component, color] = match + + const camelComponent = upperFirst(camelCase(component)) + + if (!colors.includes(color) || safelistColors.includes(color)) { + continue + } + + let name = camelComponent as string + + if (!components.includes(name)) { + continue + } + + name = name.replace(prefix, '').toLowerCase() + + const matchClasses = safelistForComponent[name](color)?.flatMap((group) => { + return typeof group === 'string' + ? '' + : ['', ...(group.variants || [])].flatMap((variant) => { + const matches = group.pattern.source.match(/\(([^)]+)\)/g) + + return ( + matches + ?.map((match) => { + const colorOptions = match + .substring(1, match.length - 1) + .split('|') + return colorOptions.map((color) => { + const classesExtracted = group.pattern.source + .replace(match, color) + .replace('^', '') + .replace('$', '') + if (variant) { + return `${variant}:${classesExtracted}` + } + return classesExtracted + }) + }) + .flat() || [] + ) + }) + }) + + classes.push(...(matchClasses as string[])) + } + + return classes +} diff --git a/src/tailwind.ts b/src/tailwind.ts index ec3f5bc..f7e5854 100644 --- a/src/tailwind.ts +++ b/src/tailwind.ts @@ -31,10 +31,13 @@ export const installTailwind = ( filename: 'ray-tailwind.config.cjs', write: true, getContents: ({ nuxt }) => ` - const { generateSafelist } = require(${JSON.stringify( + const { defaultExtractor: createDefaultExtractor } = require('tailwindcss/lib/lib/defaultExtractor.js') + const { customSafelistExtractor, generateSafelist } = require(${JSON.stringify( resolve(runtimePath, 'utils', 'colors'), )}) + const defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } }) + module.exports = { content: { files: [ @@ -46,6 +49,23 @@ export const installTailwind = ( )} ], }, + transform: { + vue: (content) => { + return content.replaceAll(/(?:\\r\\n|\\r|\\n)/g, ' ') + } + }, + extract: { + vue: (content) => { + return [ + ...defaultExtractor(content), + ...customSafelistExtractor(${JSON.stringify( + moduleOptions.prefix, + )}, content, ${JSON.stringify( + nuxt.options.appConfig.rayui.colors, + )}, ${JSON.stringify(moduleOptions.safelistColors)}) + ] + } + } safelist: generateSafelist(${JSON.stringify( moduleOptions.safeColors || [], )}, ${JSON.stringify(nuxt.options.appConfig.rayui.colors)}),