JIT: Add support for before/after pseudo-elements (#4461)

This commit is contained in:
Adam Wathan 2021-05-26 08:57:02 -04:00 committed by GitHub
parent 1badba55b7
commit c8a5f816f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 130 additions and 23 deletions

View File

@ -1,8 +1,11 @@
import * as plugins from './plugins/index.js'
import configurePlugins from './util/configurePlugins'
const jitOnlyPlugins = ['content']
export default function ({ corePlugins: corePluginConfig }) {
corePluginConfig = corePluginConfig.filter((pluginName) => !jitOnlyPlugins.includes(pluginName))
return configurePlugins(corePluginConfig, Object.keys(plugins)).map((pluginName) => {
return plugins[pluginName]()
})

View File

@ -12,6 +12,50 @@ import {
export default {
pseudoClassVariants: function ({ config, addVariant }) {
addVariant(
'before',
transformAllSelectors(
(selector) => {
return updateAllClasses(selector, (className, { withPseudo }) => {
return withPseudo(`before${config('separator')}${className}`, '::before')
})
},
{
withRule: (rule) => {
let foundContent = false
rule.walkDecls('content', () => {
foundContent = true
})
if (!foundContent) {
rule.prepend(postcss.decl({ prop: 'content', value: '""' }))
}
},
}
)
)
addVariant(
'after',
transformAllSelectors(
(selector) => {
return updateAllClasses(selector, (className, { withPseudo }) => {
return withPseudo(`after${config('separator')}${className}`, '::after')
})
},
{
withRule: (rule) => {
let foundContent = false
rule.walkDecls('content', () => {
foundContent = true
})
if (!foundContent) {
rule.prepend(postcss.decl({ prop: 'content', value: '""' }))
}
},
}
)
)
let pseudoVariants = [
['first', 'first-child'],
['last', 'last-child'],
@ -35,7 +79,7 @@ export default {
addVariant(
variantName,
transformAllClasses((className, { withPseudo }) => {
return withPseudo(`${variantName}${config('separator')}${className}`, state)
return withPseudo(`${variantName}${config('separator')}${className}`, `:${state}`)
})
)
}
@ -95,11 +139,13 @@ export default {
(className) => {
return `motion-safe${config('separator')}${className}`
},
() =>
postcss.atRule({
name: 'media',
params: '(prefers-reduced-motion: no-preference)',
})
{
wrap: () =>
postcss.atRule({
name: 'media',
params: '(prefers-reduced-motion: no-preference)',
}),
}
)
)
@ -109,11 +155,13 @@ export default {
(className) => {
return `motion-reduce${config('separator')}${className}`
},
() =>
postcss.atRule({
name: 'media',
params: '(prefers-reduced-motion: reduce)',
})
{
wrap: () =>
postcss.atRule({
name: 'media',
params: '(prefers-reduced-motion: reduce)',
}),
}
)
)
},
@ -142,11 +190,13 @@ export default {
(className) => {
return `dark${config('separator')}${className}`
},
() =>
postcss.atRule({
name: 'media',
params: '(prefers-color-scheme: dark)',
})
{
wrap: () =>
postcss.atRule({
name: 'media',
params: '(prefers-color-scheme: dark)',
}),
}
)
)
}
@ -162,7 +212,7 @@ export default {
(className) => {
return `${screen}${config('separator')}${className}`
},
() => postcss.atRule({ name: 'media', params: query })
{ wrap: () => postcss.atRule({ name: 'media', params: query }) }
)
)
}

5
src/plugins/content.js Normal file
View File

@ -0,0 +1,5 @@
import createUtilityPlugin from '../util/createUtilityPlugin'
export default function () {
return createUtilityPlugin('content')
}

View File

@ -161,3 +161,5 @@ export { default as transitionProperty } from './transitionProperty'
export { default as transitionDelay } from './transitionDelay'
export { default as transitionDuration } from './transitionDuration'
export { default as transitionTimingFunction } from './transitionTimingFunction'
export { default as content } from './content'

View File

