Skip to content

Commit c8a5f81

Browse files
authored
JIT: Add support for before/after pseudo-elements (#4461)
1 parent 1badba5 commit c8a5f81

12 files changed

+130
-23
lines changed

src/corePlugins.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import * as plugins from './plugins/index.js'
2-
32
import configurePlugins from './util/configurePlugins'
43

4+
const jitOnlyPlugins = ['content']
5+
56
export default function ({ corePlugins: corePluginConfig }) {
7+
corePluginConfig = corePluginConfig.filter((pluginName) => !jitOnlyPlugins.includes(pluginName))
8+
69
return configurePlugins(corePluginConfig, Object.keys(plugins)).map((pluginName) => {
710
return plugins[pluginName]()
811
})

src/jit/corePlugins.js

+67-17
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,50 @@ import {
1212

1313
export default {
1414
pseudoClassVariants: function ({ config, addVariant }) {
15+
addVariant(
16+
'before',
17+
transformAllSelectors(
18+
(selector) => {
19+
return updateAllClasses(selector, (className, { withPseudo }) => {
20+
return withPseudo(`before${config('separator')}${className}`, '::before')
21+
})
22+
},
23+
{
24+
withRule: (rule) => {
25+
let foundContent = false
26+
rule.walkDecls('content', () => {
27+
foundContent = true
28+
})
29+
if (!foundContent) {
30+
rule.prepend(postcss.decl({ prop: 'content', value: '""' }))
31+
}
32+
},
33+
}
34+
)
35+
)
36+
37+
addVariant(
38+
'after',
39+
transformAllSelectors(
40+
(selector) => {
41+
return updateAllClasses(selector, (className, { withPseudo }) => {
42+
return withPseudo(`after${config('separator')}${className}`, '::after')
43+
})
44+
},
45+
{
46+
withRule: (rule) => {
47+
let foundContent = false
48+
rule.walkDecls('content', () => {
49+
foundContent = true
50+
})
51+
if (!foundContent) {
52+
rule.prepend(postcss.decl({ prop: 'content', value: '""' }))
53+
}
54+
},
55+
}
56+
)
57+
)
58+
1559
let pseudoVariants = [
1660
['first', 'first-child'],
1761
['last', 'last-child'],
@@ -35,7 +79,7 @@ export default {
3579
addVariant(
3680
variantName,
3781
transformAllClasses((className, { withPseudo }) => {
38-
return withPseudo(`${variantName}${config('separator')}${className}`, state)
82+
return withPseudo(`${variantName}${config('separator')}${className}`, `:${state}`)
3983
})
4084
)
4185
}
@@ -95,11 +139,13 @@ export default {
95139
(className) => {
96140
return `motion-safe${config('separator')}${className}`
97141
},
98-
() =>
99-
postcss.atRule({
100-
name: 'media',
101-
params: '(prefers-reduced-motion: no-preference)',
102-
})
142+
{
143+
wrap: () =>
144+
postcss.atRule({
145+
name: 'media',
146+
params: '(prefers-reduced-motion: no-preference)',
147+
}),
148+
}
103149
)
104150
)
105151

@@ -109,11 +155,13 @@ export default {
109155
(className) => {
110156
return `motion-reduce${config('separator')}${className}`
111157
},
112-
() =>
113-
postcss.atRule({
114-
name: 'media',
115-
params: '(prefers-reduced-motion: reduce)',
116-
})
158+
{
159+
wrap: () =>
160+
postcss.atRule({
161+
name: 'media',
162+
params: '(prefers-reduced-motion: reduce)',
163+
}),
164+
}
117165
)
118166
)
119167
},
@@ -142,11 +190,13 @@ export default {
142190
(className) => {
143191
return `dark${config('separator')}${className}`
144192
},
145-
() =>
146-
postcss.atRule({
147-
name: 'media',
148-
params: '(prefers-color-scheme: dark)',
149-
})
193+
{
194+
wrap: () =>
195+
postcss.atRule({
196+
name: 'media',
197+
params: '(prefers-color-scheme: dark)',
198+
}),
199+
}
150200
)
151201
)
152202
}
@@ -162,7 +212,7 @@ export default {
162212
(className) => {
163213
return `${screen}${config('separator')}${className}`
164214
},
165-
() => postcss.atRule({ name: 'media', params: query })
215+
{ wrap: () => postcss.atRule({ name: 'media', params: query }) }
166216
)
167217
)
168218
}

src/plugins/content.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import createUtilityPlugin from '../util/createUtilityPlugin'
2+
3+
export default function () {
4+
return createUtilityPlugin('content')
5+
}

src/plugins/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,5 @@ export { default as transitionProperty } from './transitionProperty'
161161
export { default as transitionDelay } from './transitionDelay'
162162
export { default as transitionDuration } from './transitionDuration'
163163
export { default as transitionTimingFunction } from './transitionTimingFunction'
164+
165+
export { default as content } from './content'

src/util/pluginUtils.js

+20-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function updateAllClasses(selectors, updateClass) {
99
selectors.walkClasses((sel) => {
1010
let updatedClass = updateClass(sel.value, {
1111
withPseudo(className, pseudo) {
12-
sel.parent.insertAfter(sel, selectorParser.pseudo({ value: `:${pseudo}` }))
12+
sel.parent.insertAfter(sel, selectorParser.pseudo({ value: `${pseudo}` }))
1313
return className
1414
},
1515
})
@@ -36,7 +36,7 @@ export function updateLastClasses(selectors, updateClass) {
3636

3737
let updatedClass = updateClass(lastClass.value, {
3838
withPseudo(className, pseudo) {
39-
lastClass.parent.insertAfter(lastClass, selectorParser.pseudo({ value: `:${pseudo}` }))
39+
lastClass.parent.insertAfter(lastClass, selectorParser.pseudo({ value: `${pseudo}` }))
4040
return className
4141
},
4242
})
@@ -51,11 +51,14 @@ export function updateLastClasses(selectors, updateClass) {
5151
return result
5252
}
5353

54-
export function transformAllSelectors(transformSelector, wrap = null) {
54+
export function transformAllSelectors(transformSelector, { wrap, withRule } = {}) {
5555
return ({ container }) => {
5656
container.walkRules((rule) => {
5757
let transformed = rule.selector.split(',').map(transformSelector).join(',')
5858
rule.selector = transformed
59+
if (withRule) {
60+
withRule(rule)
61+
}
5962
return rule
6063
})
6164

@@ -67,23 +70,35 @@ export function transformAllSelectors(transformSelector, wrap = null) {
6770
}
6871
}
6972

70-
export function transformAllClasses(transformClass) {
73+
export function transformAllClasses(transformClass, { wrap, withRule } = {}) {
7174
return ({ container }) => {
7275
container.walkRules((rule) => {
7376
let selector = rule.selector
7477
let variantSelector = updateAllClasses(selector, transformClass)
7578
rule.selector = variantSelector
79+
if (withRule) {
80+
withRule(rule)
81+
}
7682
return rule
7783
})
84+
85+
if (wrap) {
86+
let wrapper = wrap()
87+
wrapper.append(container.nodes)
88+
container.append(wrapper)
89+
}
7890
}
7991
}
8092

81-
export function transformLastClasses(transformClass, wrap = null) {
93+
export function transformLastClasses(transformClass, { wrap, withRule } = {}) {
8294
return ({ container }) => {
8395
container.walkRules((rule) => {
8496
let selector = rule.selector
8597
let variantSelector = updateLastClasses(selector, transformClass)
8698
rule.selector = variantSelector
99+
if (withRule) {
100+
withRule(rule)
101+
}
87102
return rule
88103
})
89104

stubs/defaultConfig.stub.js

+3
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ module.exports = {
175175
200: '2',
176176
},
177177
container: {},
178+
content: {
179+
none: 'none',
180+
},
178181
cursor: {
179182
auto: 'auto',
180183
default: 'default',

tests/jit/arbitrary-values.test.css

+3
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,9 @@
383383
.duration-\[var\(--app-duration\)\] {
384384
transition-duration: var(--app-duration);
385385
}
386+
.content-\[attr\(content-before\)\] {
387+
content: attr(content-before);
388+
}
386389
@media (min-width: 1024px) {
387390
.lg\:grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
388391
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;

tests/jit/arbitrary-values.test.html

+1
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,6 @@
111111
<div class="ring-offset-[19rem]"></div>
112112
<div class="ring-opacity-[var(--ring-opacity)]"></div>
113113
<div class="delay-[var(--delay)]"></div>
114+
<div class="content-[attr(content-before)]"></div>
114115
</body>
115116
</html>

tests/jit/basic-usage.test.css

+3
Original file line numberDiff line numberDiff line change
@@ -777,3 +777,6 @@
777777
.ease-in-out {
778778
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
779779
}
780+
.content-none {
781+
content: none;
782+
}

tests/jit/basic-usage.test.html

+1
Original file line numberDiff line numberDiff line change
@@ -143,5 +143,6 @@
143143
<div class="w-12"></div>
144144
<div class="break-words"></div>
145145
<div class="z-30"></div>
146+
<div class="content-none"></div>
146147
</body>
147148
</html>

tests/jit/variants.test.css

+17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@
1818
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
1919
var(--tw-shadow);
2020
}
21+
.before\:block::before {
22+
content: '';
23+
display: block;
24+
}
25+
.before\:bg-red-500::before {
26+
content: '';
27+
--tw-bg-opacity: 1;
28+
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
29+
}
30+
.after\:flex::after {
31+
content: '';
32+
display: flex;
33+
}
34+
.after\:uppercase::after {
35+
content: '';
36+
text-transform: uppercase;
37+
}
2138
.first\:shadow-md:first-child {
2239
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
2340
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),

tests/jit/variants.test.html

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
<div class="empty:shadow-md"></div>
2626
<div class="read-only:shadow-md"></div>
2727

28+
<!-- Pseudo-element variants -->
29+
<div class="before:block before:bg-red-500"></div>
30+
<div class="after:flex after:uppercase"></div>
31+
2832
<!-- Group variants -->
2933
<div class="group-hover:shadow-md"></div>
3034
<div class="group-focus:shadow-md"></div>

0 commit comments

Comments
 (0)