import postcss from 'postcss'
import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map'
import { Stylesheet, type StylesheetId } from '../../stylesheet'
import { walk, WalkAction } from '../../utils/walk'
export async function split(stylesheets: Stylesheet[]) {
let stylesheetsById = new Map<StylesheetId, Stylesheet>()
let stylesheetsByFile = new Map<string, Stylesheet>()
for (let sheet of stylesheets) {
stylesheetsById.set(sheet.id, sheet)
if (sheet.file) {
stylesheetsByFile.set(sheet.file, sheet)
}
}
let requiresSplit = new Set<Stylesheet>()
for (let sheet of stylesheets) {
if (sheet.isTailwindRoot) continue
let containsUtility = false
let containsUnsafe = sheet.layers().size > 0
walk(sheet.root, (node) => {
if (node.type === 'atrule' && node.name === 'utility') {
containsUtility = true
}
else if (
(node.type === 'atrule' && node.name === 'import' && node.params.includes('layer(')) ||
(node.type === 'atrule' && node.name === 'layer') ||
node.type === 'comment'
) {
return WalkAction.Skip
}
else {
containsUnsafe = true
}
if (containsUtility && containsUnsafe) {
return WalkAction.Stop
}
return WalkAction.Skip
})
if (containsUtility && containsUnsafe) {
requiresSplit.add(sheet)
}
}
let utilitySheets = new Map<Stylesheet, Stylesheet>()
for (let sheet of stylesheets) {
if (!sheet.file) continue
if (sheet.parents.size === 0) continue
if (!requiresSplit.has(sheet)) {
if (!Array.from(sheet.descendants()).some((child) => requiresSplit.has(child))) {
continue
}
}
let utilities = postcss.root()
walk(sheet.root, (node) => {
if (node.type !== 'atrule') return
if (node.name !== 'utility') return
utilities.append(node)
return WalkAction.Skip
})
let newFileName = sheet.file.replace(/\.css$/, '.utilities.css')
let counter = 0
while (stylesheetsByFile.has(newFileName)) {
counter += 1
newFileName = sheet.file.replace(/\.css$/, `.utilities.${counter}.css`)
}
let utilitySheet = await Stylesheet.fromRoot(utilities, newFileName)
utilitySheet.extension = counter > 0 ? `.utilities.${counter}.css` : `.utilities.css`
utilitySheets.set(sheet, utilitySheet)
stylesheetsById.set(utilitySheet.id, utilitySheet)
}
for (let [normalSheet, utilitySheet] of utilitySheets) {
for (let parent of normalSheet.parents) {
let utilityParent = utilitySheets.get(parent.item)
if (!utilityParent) continue
utilitySheet.parents.add({
item: utilityParent,
meta: parent.meta,
})
}
for (let child of normalSheet.children) {
let utilityChild = utilitySheets.get(child.item)
if (!utilityChild) continue
utilitySheet.children.add({
item: utilityChild,
meta: child.meta,
})
}
}
for (let sheet of stylesheets) {
let utilitySheet = utilitySheets.get(sheet)
let utilityImports: Set<postcss.AtRule> = new Set()
for (let node of sheet.importRules) {
let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined
if (!sheetId) continue
let originalDestination = stylesheetsById.get(sheetId)
if (!originalDestination) continue
let utilityDestination = utilitySheets.get(originalDestination)
if (!utilityDestination) continue
let match = node.params.match(/(['"])(.*)\1/)
if (!match) return
let quote = match[1]
let id = match[2]
let newFile = id.replace(/\.css$/, utilityDestination.extension!)
let newImport = node.clone({
params: `${quote}${newFile}${quote}`,
raws: {
tailwind_injected_layer: node.raws.tailwind_injected_layer,
tailwind_original_params: `${quote}${id}${quote}`,
tailwind_destination_sheet_id: utilityDestination.id,
},
})
if (utilitySheet) {
utilityImports.add(newImport)
} else {
node.after(newImport)
}
}
if (utilitySheet && utilityImports.size > 0) {
utilitySheet.root.prepend(Array.from(utilityImports))
}
}
let importNodes = new DefaultMap<Stylesheet, Set<postcss.AtRule>>(() => new Set())
for (let sheet of stylesheetsById.values()) {
for (let node of sheet.importRules) {
let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined
if (!sheetId) continue
let destination = stylesheetsById.get(sheetId)
if (!destination) continue
importNodes.get(destination).add(node)
}
}
let list: Stylesheet[] = []
for (let sheet of stylesheets.slice()) {
for (let child of sheet.descendants()) {
list.push(child)
}
list.push(sheet)
}
for (let sheet of list) {
let utilitySheet = utilitySheets.get(sheet)
if (!utilitySheet) continue
if (!sheet.isEmpty) continue
sheet.root = utilitySheet.root
for (let node of importNodes.get(utilitySheet)) {
node.params = node.raws.tailwind_original_params as any
}
for (let node of importNodes.get(sheet)) {
node.remove()
}
utilitySheets.delete(sheet)
}
stylesheets.push(...utilitySheets.values())
}