Merge 44457a12ed into fa81d697fe
This commit is contained in:
commit
65172840a8
2 changed files with 328 additions and 1 deletions
|
|
@ -158,6 +158,7 @@ async function parseCss(
|
|||
let customVariants = new Map<string, (designSystem: DesignSystem) => void>()
|
||||
let customVariantDependencies = new Map<string, Set<string>>()
|
||||
let customUtilities: ((designSystem: DesignSystem) => void)[] = []
|
||||
let importedUtilityRootsByPrefix = new Map<string, Set<string>>()
|
||||
let firstThemeRule = null as StyleRule | null
|
||||
let utilitiesNode = null as AtRule | null
|
||||
let variantNodes: AtRule[] = []
|
||||
|
|
@ -233,6 +234,19 @@ async function parseCss(
|
|||
}
|
||||
|
||||
let utility = createCssUtility(node)
|
||||
let importPrefix = ctx.context.importPrefix as string | undefined
|
||||
if (importPrefix) {
|
||||
let prefixedParams = `${importPrefix}-${node.params}`
|
||||
utility = createCssUtility({ ...node, params: prefixedParams })
|
||||
|
||||
if (utility !== null) {
|
||||
let roots = importedUtilityRootsByPrefix.get(importPrefix)
|
||||
if (!roots) importedUtilityRootsByPrefix.set(importPrefix, (roots = new Set()))
|
||||
|
||||
let root = unescape(prefixedParams)
|
||||
roots.add(root.endsWith('-*') ? root.slice(0, -2) : root)
|
||||
}
|
||||
}
|
||||
if (utility === null) {
|
||||
if (!node.params.endsWith('-*')) {
|
||||
if (node.params.endsWith('*')) {
|
||||
|
|
@ -509,13 +523,36 @@ async function parseCss(
|
|||
else if (param.startsWith('prefix(')) {
|
||||
let prefix = param.slice(7, -1)
|
||||
|
||||
if (!IS_VALID_PREFIX.test(prefix)) {
|
||||
throw new Error(
|
||||
`The prefix "${prefix}" is invalid. Prefixes must be lowercase ASCII letters (a-z) only.`,
|
||||
)
|
||||
}
|
||||
|
||||
let containsTailwindUtilities = false
|
||||
let themeNodes: AtRule[] = []
|
||||
|
||||
walk(node.nodes, (child) => {
|
||||
if (child.kind !== 'at-rule') return
|
||||
if (child.name === '@tailwind' && child.params.startsWith('utilities')) {
|
||||
containsTailwindUtilities = true
|
||||
}
|
||||
|
||||
if (child.name === '@theme') {
|
||||
child.params += ` prefix(${prefix})`
|
||||
themeNodes.push(child)
|
||||
return WalkAction.Skip
|
||||
}
|
||||
})
|
||||
|
||||
if (containsTailwindUtilities) {
|
||||
theme.prefix = prefix
|
||||
|
||||
for (let themeNode of themeNodes) {
|
||||
themeNode.params += ` prefix(${prefix})`
|
||||
}
|
||||
}
|
||||
|
||||
node.nodes = [contextNode({ importPrefix: prefix }, node.nodes)]
|
||||
}
|
||||
|
||||
// Handle important
|
||||
|
|
@ -602,6 +639,49 @@ async function parseCss(
|
|||
|
||||
let designSystem = buildDesignSystem(theme, utilitiesNode?.src)
|
||||
|
||||
if (importedUtilityRootsByPrefix.size > 0) {
|
||||
let parseCandidate = designSystem.parseCandidate
|
||||
let importedUtilityRoots = new Set<string>()
|
||||
for (let roots of importedUtilityRootsByPrefix.values()) {
|
||||
for (let root of roots) importedUtilityRoots.add(root)
|
||||
}
|
||||
|
||||
function parseCandidateWithoutImportedRoots(candidate: string) {
|
||||
return parseCandidate(candidate).filter((parsedCandidate) => {
|
||||
return (
|
||||
parsedCandidate.kind === 'arbitrary' || !importedUtilityRoots.has(parsedCandidate.root)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
designSystem.parseCandidate = (candidate) => {
|
||||
let [prefix, ...parts] = segment(candidate, ':')
|
||||
let roots = importedUtilityRootsByPrefix.get(prefix)
|
||||
if (!roots) return parseCandidateWithoutImportedRoots(candidate)
|
||||
|
||||
let base = parts.pop()
|
||||
if (!base) return []
|
||||
|
||||
// Imported utilities use `prefix:name` publicly, but are registered as
|
||||
// `prefix-name` internally because `@utility` names cannot contain `:`.
|
||||
let internalCandidate = [...parts, `${prefix}-${base}`].join(':')
|
||||
if (designSystem.theme.prefix) {
|
||||
internalCandidate = `${designSystem.theme.prefix}:${internalCandidate}`
|
||||
}
|
||||
|
||||
let results = parseCandidate(internalCandidate)
|
||||
.filter((parsedCandidate) => {
|
||||
return parsedCandidate.kind !== 'arbitrary' && roots.has(parsedCandidate.root)
|
||||
})
|
||||
.map((parsedCandidate) => ({
|
||||
...parsedCandidate,
|
||||
raw: candidate,
|
||||
}))
|
||||
|
||||
return results.length > 0 ? results : parseCandidateWithoutImportedRoots(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
if (important) {
|
||||
designSystem.important = important
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,6 +311,253 @@ test('a prefix can be configured via @import prefix(…)', async () => {
|
|||
expect(await run(['underline', 'hover:line-through', 'custom'], input, options)).toEqual('')
|
||||
})
|
||||
|
||||
test('custom utilities from imports use the Tailwind import prefix', async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss' prefix(tw);
|
||||
@import './components.css';
|
||||
`
|
||||
|
||||
async function loadStylesheet(id: string, base: string) {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
content:
|
||||
id === 'tailwindcss'
|
||||
? css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
: css`
|
||||
@utility card {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
expect(await run(['underline', 'card'], input, { loadStylesheet })).toEqual('')
|
||||
|
||||
expect(await run(['tw:underline', 'tw:card'], input, { loadStylesheet })).toMatchInlineSnapshot(`
|
||||
"
|
||||
.tw\\:card {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.tw\\:underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom utilities from prefixed imports use their import prefix', async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss' prefix(tw);
|
||||
@import './components.css' prefix(ui);
|
||||
|
||||
@utility ui-underline {
|
||||
color: red;
|
||||
}
|
||||
`
|
||||
|
||||
async function loadStylesheet(id: string, base: string) {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
content:
|
||||
id === 'tailwindcss'
|
||||
? css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
: css`
|
||||
@utility card {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
expect(
|
||||
await run(['underline', 'card', 'tw:card', 'ui:underline', 'tw:ui-card'], input, {
|
||||
loadStylesheet,
|
||||
}),
|
||||
).toEqual('')
|
||||
|
||||
expect(await run(['tw:underline', 'ui:card', 'tw:ui-underline'], input, { loadStylesheet }))
|
||||
.toMatchInlineSnapshot(`
|
||||
"
|
||||
.tw\\:ui-underline, .ui\\:card {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.tw\\:underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('a prefixed custom import does not prefix Tailwind utilities', async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
@import './components.css' prefix(ui);
|
||||
`
|
||||
|
||||
async function loadStylesheet(id: string, base: string) {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
content:
|
||||
id === 'tailwindcss'
|
||||
? css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
: css`
|
||||
@utility card {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
expect(await run(['card', 'ui-card', 'ui:underline'], input, { loadStylesheet })).toEqual('')
|
||||
|
||||
expect(await run(['underline', 'ui:card'], input, { loadStylesheet })).toMatchInlineSnapshot(`
|
||||
"
|
||||
.ui\\:card {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom utilities from prefixed imports support variants', async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
@import './components.css' prefix(ui);
|
||||
`
|
||||
|
||||
async function loadStylesheet(id: string, base: string) {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
content:
|
||||
id === 'tailwindcss'
|
||||
? css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
: css`
|
||||
@utility card {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
expect(await run(['hover:ui:card'], input, { loadStylesheet })).toEqual('')
|
||||
|
||||
expect(await run(['ui:hover:card'], input, { loadStylesheet })).toMatchInlineSnapshot(`
|
||||
"
|
||||
@media (hover: hover) {
|
||||
.ui\\:hover\\:card:hover {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('a prefixed custom import with a theme does not prefix Tailwind utilities', async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
@import './components.css' prefix(ui);
|
||||
`
|
||||
|
||||
async function loadStylesheet(id: string, base: string) {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
content:
|
||||
id === 'tailwindcss'
|
||||
? css`
|
||||
@tailwind utilities;
|
||||
`
|
||||
: css`
|
||||
@theme {
|
||||
--color-red: red;
|
||||
}
|
||||
|
||||
@utility card {
|
||||
color: var(--color-red);
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
expect(await run(['ui:underline'], input, { loadStylesheet })).toEqual('')
|
||||
|
||||
expect(await run(['underline', 'ui:card'], input, { loadStylesheet })).toMatchInlineSnapshot(`
|
||||
"
|
||||
.ui\\:card {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
:root, :host {
|
||||
--color-red: red;
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('custom import prefixes do not shadow variants with the same name', async () => {
|
||||
let input = css`
|
||||
@import 'tailwindcss';
|
||||
@import './components.css' prefix(sm);
|
||||
`
|
||||
|
||||
async function loadStylesheet(id: string, base: string) {
|
||||
return {
|
||||
path: '',
|
||||
base,
|
||||
content:
|
||||
id === 'tailwindcss'
|
||||
? css`
|
||||
@theme {
|
||||
--breakpoint-sm: 40rem;
|
||||
}
|
||||
|
||||
@tailwind utilities;
|
||||
`
|
||||
: css`
|
||||
@utility card {
|
||||
color: red;
|
||||
}
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
expect(await run(['sm:card', 'sm:flex'], input, { loadStylesheet })).toMatchInlineSnapshot(`
|
||||
"
|
||||
.sm\\:card {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
.sm\\:flex {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('a prefix must be letters only', async () => {
|
||||
let input = css`
|
||||
@theme reference prefix(__);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue