blob: 9150b1519b5c54cb22d264bad153b24e8878403e [file] [edit]
---
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
interface Props {
utility: string | string[] // The utility key(s) from the metadata (e.g., "font-size" or ["font-size", "text-size"])
className?: string
}
const { utility, className = 'table reference-table' } = Astro.props
// Normalize to array
const utilities = Array.isArray(utility) ? utility : [utility]
// Parse CSS variables from _root.scss at build time
function parseCSSVariables(): Record<string, string> {
try {
const projectRoot = process.cwd()
const rootScssPath = join(projectRoot, 'scss/_root.scss')
const scssContent = readFileSync(rootScssPath, 'utf-8')
const cssVarValues: Record<string, string> = {}
const varRegex = /--#\{\$prefix\}([a-z0-9-]+):\s*([^;]+);/gi
let match
while ((match = varRegex.exec(scssContent)) !== null) {
const varName = `--bs-${match[1]}`
let value = match[2].trim()
value = value.replace(/#\{[^}]+\}/g, '').trim()
value = value.replace(/\/\/.*$/gm, '').trim()
if (value) {
cssVarValues[varName] = value
}
}
return cssVarValues
} catch (error) {
console.warn('Could not parse CSS variables from _root.scss:', error)
return {}
}
}
// Load utilities metadata
function loadUtilitiesMetadata(): any {
try {
const projectRoot = process.cwd()
const metadataPath = join(projectRoot, 'dist/css/bootstrap-utilities.metadata.json')
const metadataContent = readFileSync(metadataPath, 'utf-8')
return JSON.parse(metadataContent)
} catch (error) {
console.warn('Could not load utilities metadata:', error)
return { utilities: {} }
}
}
// Parse compiled CSS to extract styles for given class selectors
function parseCompiledCSS(classNames: string[]): Record<string, string[]> {
try {
const projectRoot = process.cwd()
const bootstrapCssPath = join(projectRoot, 'dist/css/bootstrap.css')
const cssContent = readFileSync(bootstrapCssPath, 'utf-8')
const classStyles: Record<string, string[]> = {}
classNames.forEach((className) => {
// Match class selectors, including :where() wrapped child-selector utilities
const escapedClass = className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const selectorRegex = new RegExp(
`(?:^|\\n)\\s*(?::where\\()?\\s*\\.${escapedClass}(?:\\s[^{]*)?\\)?\\s*\\{([^}]+)\\}`,
'gm'
)
let match
let foundDeclarations: string[] = []
while ((match = selectorRegex.exec(cssContent)) !== null) {
const declarations = match[1]
.split(';')
.map((decl) => decl.trim())
.filter((decl) => decl.length > 0)
.map((decl) => `${decl};`)
if (declarations.length > 0) {
foundDeclarations = declarations
break
}
}
classStyles[className] = foundDeclarations
})
return classStyles
} catch (error) {
console.warn('Could not parse compiled CSS:', error)
return {}
}
}
const cssVarValues = parseCSSVariables()
const metadata = loadUtilitiesMetadata()
// Collect classes from all specified utilities
let allClasses: string[] = []
utilities.forEach((util) => {
const utilityMeta = metadata.utilities[util]
if (!utilityMeta) {
console.warn(
`Utility "${util}" not found in metadata. Available utilities: ${Object.keys(metadata.utilities).join(', ')}`
)
return
}
const classes = utilityMeta.classes || []
allClasses = allClasses.concat(classes)
})
if (allClasses.length === 0) {
throw new Error(`No classes found for utilities: ${utilities.join(', ')}`)
}
const classStyles = parseCompiledCSS(allClasses)
// Function to add CSS variable value comments
function addVarComments(cssValue: string): string {
const comments: string[] = []
cssValue.replace(/var\((--[a-z0-9-]+)\)/gi, (match, varName) => {
const resolvedValue = cssVarValues[varName]
if (resolvedValue) {
comments.push(`<span class="fg-3">/* ${resolvedValue} */</span>`)
}
return match
})
if (comments.length > 0) {
const hasSemicolon = cssValue.trimEnd().endsWith(';')
return `${cssValue}${hasSemicolon ? '' : ';'} ${comments.join(' ')}`
}
return cssValue
}
// Build table data
const tableData = allClasses.map((cls) => {
const styles = classStyles[cls] || []
const formattedStyles = styles.map((style) => addVarComments(style)).join('<br/>')
return {
class: `.${cls}`,
styles: formattedStyles || '<em>Not found</em>'
}
})
---
<div class="table-responsive bd-reference-table">
<table class={className}>
<thead>
<tr>
<th scope="col">Class</th>
<th scope="col">Styles</th>
</tr>
</thead>
<tbody>
{
tableData.map((row) => (
<tr>
<td>{row.class}</td>
<td>
<Fragment set:html={row.styles} />
</td>
</tr>
))
}
</tbody>
</table>
</div>