Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 48 additions & 26 deletions packages/tailwindcss/src/design-system.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Polyfills } from '.'
import { optimizeAst, toCss } from './ast'
import { optimizeAst, toCss, type AstNode } from './ast'
import {
parseCandidate,
parseVariant,
Expand All @@ -19,11 +19,13 @@ import {
type VariantEntry,
} from './intellisense'
import { getClassOrder } from './sort'
import type { SourceLocation } from './source-maps/source'
import { Theme, ThemeOptions, type ThemeKey } from './theme'
import { Utilities, createUtilities, withAlpha } from './utilities'
import { DefaultMap } from './utils/default-map'
import { extractUsedVariables } from './utils/variables'
import { Variants, createVariants, substituteAtVariant } from './variants'
import { WalkAction, walk } from './walk'

export const enum CompileAstFlags {
None = 0,
Expand Down Expand Up @@ -59,12 +61,16 @@ export type DesignSystem = {

// Used by IntelliSense
candidatesToCss(classes: string[]): (string | null)[]
candidatesToAst(classes: string[]): AstNode[][]

// General purpose storage
storage: Record<symbol, unknown>
}

export function buildDesignSystem(theme: Theme): DesignSystem {
export function buildDesignSystem(
theme: Theme,
utilitiesSrc?: SourceLocation | undefined,
): DesignSystem {
let utilities = createUtilities(theme)
let variants = createVariants(theme)

Expand Down Expand Up @@ -109,6 +115,44 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
}
})

function candidatesToAst(classes: string[]): AstNode[][] {
let result: AstNode[][] = []

for (let className of classes) {
let wasValid = true

let { astNodes } = compileCandidates([className], designSystem, {
onInvalidCandidate() {
wasValid = false
},
})

if (utilitiesSrc) {
walk(astNodes, (node) => {
// We do this conditionally to preserve source locations from both
// `@utility` and `@custom-variant`. Even though generated nodes are
// cached this should be fine because `utilitiesNode.src` should not
// change without a full rebuild which destroys the cache.
node.src ??= utilitiesSrc
return WalkAction.Continue
})
}

// Disable all polyfills to not unnecessarily pollute IntelliSense output
astNodes = optimizeAst(astNodes, designSystem, Polyfills.None)

result.push(wasValid ? astNodes : [])
}

return result
}

function candidatesToCss(classes: string[]): (string | null)[] {
return candidatesToAst(classes).map((nodes) => {
return nodes.length > 0 ? toCss(nodes) : null
})
}

let designSystem: DesignSystem = {
theme,
utilities,
Expand All @@ -117,30 +161,8 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
invalidCandidates: new Set(),
important: false,

candidatesToCss(classes: string[]) {
let result: (string | null)[] = []

for (let className of classes) {
let wasInvalid = false

let { astNodes } = compileCandidates([className], this, {
onInvalidCandidate() {
wasInvalid = true
},
})

// Disable all polyfills to not unnecessarily pollute IntelliSense output
astNodes = optimizeAst(astNodes, designSystem, Polyfills.None)

if (astNodes.length === 0 || wasInvalid) {
result.push(null)
} else {
result.push(toCss(astNodes))
}
}

return result
},
candidatesToCss,
candidatesToAst,

getClassOrder(classes) {
return getClassOrder(this, classes)
Expand Down
4 changes: 2 additions & 2 deletions packages/tailwindcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ async function parseCss(
}
})

let designSystem = buildDesignSystem(theme)
let designSystem = buildDesignSystem(theme, utilitiesNode?.src)

if (important) {
designSystem.important = important
Expand Down Expand Up @@ -855,7 +855,7 @@ export async function compile(
}

export async function __unstable__loadDesignSystem(css: string, opts: CompileOptions = {}) {
let result = await parseCss(CSS.parse(css), opts)
let result = await parseCss(CSS.parse(css, { from: opts.from }), opts)
return result.designSystem
}

Expand Down
25 changes: 23 additions & 2 deletions packages/tailwindcss/src/intellisense.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect, test } from 'vitest'
import { __unstable__loadDesignSystem } from '.'
import { decl, rule } from './ast'
import plugin from './plugin'
import { ThemeOptions } from './theme'

Expand Down Expand Up @@ -165,6 +166,26 @@ test('Can produce CSS per candidate using `candidatesToCss`', async () => {
`)
})

test('Can produce AST per candidate using `candidatesToAst`', async () => {
let design = await loadDesignSystem()
design.invalidCandidates = new Set(['bg-[#fff]'])

expect(
design.candidatesToAst(['underline', 'i-dont-exist', 'bg-[#fff]', 'bg-[#000]', 'text-xs']),
).toEqual([
[rule('.underline', [decl('text-decoration-line', 'underline')])],
[],
[],
[rule('.bg-\\[\\#000\\]', [decl('background-color', '#000')])],
[
rule('.text-xs', [
decl('font-size', 'var(--text-xs)'),
decl('line-height', 'var(--tw-leading, var(--text-xs--line-height))'),
]),
],
])
})

test('Utilities do not show wrapping selector in intellisense', async () => {
let input = css`
@import 'tailwindcss/utilities';
Expand Down Expand Up @@ -238,7 +259,7 @@ test('Utilities, when marked as important, show as important in intellisense', a
test('Static utilities from plugins are listed in hovers and completions', async () => {
let input = css`
@import 'tailwindcss/utilities';
@plugin "./plugin.js"l;
@plugin "./plugin.js";
`

let design = await __unstable__loadDesignSystem(input, {
Expand Down Expand Up @@ -275,7 +296,7 @@ test('Static utilities from plugins are listed in hovers and completions', async
test('Functional utilities from plugins are listed in hovers and completions', async () => {
let input = css`
@import 'tailwindcss/utilities';
@plugin "./plugin.js"l;
@plugin "./plugin.js";
`

let design = await __unstable__loadDesignSystem(input, {
Expand Down