mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Expose candidatesToAst to the language server (#19405)
This will be used to improve performance and potentially enable future features that require generated CSS source locations. Note: This is still 100% internal API. You can only access this via `__unstable__loadDesignSystem` for a reason. We may chance the structure of the arguments and/or return values as needed.
This commit is contained in:
parent
478e959097
commit
820d90797c
@ -1,5 +1,5 @@
|
||||
import { Polyfills } from '.'
|
||||
import { optimizeAst, toCss } from './ast'
|
||||
import { optimizeAst, toCss, type AstNode } from './ast'
|
||||
import {
|
||||
parseCandidate,
|
||||
parseVariant,
|
||||
@ -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,
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
@ -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)
|
||||
|
||||
@ -595,7 +595,7 @@ async function parseCss(
|
||||
}
|
||||
})
|
||||
|
||||
let designSystem = buildDesignSystem(theme)
|
||||
let designSystem = buildDesignSystem(theme, utilitiesNode?.src)
|
||||
|
||||
if (important) {
|
||||
designSystem.important = important
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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';
|
||||
@ -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, {
|
||||
@ -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, {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user