tailwindcss/packages/@tailwindcss-upgrade/src/stylesheet.ts
Jordan Pittman f387d88e46 wip
2024-10-04 16:08:58 -04:00

109 lines
2.4 KiB
TypeScript

import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as util from 'node:util'
import * as postcss from 'postcss'
export type StylesheetId = string
export class Stylesheet {
/**
* A unique identifier for this stylesheet
*
* Used to track the stylesheet in PostCSS nodes.
*/
id: StylesheetId
/**
* The PostCSS AST that represents this stylesheet.
*/
root: postcss.Root
/**
* The path to the file that this stylesheet was loaded from.
*
* If this stylesheet was not loaded from a file this will be `null`.
*/
file: string | null = null
/**
* Stylesheets that import this stylesheet.
*/
parents = new Set<Stylesheet>()
/**
* Stylesheets that are imported by stylesheet.
*/
children = new Set<Stylesheet>()
/**
* The layers this stylesheet is in, even transitive layers from parents.
*/
layers = new Set<string>()
static async load(filepath: string) {
filepath = path.resolve(process.cwd(), filepath)
let css = await fs.readFile(filepath, 'utf-8')
let root = postcss.parse(css, { from: filepath })
return new Stylesheet(root, filepath)
}
static async fromString(css: string) {
let root = postcss.parse(css)
return new Stylesheet(root)
}
static async fromRoot(root: postcss.Root, file?: string) {
return new Stylesheet(root, file)
}
constructor(root: postcss.Root, file?: string) {
this.id = Math.random().toString(36).slice(2)
this.root = root
this.file = file ?? null
}
get importRules() {
let imports = new Set<postcss.AtRule>()
this.root.walkAtRules('import', (rule) => {
imports.add(rule)
})
return imports
}
get isEmpty() {
return this.root.toString().trim() === ''
}
get ancestors() {
return walk<Stylesheet>(this, (sheet) => sheet.parents ?? [])
}
get descendants() {
return walk<Stylesheet>(this, (sheet) => sheet.children ?? [])
}
[util.inspect.custom]() {
return {
...this,
root: this.root.toString(),
layers: Array.from(this.layers),
parents: Array.from(this.parents, (s) => s.id),
children: Array.from(this.children, (s) => s.id),
}
}
}
function* walk<T>(value: T, getList: (value: T) => Iterable<T>, seen = new Set<T>()): Iterable<T> {
for (let item of getList(value)) {
if (seen.has(item)) continue
seen.add(item)
yield item
yield* walk(item, getList, seen)
}
}