Refactor
This commit is contained in:
parent
cae3cc7ba2
commit
0bb3e0863d
3 changed files with 126 additions and 116 deletions
|
|
@ -11,11 +11,10 @@ import { help } from './commands/help'
|
|||
import {
|
||||
analyze as analyzeStylesheets,
|
||||
migrate as migrateStylesheet,
|
||||
prepare as prepareStylesheet,
|
||||
split as splitStylesheets,
|
||||
type MigrateOptions,
|
||||
type Stylesheet,
|
||||
} from './migrate'
|
||||
import { Stylesheet } from './stylesheet'
|
||||
import { migrate as migrateTemplate } from './template/migrate'
|
||||
import { parseConfig } from './template/parseConfig'
|
||||
import { args, type Arg } from './utils/args'
|
||||
|
|
@ -120,19 +119,19 @@ async function run() {
|
|||
files = files.filter((file) => file.endsWith('.css'))
|
||||
|
||||
// Analyze the stylesheets
|
||||
let stylesheets: Stylesheet[] = files.map((file) => ({ file }))
|
||||
let loadResults = await Promise.allSettled(files.map((filepath) => Stylesheet.load(filepath)))
|
||||
|
||||
// Load and parse all stylesheets
|
||||
let prepareResults = await Promise.allSettled(
|
||||
stylesheets.map((sheet) => prepareStylesheet(sheet)),
|
||||
)
|
||||
|
||||
for (let result of prepareResults) {
|
||||
for (let result of loadResults) {
|
||||
if (result.status === 'rejected') {
|
||||
error(`${result.reason}`)
|
||||
}
|
||||
}
|
||||
|
||||
let stylesheets = loadResults
|
||||
.filter((result) => result.status === 'fulfilled')
|
||||
.map((result) => result.value)
|
||||
|
||||
// Analyze the stylesheets
|
||||
try {
|
||||
await analyzeStylesheets(stylesheets)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import postcss, { AtRule } from 'postcss'
|
||||
import postcss from 'postcss'
|
||||
import type { Config } from 'tailwindcss'
|
||||
import type { DesignSystem } from '../../tailwindcss/src/design-system'
|
||||
import { segment } from '../../tailwindcss/src/utils/segment'
|
||||
|
|
@ -8,6 +7,7 @@ import { migrateAtApply } from './codemods/migrate-at-apply'
|
|||
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
|
||||
import { migrateMissingLayers } from './codemods/migrate-missing-layers'
|
||||
import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives'
|
||||
import { Stylesheet } from './stylesheet'
|
||||
import { resolveCssId } from './utils/resolve'
|
||||
import { walk, WalkAction } from './utils/walk'
|
||||
|
||||
|
|
@ -17,36 +17,12 @@ export interface MigrateOptions {
|
|||
userConfig?: Config
|
||||
}
|
||||
|
||||
export interface Stylesheet {
|
||||
file?: string
|
||||
unlink?: boolean
|
||||
|
||||
rootFile?: string
|
||||
rootImport?: postcss.AtRule
|
||||
|
||||
content?: string | null
|
||||
root?: postcss.Root | null
|
||||
layers?: Set<string>
|
||||
|
||||
parents?: Set<Stylesheet>
|
||||
children?: Set<Stylesheet>
|
||||
importsFromParents?: Set<AtRule>
|
||||
importsInSelf?: Set<AtRule>
|
||||
hasUtilities?: boolean
|
||||
|
||||
readonly ancestors?: Set<Stylesheet>
|
||||
readonly descendants?: Set<Stylesheet>
|
||||
}
|
||||
|
||||
export async function migrateContents(
|
||||
stylesheet: Stylesheet | string,
|
||||
options: MigrateOptions = {},
|
||||
) {
|
||||
if (typeof stylesheet === 'string') {
|
||||
stylesheet = {
|
||||
content: stylesheet,
|
||||
root: postcss.parse(stylesheet),
|
||||
}
|
||||
stylesheet = await Stylesheet.fromString(stylesheet)
|
||||
}
|
||||
|
||||
return postcss()
|
||||
|
|
@ -54,7 +30,7 @@ export async function migrateContents(
|
|||
.use(migrateAtLayerUtilities(stylesheet))
|
||||
.use(migrateMissingLayers())
|
||||
.use(migrateTailwindDirectives(options))
|
||||
.process(stylesheet.root!, { from: stylesheet.file })
|
||||
.process(stylesheet.root, { from: stylesheet.file ?? undefined })
|
||||
}
|
||||
|
||||
export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) {
|
||||
|
|
@ -67,33 +43,11 @@ export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) {
|
|||
|
||||
export async function analyze(stylesheets: Stylesheet[]) {
|
||||
let stylesheetsByFile = new Map<string, Stylesheet>()
|
||||
for (let stylesheet of stylesheets) {
|
||||
if (!stylesheet.file) continue
|
||||
stylesheetsByFile.set(stylesheet.file, stylesheet)
|
||||
|
||||
stylesheet.layers ??= new Set()
|
||||
stylesheet.importsFromParents ??= new Set()
|
||||
stylesheet.importsInSelf ??= new Set()
|
||||
stylesheet.parents ??= new Set()
|
||||
stylesheet.children ??= new Set()
|
||||
|
||||
function* traverse(
|
||||
sheet: Stylesheet,
|
||||
list: (sheet: Stylesheet) => Iterable<Stylesheet>,
|
||||
): Iterable<Stylesheet> {
|
||||
for (let child of list(sheet)) {
|
||||
yield child
|
||||
yield* traverse(child, list)
|
||||
}
|
||||
for (let sheet of stylesheets) {
|
||||
if (sheet.file) {
|
||||
stylesheetsByFile.set(sheet.file, sheet)
|
||||
}
|
||||
|
||||
Object.defineProperty(stylesheet, 'ancestors', {
|
||||
get: () => new Set(traverse(stylesheet, (sheet) => sheet.parents ?? [])),
|
||||
})
|
||||
|
||||
Object.defineProperty(stylesheet, 'descendants', {
|
||||
get: () => new Set(traverse(stylesheet, (sheet) => sheet.children ?? [])),
|
||||
})
|
||||
}
|
||||
|
||||
// Step 1: Record which `@import` rules point to which stylesheets
|
||||
|
|
@ -136,22 +90,22 @@ export async function analyze(stylesheets: Stylesheet[]) {
|
|||
if (!parent) return
|
||||
|
||||
// Record the import node for this sheet so it can be modified later
|
||||
stylesheet.importsFromParents!.add(node)
|
||||
parent.importsInSelf!.add(node)
|
||||
stylesheet.importsFromParents.add(node)
|
||||
parent.importsInSelf.add(node)
|
||||
|
||||
// Connect all stylesheets together in a dependency graph
|
||||
// The way this works is it uses the knowledge that we have a list of
|
||||
// the `@import` nodes that cause a given stylesheet to be imported.
|
||||
// That import has a `source` pointing to parent stylesheet's file path
|
||||
// which can be used to look it up
|
||||
stylesheet.parents!.add(parent)
|
||||
parent.children!.add(stylesheet)
|
||||
stylesheet.parents.add(parent)
|
||||
parent.children.add(stylesheet)
|
||||
|
||||
for (let part of segment(node.params, ' ')) {
|
||||
if (!part.startsWith('layer(')) continue
|
||||
if (!part.endsWith(')')) continue
|
||||
|
||||
stylesheet.layers!.add(part.slice(6, -1).trim())
|
||||
stylesheet.layers.add(part.slice(6, -1).trim())
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -160,43 +114,28 @@ export async function analyze(stylesheets: Stylesheet[]) {
|
|||
|
||||
for (let sheet of stylesheets) {
|
||||
if (!sheet.file) continue
|
||||
if (!sheet.root) continue
|
||||
|
||||
await processor.process(sheet.root, { from: sheet.file })
|
||||
}
|
||||
|
||||
// Step 2: Analyze the AST so each stylesheet can know what layers it is inside
|
||||
for (let sheet of stylesheets) {
|
||||
for (let ancestor of sheet.ancestors ?? []) {
|
||||
for (let layer of ancestor.layers ?? []) {
|
||||
sheet.layers!.add(layer)
|
||||
for (let ancestor of sheet.ancestors) {
|
||||
for (let layer of ancestor.layers) {
|
||||
sheet.layers.add(layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepare(stylesheet: Stylesheet) {
|
||||
if (stylesheet.file) {
|
||||
stylesheet.file = path.resolve(process.cwd(), stylesheet.file)
|
||||
stylesheet.content = await fs.readFile(stylesheet.file, 'utf-8')
|
||||
}
|
||||
|
||||
if (stylesheet.content) {
|
||||
stylesheet.root = postcss.parse(stylesheet.content, {
|
||||
from: stylesheet.file,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function split(stylesheets: Stylesheet[]) {
|
||||
let utilitySheets = new Map<Stylesheet, Stylesheet>()
|
||||
|
||||
for (let sheet of stylesheets) {
|
||||
if (!sheet.root) continue
|
||||
if (!sheet.file) continue
|
||||
|
||||
// We only care about stylesheets that were imported into a layer e.g. `layer(utilities)`
|
||||
let isLayered = sheet.layers?.has('utilities') || sheet.layers?.has('components')
|
||||
let isLayered = sheet.layers.has('utilities') || sheet.layers.has('components')
|
||||
if (!isLayered) continue
|
||||
|
||||
// We only care about stylesheets that contain an `@utility`
|
||||
|
|
@ -215,13 +154,12 @@ export async function split(stylesheets: Stylesheet[]) {
|
|||
}
|
||||
|
||||
for (let sheet of stylesheets) {
|
||||
if (!sheet.root) continue
|
||||
if (!sheet.importsFromParents?.size) continue
|
||||
if (!sheet.importsFromParents.size) continue
|
||||
|
||||
// Skip stylesheets that don't have utilities
|
||||
// and don't have any children that have utilities
|
||||
if (!sheet.hasUtilities) {
|
||||
if (!Array.from(sheet.descendants ?? []).some((child) => child.hasUtilities)) {
|
||||
if (!Array.from(sheet.descendants).some((child) => child.hasUtilities)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
@ -242,30 +180,22 @@ export async function split(stylesheets: Stylesheet[]) {
|
|||
return WalkAction.Skip
|
||||
})
|
||||
|
||||
let utilitySheet: Stylesheet = {
|
||||
file: sheet.file!.replace(/\.css$/, '.utilities.css'),
|
||||
root: utilities,
|
||||
importsFromParents: new Set(),
|
||||
importsInSelf: new Set(),
|
||||
parents: new Set(),
|
||||
children: new Set(),
|
||||
layers: new Set(),
|
||||
hasUtilities: true,
|
||||
}
|
||||
let utilitySheet = await Stylesheet.fromRoot(
|
||||
utilities,
|
||||
sheet.file!.replace(/\.css$/, '.utilities.css'),
|
||||
)
|
||||
|
||||
utilitySheets.set(sheet, utilitySheet)
|
||||
}
|
||||
|
||||
for (let sheet of stylesheets) {
|
||||
if (!sheet.root) continue
|
||||
|
||||
let utilitySheet = utilitySheets.get(sheet)
|
||||
let utilityImports: Set<postcss.AtRule> = new Set()
|
||||
|
||||
console.log(`---- ${sheet.file} ----`)
|
||||
console.log(Array.from(sheet.importsInSelf ?? []).map((node) => node.toString()))
|
||||
console.log(Array.from(sheet.importsInSelf).map((node) => node.toString()))
|
||||
|
||||
for (let node of sheet.importsInSelf ?? []) {
|
||||
for (let node of sheet.importsInSelf) {
|
||||
let id = node.params.match(/['"](.*)['"]/)?.[1]
|
||||
if (!id) return
|
||||
|
||||
|
|
@ -279,11 +209,11 @@ export async function split(stylesheets: Stylesheet[]) {
|
|||
|
||||
if (utilitySheet) {
|
||||
utilityImports.add(newImport)
|
||||
utilitySheet.importsInSelf!.add(newImport)
|
||||
utilitySheet.importsInSelf.add(newImport)
|
||||
|
||||
for (let child of sheet.children ?? []) {
|
||||
if (child.importsFromParents?.has(node)) {
|
||||
utilitySheets.get(child)!.importsFromParents!.add(newImport)
|
||||
for (let child of sheet.children) {
|
||||
if (child.importsFromParents.has(node)) {
|
||||
utilitySheets.get(child)!.importsFromParents.add(newImport)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -292,22 +222,19 @@ export async function split(stylesheets: Stylesheet[]) {
|
|||
}
|
||||
|
||||
if (utilitySheet && utilityImports.size > 0) {
|
||||
utilitySheet.root!.prepend(Array.from(utilityImports))
|
||||
utilitySheet.root.prepend(Array.from(utilityImports))
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the utility sheets track parents and import nodes and what not
|
||||
for (let [normalSheet, utilitySheet] of utilitySheets) {
|
||||
if (!utilitySheet.parents) continue
|
||||
if (!utilitySheet.children) continue
|
||||
|
||||
for (let parent of normalSheet.parents ?? []) {
|
||||
for (let parent of normalSheet.parents) {
|
||||
let utilityParent = utilitySheets.get(parent)
|
||||
if (!utilityParent) continue
|
||||
utilitySheet.parents.add(utilityParent)
|
||||
}
|
||||
|
||||
for (let child of normalSheet.children ?? []) {
|
||||
for (let child of normalSheet.children) {
|
||||
let utilityChild = utilitySheets.get(child)
|
||||
if (!utilityChild) continue
|
||||
utilitySheet.children.add(utilityChild)
|
||||
|
|
@ -322,8 +249,6 @@ export async function split(stylesheets: Stylesheet[]) {
|
|||
// can also remove those.
|
||||
for (let sheet of stylesheets) {
|
||||
continue
|
||||
if (!sheet.root) continue
|
||||
|
||||
let utilitySheet = utilitySheets.get(sheet)
|
||||
if (!utilitySheet) continue
|
||||
|
||||
|
|
@ -334,14 +259,14 @@ export async function split(stylesheets: Stylesheet[]) {
|
|||
sheet.root = utilitySheet.root
|
||||
|
||||
// 2. Point the imports back to the original file since we don't need the utility file anymore
|
||||
for (let node of utilitySheet.importsFromParents ?? []) {
|
||||
for (let node of utilitySheet.importsFromParents) {
|
||||
node.params = node.params.replace(/\.utilities\.css['"]/, '.css')
|
||||
}
|
||||
|
||||
// 3. Remove the original import from the non-utility sheet
|
||||
// TODO: This does not work because we're cloning trees during the migration
|
||||
// we *cannot* rely on reference semantics at all for any postcss nodes
|
||||
for (let node of sheet.importsFromParents ?? []) {
|
||||
for (let node of sheet.importsFromParents) {
|
||||
node.remove()
|
||||
}
|
||||
|
||||
|
|
|
|||
86
packages/@tailwindcss-upgrade/src/stylesheet.ts
Normal file
86
packages/@tailwindcss-upgrade/src/stylesheet.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import * as fs from 'node:fs/promises'
|
||||
import * as path from 'node:path'
|
||||
import * as postcss from 'postcss'
|
||||
|
||||
export class Stylesheet {
|
||||
/**
|
||||
* A unique identifier for this stylesheet
|
||||
*
|
||||
* Used to track the stylesheet in PostCSS nodes.
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* 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>()
|
||||
|
||||
importsFromParents = new Set<postcss.AtRule>()
|
||||
importsInSelf = new Set<postcss.AtRule>()
|
||||
hasUtilities = false
|
||||
|
||||
constructor(root: postcss.Root, file?: string) {
|
||||
this.id = crypto.randomUUID()
|
||||
this.root = root
|
||||
this.file = file ?? null
|
||||
}
|
||||
|
||||
get ancestors() {
|
||||
return walk<Stylesheet>(this, (sheet) => sheet.parents ?? [])
|
||||
}
|
||||
|
||||
get descendants() {
|
||||
return walk<Stylesheet>(this, (sheet) => sheet.children ?? [])
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue