UNPKG

ksk-core

Version:

Core design system components and styles for Kickstart projects

209 lines (167 loc) 7.38 kB
/** * Astro integration to add "ast-" prefix to HTML class attributes * This works with the Vite plugin to ensure both HTML and CSS are prefixed */ export function astPrefixIntegration(options = {}) { const { prefix = 'ast-', ignoreClasses = [ 'swiper',], ignorePatterns = [ /^astro-/, // Astro's own classes /^swiper/, // Swiper classes // /^layout-/, // Layout utility classes // /^site-/, // Site-wide classes // /^demo-/, // Demo page classes // /^owl-/, // Spacing utility classes // /^col-/, // Grid system classes // /^md:/, // Responsive prefixes // /^lg:/, // Responsive prefixes // /^bg-/, // Background utility classes // /^text-/, // Text utility classes // /^font-/, // Font utility classes // /^leading-/, // Line height utility classes // /^tracking-/, // Letter spacing utility classes ] } = options // Function to check if a class should be prefixed function shouldPrefixClass(className) { // Skip if already prefixed if (className.startsWith(prefix)) return false // Skip if in ignore list if (ignoreClasses.includes(className)) return false // Skip if matches ignore patterns if (ignorePatterns.some(pattern => pattern.test(className))) return false return true } // Function to prefix a class name function prefixClass(className) { return shouldPrefixClass(className) ? `${prefix}${className}` : className } // Function to process HTML content function processHTML(html) { return html.replace(/class="([^"]*)"/g, (match, classValue) => { const classes = classValue.split(/\s+/).filter(Boolean) const prefixedClasses = classes.map(prefixClass) const result = `class="${prefixedClasses.join(' ')}"` // Log if we made changes if (result !== match) { } return result }) } // Function to process CSS content function processCSS(css) { return css.replace(/\.([a-zA-Z][a-zA-Z0-9_-]*)/g, (match, className) => { const prefixedClassName = prefixClass(className) return `.${prefixedClassName}` }) } // Function to process JavaScript content function processJS(js) { console.log('[ast-prefix-integration] Processing JavaScript content...') let result = js // Process querySelector and querySelectorAll calls with class selectors // This handles complex selectors like: querySelector('.class1, .class2 h1, .class3') result = result.replace(/(querySelector(?:All)?)\(['"`]([^'"`]*)/g, (match, method, selectorString) => { // Process each class selector in the string const processedSelector = selectorString.replace(/\.([a-zA-Z][a-zA-Z0-9_-]*(?:__[a-zA-Z0-9_-]+)*(?:--[a-zA-Z0-9_-]+)*)/g, (classMatch, className) => { const prefixedClassName = prefixClass(className) return `.${prefixedClassName}` }) const newMatch = `${method}('${processedSelector}` if (newMatch !== match) { console.log(`[ast-prefix-integration] JS querySelector: ${selectorString} -> ${processedSelector}`) } return newMatch }) // Handle template literals with querySelector result = result.replace(/(querySelector(?:All)?)\(`([^`]*)/g, (match, method, selectorString) => { const processedSelector = selectorString.replace(/\.([a-zA-Z][a-zA-Z0-9_-]*(?:__[a-zA-Z0-9_-]+)*(?:--[a-zA-Z0-9_-]+)*)/g, (classMatch, className) => { const prefixedClassName = prefixClass(className) return `.${prefixedClassName}` }) const newMatch = `${method}(\`${processedSelector}` if (newMatch !== match) { console.log(`[ast-prefix-integration] JS querySelector template: ${selectorString} -> ${processedSelector}`) } return newMatch }) // Process classList operations (add, remove, toggle, contains) result = result.replace(/(classList\.(add|remove|toggle|contains))\(['"`]([a-zA-Z][a-zA-Z0-9_-]*(?:__[a-zA-Z0-9_-]+)*(?:--[a-zA-Z0-9_-]+)*)/g, (match, method, operation, className) => { const prefixedClassName = prefixClass(className) const newMatch = `${method}('${prefixedClassName}` if (newMatch !== match) { console.log(`[ast-prefix-integration] JS classList: ${match} -> ${newMatch}`) } return newMatch }) // Process className assignments result = result.replace(/(\.className\s*=\s*['"`])([^'"`]*)/g, (match, prefix, classNames) => { const classes = classNames.split(/\s+/).filter(Boolean) const prefixedClasses = classes.map(prefixClass) const newMatch = `${prefix}${prefixedClasses.join(' ')}` if (newMatch !== match) { console.log(`[ast-prefix-integration] JS className: ${match} -> ${newMatch}`) } return newMatch }) return result } return { name: 'ast-prefix-integration', hooks: { 'astro:build:done': async ({ dir, pages }) => { const fs = await import('fs') const path = await import('path') // Process all HTML files for (const page of pages) { // Handle root page (empty pathname) and other pages const pagePath = page.pathname === '' || page.pathname === '/' ? 'index' : page.pathname.replace(/\/$/, '') const filePath = path.resolve(dir.pathname, pagePath + '.html') try { if (fs.existsSync(filePath)) { const content = fs.readFileSync(filePath, 'utf8') const processedContent = processHTML(content) if (content !== processedContent) { fs.writeFileSync(filePath, processedContent, 'utf8') } else { } } else { } } catch (error) { console.error(`[ast-prefix-integration] Error processing ${page.pathname}:`, error.message) } } // Process all CSS files try { const findCSSFiles = (dir) => { const cssFiles = [] const items = fs.readdirSync(dir, { withFileTypes: true }) for (const item of items) { const fullPath = path.join(dir, item.name) if (item.isDirectory()) { cssFiles.push(...findCSSFiles(fullPath)) } else if (item.isFile() && item.name.endsWith('.css')) { cssFiles.push(fullPath) } } return cssFiles } const cssFiles = findCSSFiles(dir.pathname) for (const cssPath of cssFiles) { const relativePath = path.relative(dir.pathname, cssPath) const cssContent = fs.readFileSync(cssPath, 'utf8') const processedCSS = processCSS(cssContent) if (cssContent !== processedCSS) { fs.writeFileSync(cssPath, processedCSS, 'utf8') } else { } } } catch (error) { console.error('[ast-prefix-integration] Error processing CSS files:', error.message) } console.log('[ast-prefix-integration] HTML and CSS processing complete') } } } }