Skip to content

Commit

Permalink
JIT: Add support for before/after pseudo-elements (#4461)
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwathan committed May 26, 2021
1 parent 1badba5 commit c8a5f81
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 23 deletions.
5 changes: 4 additions & 1 deletion src/corePlugins.js
@@ -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]()
})
Expand Down
84 changes: 67 additions & 17 deletions src/jit/corePlugins.js
Expand Up @@ -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'],
Expand All @@ -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}`)
})
)
}
Expand Down Expand Up @@ -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)',
}),
}
)
)

Expand All @@ -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)',
}),
}
)
)
},
Expand Down Expand Up @@ -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)',
}),
}
)
)
}
Expand All @@ -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 }) }
)
)
}
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/content.js
@@ -0,0 +1,5 @@
import createUtilityPlugin from '../util/createUtilityPlugin'

export default function () {
return createUtilityPlugin('content')
}
2 changes: 2 additions & 0 deletions src/plugins/index.js
Expand Up @@ -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'
25 changes: 20 additions & 5 deletions src/util/pluginUtils.js
Expand Up @@ -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
},
})
Expand All @@ -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
},
})
Expand All @@ -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
})

Expand All @@ -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
})

Expand Down
3 changes: 3 additions & 0 deletions stubs/defaultConfig.stub.js
Expand Up @@ -175,6 +175,9 @@ module.exports = {
200: '2',
},
container: {},
content: {
none: 'none',
},
cursor: {
auto: 'auto',
default: 'default',
Expand Down
3 changes: 3 additions & 0 deletions tests/jit/arbitrary-values.test.css
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions tests/jit/arbitrary-values.test.html
Expand Up @@ -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>
3 changes: 3 additions & 0 deletions tests/jit/basic-usage.test.css
Expand Up @@ -777,3 +777,6 @@
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.content-none {
content: none;
}
1 change: 1 addition & 0 deletions tests/jit/basic-usage.test.html
Expand Up @@ -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>
17 changes: 17 additions & 0 deletions tests/jit/variants.test.css
Expand Up @@ -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),
Expand Down
4 changes: 4 additions & 0 deletions tests/jit/variants.test.html
Expand Up @@ -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>
Expand Down

0 comments on commit c8a5f81

Please sign in to comment.