mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Error when @layer used without matching @tailwind directive (#4335)
Also refactor to only detect `@tailwind` directives once per build to improve performance.
This commit is contained in:
parent
87a4516871
commit
30dc2990c3
@ -30,9 +30,9 @@ export default function (configOrPath = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
rewriteTailwindImports(root)
|
||||
let tailwindDirectives = rewriteTailwindImports(root)
|
||||
|
||||
let context = setupContext(configOrPath)(result, root)
|
||||
let context = setupContext(configOrPath, tailwindDirectives)(result, root)
|
||||
|
||||
if (!env.TAILWIND_DISABLE_TOUCH) {
|
||||
if (context.configPath !== null) {
|
||||
@ -41,8 +41,8 @@ export default function (configOrPath = {}) {
|
||||
}
|
||||
|
||||
return postcss([
|
||||
removeLayerAtRules(context),
|
||||
expandTailwindAtRules(context, registerDependency),
|
||||
removeLayerAtRules(context, tailwindDirectives),
|
||||
expandTailwindAtRules(context, registerDependency, tailwindDirectives),
|
||||
expandApplyAtRules(context),
|
||||
evaluateTailwindFunctions(context.tailwindConfig),
|
||||
substituteScreenAtRules(context.tailwindConfig),
|
||||
|
||||
@ -111,9 +111,12 @@ function buildStylesheet(rules, context) {
|
||||
return returnValue
|
||||
}
|
||||
|
||||
export default function expandTailwindAtRules(context, registerDependency) {
|
||||
export default function expandTailwindAtRules(context, registerDependency, tailwindDirectives) {
|
||||
return (root) => {
|
||||
let foundTailwind = false
|
||||
if (tailwindDirectives.size === 0) {
|
||||
return root
|
||||
}
|
||||
|
||||
let layerNodes = {
|
||||
base: null,
|
||||
components: null,
|
||||
@ -126,8 +129,6 @@ export default function expandTailwindAtRules(context, registerDependency) {
|
||||
// file as a dependency since the output of this CSS does not depend on
|
||||
// the source of any templates. Think Vue <style> blocks for example.
|
||||
root.walkAtRules('tailwind', (rule) => {
|
||||
foundTailwind = true
|
||||
|
||||
if (rule.params === 'base') {
|
||||
layerNodes.base = rule
|
||||
}
|
||||
@ -145,10 +146,6 @@ export default function expandTailwindAtRules(context, registerDependency) {
|
||||
}
|
||||
})
|
||||
|
||||
if (!foundTailwind) {
|
||||
return root
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
if (sharedState.env.TAILWIND_DISABLE_TOUCH) {
|
||||
|
||||
@ -1,7 +1,22 @@
|
||||
export default function removeLayerAtRules() {
|
||||
export default function removeLayerAtRules(_context, tailwindDirectives) {
|
||||
return (root) => {
|
||||
root.walkAtRules((rule) => {
|
||||
if (['layer', 'responsive', 'variants'].includes(rule.name)) {
|
||||
if (rule.name === 'layer' && ['base', 'components', 'utilities'].includes(rule.params)) {
|
||||
if (!tailwindDirectives.has(rule.params)) {
|
||||
throw rule.error(
|
||||
`\`@layer ${rule.params}\` is used but no matching \`@tailwind ${rule.params}\` directive is present.`
|
||||
)
|
||||
}
|
||||
rule.remove()
|
||||
} else if (rule.name === 'responsive') {
|
||||
if (!tailwindDirectives.has('utilities')) {
|
||||
throw rule.error('`@responsive` is used but `@tailwind utilities` is missing.')
|
||||
}
|
||||
rule.remove()
|
||||
} else if (rule.name === 'variants') {
|
||||
if (!tailwindDirectives.has('utilities')) {
|
||||
throw rule.error('`@variants` is used but `@tailwind utilities` is missing.')
|
||||
}
|
||||
rule.remove()
|
||||
}
|
||||
})
|
||||
|
||||
@ -23,4 +23,12 @@ export default function rewriteTailwindImports(root) {
|
||||
atRule.params = 'screens'
|
||||
}
|
||||
})
|
||||
|
||||
let tailwindDirectives = new Set()
|
||||
|
||||
root.walkAtRules('tailwind', (rule) => {
|
||||
tailwindDirectives.add(rule.params)
|
||||
})
|
||||
|
||||
return tailwindDirectives
|
||||
}
|
||||
|
||||
@ -314,6 +314,7 @@ function rebootWatcher(context) {
|
||||
touch(context.configPath)
|
||||
} else {
|
||||
context.changedFiles.add(path.resolve('.', file))
|
||||
console.log(context.touchFile)
|
||||
touch(context.touchFile)
|
||||
}
|
||||
})
|
||||
@ -686,14 +687,8 @@ function cleanupContext(context) {
|
||||
// Retrieve an existing context from cache if possible (since contexts are unique per
|
||||
// source path), or set up a new one (including setting up watchers and registering
|
||||
// plugins) then return it
|
||||
export default function setupContext(configOrPath) {
|
||||
export default function setupContext(configOrPath, tailwindDirectives) {
|
||||
return (result, root) => {
|
||||
let foundTailwind = false
|
||||
|
||||
root.walkAtRules('tailwind', () => {
|
||||
foundTailwind = true
|
||||
})
|
||||
|
||||
let sourcePath = result.opts.from
|
||||
let [
|
||||
tailwindConfig,
|
||||
@ -712,7 +707,7 @@ export default function setupContext(configOrPath) {
|
||||
// We may want to think about `@layer` being part of this trigger too, but it's tough
|
||||
// because it's impossible for a layer in one file to end up in the actual @tailwind rule
|
||||
// in another file since independent sources are effectively isolated.
|
||||
if (foundTailwind) {
|
||||
if (tailwindDirectives.size > 0) {
|
||||
contextDependencies.add(sourcePath)
|
||||
for (let message of result.messages) {
|
||||
if (message.type === 'dependency') {
|
||||
|
||||
5
tests/jit/layer-without-tailwind.test.css
Normal file
5
tests/jit/layer-without-tailwind.test.css
Normal file
@ -0,0 +1,5 @@
|
||||
@layer components {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
1
tests/jit/layer-without-tailwind.test.html
Normal file
1
tests/jit/layer-without-tailwind.test.html
Normal file
@ -0,0 +1 @@
|
||||
<div class="foo"></div>
|
||||
101
tests/jit/layer-without-tailwind.test.js
Normal file
101
tests/jit/layer-without-tailwind.test.js
Normal file
@ -0,0 +1,101 @@
|
||||
import postcss from 'postcss'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
const { currentTestName } = expect.getState()
|
||||
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: `${path.resolve(__filename)}?test=${currentTestName}`,
|
||||
})
|
||||
}
|
||||
|
||||
test('using @layer without @tailwind', async () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './layer-without-tailwind.test.html')],
|
||||
mode: 'jit',
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@layer components {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(css, config)).rejects.toThrowError(
|
||||
'`@layer components` is used but no matching `@tailwind components` directive is present.'
|
||||
)
|
||||
})
|
||||
|
||||
test('using @responsive without @tailwind', async () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './layer-without-tailwind.test.html')],
|
||||
mode: 'jit',
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@responsive {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(css, config)).rejects.toThrowError(
|
||||
'`@responsive` is used but `@tailwind utilities` is missing.'
|
||||
)
|
||||
})
|
||||
|
||||
test('using @variants without @tailwind', async () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './layer-without-tailwind.test.html')],
|
||||
mode: 'jit',
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@variants hover {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
await expect(run(css, config)).rejects.toThrowError(
|
||||
'`@variants` is used but `@tailwind utilities` is missing.'
|
||||
)
|
||||
})
|
||||
|
||||
test('non-Tailwind @layer rules are okay', async () => {
|
||||
let config = {
|
||||
purge: [path.resolve(__dirname, './layer-without-tailwind.test.html')],
|
||||
mode: 'jit',
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `
|
||||
@layer custom {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(`
|
||||
@layer custom {
|
||||
.foo {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
@ -66,7 +66,7 @@ test('prefix', () => {
|
||||
}
|
||||
}
|
||||
@tailwind utilities;
|
||||
@layer utilites {
|
||||
@layer utilities {
|
||||
.custom-utility {
|
||||
foo: bar;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user