@ -9,7 +9,7 @@ export function updateAllClasses(selectors, updateClass) {
selectors.walkClasses((sel) => {
let updatedClass = updateClass(sel.value, {
withPseudo(className, pseudo) {
sel.parent.insertAfter(sel, selectorParser.pseudo({ value: `:${pseudo}` }))
sel.parent.insertAfter(sel, selectorParser.pseudo({ value: `${pseudo}` }))
return className
},
})
@ -36,7 +36,7 @@ export function updateLastClasses(selectors, updateClass) {
let updatedClass = updateClass(lastClass.value, {
withPseudo(className, pseudo) {
lastClass.parent.insertAfter(lastClass, selectorParser.pseudo({ value: `:${pseudo}` }))
lastClass.parent.insertAfter(lastClass, selectorParser.pseudo({ value: `${pseudo}` }))
return className
},
})
@ -51,11 +51,14 @@ export function updateLastClasses(selectors, updateClass) {
return result
}
export function transformAllSelectors(transformSelector, wrap = null) {
export function transformAllSelectors(transformSelector, { wrap, withRule } = {}) {
return ({ container }) => {
container.walkRules((rule) => {
let transformed = rule.selector.split(',').map(transformSelector).join(',')
rule.selector = transformed
if (withRule) {
withRule(rule)
}
return rule
})
@ -67,23 +70,35 @@ export function transformAllSelectors(transformSelector, wrap = null) {
}
}
export function transformAllClasses(transformClass) {
export function transformAllClasses(transformClass, { wrap, withRule } = {}) {
return ({ container }) => {
container.walkRules((rule) => {
let selector = rule.selector
let variantSelector = updateAllClasses(selector, transformClass)
rule.selector = variantSelector
if (withRule) {
withRule(rule)
}
return rule
})
if (wrap) {
let wrapper = wrap()
wrapper.append(container.nodes)
container.append(wrapper)
}
}
}
export function transformLastClasses(transformClass, wrap = null) {
export function transformLastClasses(transformClass, { wrap, withRule } = {}) {
return ({ container }) => {
container.walkRules((rule) => {
let selector = rule.selector
let variantSelector = updateLastClasses(selector, transformClass)
rule.selector = variantSelector
if (withRule) {
withRule(rule)
}
return rule
})

View File

@ -175,6 +175,9 @@ module.exports = {
200: '2',
},
container: {},
content: {
none: 'none',
},
cursor: {
auto: 'auto',
default: 'default',

View File

@ -383,6 +383,9 @@
.duration-\[var\(--app-duration\)\] {
transition-duration: var(--app-duration);
}
.content-\[attr\(content-before\)\] {
content: attr(content-before);
}
@media (min-width: 1024px) {
.lg\:grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;

View File

@ -111,5 +111,6 @@
<div class="ring-offset-[19rem]"></div>
<div class="ring-opacity-[var(--ring-opacity)]"></div>
<div class="delay-[var(--delay)]"></div>
<div class="content-[attr(content-before)]"></div>
</body>
</html>

View File

@ -777,3 +777,6 @@
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.content-none {
content: none;
}

View File

@ -143,5 +143,6 @@
<div class="w-12"></div>
<div class="break-words"></div>
<div class="z-30"></div>
<div class="content-none"></div>
</body>
</html>

View File

@ -18,6 +18,23 @@
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.before\:block::before {
content: '';
display: block;
}
.before\:bg-red-500::before {
content: '';
--tw-bg-opacity: 1;
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
}
.after\:flex::after {
content: '';
display: flex;
}
.after\:uppercase::after {
content: '';
text-transform: uppercase;
}
.first\:shadow-md:first-child {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),

View File

@ -25,6 +25,10 @@
<div class="empty:shadow-md"></div>
<div class="read-only:shadow-md"></div>
<!-- Pseudo-element variants -->
<div class="before:block before:bg-red-500"></div>
<div class="after:flex after:uppercase"></div>
<!-- Group variants -->
<div class="group-hover:shadow-md"></div>
<div class="group-focus:shadow-md"></div>