Skip to content

Commit

Permalink
fix: introduce presedence layers (base, utilities, components) for cl…
Browse files Browse the repository at this point in the history
…ear separation of these
  • Loading branch information
sastan committed Jan 2, 2021
1 parent e8ee9c1 commit ec79a50
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 40 deletions.
2 changes: 1 addition & 1 deletion docs/examples.md
Expand Up @@ -128,7 +128,7 @@ render(
## [Next.js](https://nextjs.org/)

```jsx
import { tw } from "twind"
import { tw } from 'twind'

export default function IndexPage() {
return (
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/api.json
Expand Up @@ -431,8 +431,8 @@
"animate-spin": [
"animate-spin",
[
".animate-spin{animation:spin 1s linear infinite}",
"@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}"
"@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}",
".animate-spin{animation:spin 1s linear infinite}"
]
],
"(text(center 2xl) underline)": [
Expand Down Expand Up @@ -594,8 +594,8 @@
"divide(y-2 green-600 opacity(80 md:50))": [
"divide-y-2 divide-green-600 divide-opacity-80 md:divide-opacity-50",
[
".divide-green-600>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:#059669;border-color:rgba(5,150,105,var(--tw-divide-opacity))}",
".divide-y-2>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(2px * var(--tw-divide-y-reverse));border-top-width:2px;border-top-width:calc(2px * calc(1 - var(--tw-divide-y-reverse)))}",
".divide-green-600>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:#059669;border-color:rgba(5,150,105,var(--tw-divide-opacity))}",
".divide-opacity-80>:not([hidden])~:not([hidden]){--tw-divide-opacity:0.8}",
"@media (min-width: 768px){.md\\:divide-opacity-50>:not([hidden])~:not([hidden]){--tw-divide-opacity:0.5}}"
]
Expand Down Expand Up @@ -699,8 +699,8 @@
"md:(container mx-auto)": [
"md:container md:mx-auto",
[
"@media (min-width: 768px){.md\\:container{width:100%}}",
"@media (min-width: 768px){.md\\:mx-auto{margin-left:auto;margin-right:auto}}",
"@media (min-width: 768px){.md\\:container{width:100%}}",
"@media (min-width: 768px){@media (min-width: 640px){.md\\:container{max-width:640px}}}",
"@media (min-width: 768px){@media (min-width: 768px){.md\\:container{max-width:768px}}}",
"@media (min-width: 768px){@media (min-width: 1024px){.md\\:container{max-width:1024px}}}",
Expand Down
14 changes: 7 additions & 7 deletions src/__tests__/api.test.ts
Expand Up @@ -157,14 +157,14 @@ test('properties presedence (border)', ({ sheet, tw }) => {
'border rounded rounded-t-sm border-2 border-lrt-4 border-t-8 border-gray-300 border-dashed',
)
assert.equal(sheet.target, [
'.border-lrt-4{border-left-width:4px;border-right-width:4px;border-top-width:4px}',
'.border-gray-300{--tw-border-opacity:1;border-color:#d1d5db;border-color:rgba(209,213,219,var(--tw-border-opacity))}',
'.rounded-t-sm{border-top-left-radius:0.125rem;border-top-right-radius:0.125rem}',
'.border{border-width:1px}',
'.border-2{border-width:2px}',
'.border-dashed{border-style:dashed}',
'.rounded{border-radius:0.25rem}',
'.border-lrt-4{border-left-width:4px;border-right-width:4px;border-top-width:4px}',
'.border-t-8{border-top-width:8px}',
'.rounded-t-sm{border-top-left-radius:0.125rem;border-top-right-radius:0.125rem}',
])
})

Expand All @@ -187,8 +187,8 @@ test('properties presedence (divide)', ({ sheet, tw }) => {
'divide-x divide-x-reverse divide-opacity-75 divide-green-500',
)
assert.equal(sheet.target, [
'.divide-green-500>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:#10b981;border-color:rgba(16,185,129,var(--tw-divide-opacity))}',
'.divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;border-right-width:calc(1px * var(--tw-divide-x-reverse));border-left-width:1px;border-left-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}',
'.divide-green-500>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:#10b981;border-color:rgba(16,185,129,var(--tw-divide-opacity))}',
'.divide-x-reverse>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:1}',
'.divide-opacity-75>:not([hidden])~:not([hidden]){--tw-divide-opacity:0.75}',
])
Expand Down Expand Up @@ -245,26 +245,26 @@ test('properties presedence (divide)', ({ sheet, tw }) => {
'hover:bg-red-500 hover:p-3 m-1',
[
'.m-1{margin:0.25rem}',
'.hover\\:p-3:hover{padding:0.75rem}',
'.hover\\:bg-red-500:hover{--tw-bg-opacity:1;background-color:#ef4444;background-color:rgba(239,68,68,var(--tw-bg-opacity))}',
'.hover\\:p-3:hover{padding:0.75rem}',
],
],
[
['hover:(', 'bg-red-500', 'p-3', ')', 'm-1'],
'hover:bg-red-500 hover:p-3 m-1',
[
'.m-1{margin:0.25rem}',
'.hover\\:p-3:hover{padding:0.75rem}',
'.hover\\:bg-red-500:hover{--tw-bg-opacity:1;background-color:#ef4444;background-color:rgba(239,68,68,var(--tw-bg-opacity))}',
'.hover\\:p-3:hover{padding:0.75rem}',
],
],
[
['m-1', { hover: ['bg-red-500', 'p-3'] }],
'm-1 hover:bg-red-500 hover:p-3',
[
'.m-1{margin:0.25rem}',
'.hover\\:p-3:hover{padding:0.75rem}',
'.hover\\:bg-red-500:hover{--tw-bg-opacity:1;background-color:#ef4444;background-color:rgba(239,68,68,var(--tw-bg-opacity))}',
'.hover\\:p-3:hover{padding:0.75rem}',
],
],
[
Expand Down Expand Up @@ -634,8 +634,8 @@ test('inject global styles', ({ sheet, tw }) => {
)

assert.equal(sheet.target, [
'.tw-1kfw9fm{background-color:var(--main-bg-color)}',
'.tw-1kfw9fm :root{--main-bg-color:brown}',
'.tw-1kfw9fm{background-color:var(--main-bg-color)}',
])
})

Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/hash.test.ts
Expand Up @@ -41,8 +41,8 @@ test('class names are hashed', ({ instance, sheet }) => {
test('keyframes are hashed', ({ instance, sheet }) => {
assert.is(instance.tw('animate-pulse'), 'tw-15o2im6')
assert.equal(sheet.target, [
'.tw-15o2im6{animation:tw-sktrkv 2s cubic-bezier(0.4, 0, 0.6, 1) infinite}',
'@keyframes tw-sktrkv{0%,100%{opacity:1}50%{opacity:.5}}',
'.tw-15o2im6{animation:tw-sktrkv 2s cubic-bezier(0.4, 0, 0.6, 1) infinite}',
])
})

Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/plugins.test.ts
Expand Up @@ -30,11 +30,11 @@ test('value can be a token string', ({ setup, sheet }) => {
'mx-auto max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl my-4',
)
assert.equal(sheet.target, [
'.overflow-hidden{overflow:hidden}',
'.mx-auto{margin-left:auto;margin-right:auto}',
'.bg-white{--tw-bg-opacity:1;background-color:#fff;background-color:rgba(255,255,255,var(--tw-bg-opacity))}',
'.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);box-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 transparent),var(--tw-ring-shadow,0 0 transparent),var(--tw-shadow,0 0 transparent)}',
'.my-4{margin-bottom:1rem;margin-top:1rem}',
'.overflow-hidden{overflow:hidden}',
'.max-w-md{max-width:28rem}',
'.rounded-xl{border-radius:0.75rem}',
'@media (min-width: 768px){.md\\:max-w-2xl{max-width:42rem}}',
Expand Down
29 changes: 17 additions & 12 deletions src/__tests__/preflight.test.ts
Expand Up @@ -8,23 +8,19 @@ const test = suite('preflight')

test('add preflight styles', () => {
const sheet = virtualSheet()
create({ sheet, mode: strict })

const { tw } = create({ sheet, mode: strict })

// Ensure utitilities are added after base styles
assert.is(tw`text-center`, 'text-center')

assert.equal(sheet.target, [
'::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}',
'body,blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre,fieldset,ol,ul{margin:0}',
'button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}',
'fieldset,ol,ul,legend{padding:0}',
'textarea{resize:vertical}',
'input::placeholder,textarea::placeholder{color:#a1a1aa}',
'button,[role="button"]{cursor:pointer}',
':-moz-focusring{outline:1px dotted ButtonText}',
'::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}',
'summary{display:list-item}',
'button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;margin:0;padding:0;line-height:inherit;color:inherit}',
'sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}',
'html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}',
'table{text-indent:0;border-color:inherit;border-collapse:collapse}',
'hr{height:0;color:inherit;border-top-width:1px}',
'::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}',
'button{background-color:transparent;background-image:none}',
'body{font-family:inherit;line-height:inherit}',
'*,::before,::after{box-sizing:border-box;border:0 solid #e5e7eb}',
Expand All @@ -35,6 +31,15 @@ test('add preflight styles', () => {
'pre,code,kbd,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}',
'img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}',
'img,video{max-width:100%;height:auto}',
'body,blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre,fieldset,ol,ul{margin:0}',
'button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}',
'fieldset,ol,ul,legend{padding:0}',
'textarea{resize:vertical}',
'input::placeholder,textarea::placeholder{color:#a1a1aa}',
'button,[role="button"]{cursor:pointer}',
':-moz-focusring{outline:1px dotted ButtonText}',
'::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}',
'summary{display:list-item}',
':root{-moz-tab-size:4;tab-size:4}',
'ol,ul{list-style:none}',
'img{border-style:solid}',
Expand All @@ -45,9 +50,9 @@ test('add preflight styles', () => {
'b,strong{font-weight:bolder}',
'sub{bottom:-0.25em}',
'sup{top:-0.5em}',
'hr{height:0;color:inherit;border-top-width:1px}',
'button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}',
'::-webkit-search-decoration{-webkit-appearance:none}',
'.text-center{text-align:center}',
])
})

Expand Down
10 changes: 5 additions & 5 deletions src/css/css.test.ts
Expand Up @@ -207,8 +207,8 @@ test('keyframes', ({ keyframes, css, tw, sheet }) => {
'tw-1v80189',
)
assert.equal(sheet.target, [
'.tw-1v80189{animation:tw-cm8eaz 1s ease infinite}',
'@keyframes tw-cm8eaz{from, 20%, 53%, 80%, to{transform:translate3d(0,0,0)}40%, 43%{transform:translate3d(0, -30px, 0)}70%{transform:translate3d(0, -15px, 0)}90%{transform:translate3d(0, -4px, 0)}}',
'.tw-1v80189{animation:tw-cm8eaz 1s ease infinite}',
])
})

Expand Down Expand Up @@ -239,8 +239,8 @@ test('keyframes lazy', ({ keyframes, css, tw, sheet }) => {
assert.is(tw(styles), 'tw-14tbawn')

assert.equal(sheet.target, [
'.tw-14tbawn{animation:1s ease infinite;animation-name:tw-cm8eaz}',
'@keyframes tw-cm8eaz{from, 20%, 53%, 80%, to{transform:translate3d(0,0,0)}40%, 43%{transform:translate3d(0, -30px, 0)}70%{transform:translate3d(0, -15px, 0)}90%{transform:translate3d(0, -4px, 0)}}',
'.tw-14tbawn{animation:1s ease infinite;animation-name:tw-cm8eaz}',
])
})

Expand All @@ -265,8 +265,8 @@ test('animation', ({ animation, tw, sheet }) => {

assert.is(tw(bounce), 'tw-14tbawn')
assert.equal(sheet.target, [
'.tw-14tbawn{animation:1s ease infinite;animation-name:tw-cm8eaz}',
'@keyframes tw-cm8eaz{from, 20%, 53%, 80%, to{transform:translate3d(0,0,0)}40%, 43%{transform:translate3d(0, -30px, 0)}70%{transform:translate3d(0, -15px, 0)}90%{transform:translate3d(0, -4px, 0)}}',
'.tw-14tbawn{animation:1s ease infinite;animation-name:tw-cm8eaz}',
])
})

Expand All @@ -288,8 +288,8 @@ test('animation with callback', ({ animation, tw, sheet }) => {

assert.is(tw(slidein), 'tw-tchezo')
assert.equal(sheet.target, [
'.tw-tchezo{animation:500ms cubic-bezier(0.4,0,0.2,1);animation-name:tw-nlnhc}',
'@keyframes tw-nlnhc{from{transform:translateX(0%)}to{transform:translateX(100%)}}',
'.tw-tchezo{animation:500ms cubic-bezier(0.4,0,0.2,1);animation-name:tw-nlnhc}',
])
})

Expand Down Expand Up @@ -321,8 +321,8 @@ test('animation object notation', ({ animation, tw, sheet }) => {

assert.is(tw(bounce), 'tw-1e66f79')
assert.equal(sheet.target, [
'.tw-1e66f79{animation-duration:1s;animation-timing-function:cubic-bezier(0.4,0,0.2,1);animation-iteration-count:infinite;animation-name:tw-cm8eaz}',
'@keyframes tw-cm8eaz{from, 20%, 53%, 80%, to{transform:translate3d(0,0,0)}40%, 43%{transform:translate3d(0, -30px, 0)}70%{transform:translate3d(0, -15px, 0)}90%{transform:translate3d(0, -4px, 0)}}',
'.tw-1e66f79{animation-duration:1s;animation-timing-function:cubic-bezier(0.4,0,0.2,1);animation-iteration-count:infinite;animation-name:tw-cm8eaz}',
])
})

Expand Down
27 changes: 18 additions & 9 deletions src/twind/serialize.ts
Expand Up @@ -17,11 +17,17 @@ export interface RuleWithPresedence {

const stringifyBlock = (body: string, selector: string): string => selector + '{' + body + '}'

export const enum Layer {
base = 0,
utilities = 1,
components = 2,
}

export const serialize = (
prefix: Prefixer,
variants: Record<string, string>,
context: Context,
): ((css: CSSRules, className?: string, rule?: Rule) => RuleWithPresedence[]) => {
): ((css: CSSRules, className?: string, rule?: Rule, layer?: Layer) => RuleWithPresedence[]) => {
const { theme, tag } = context

// Hash/Tag tailwind custom properties during serialization
Expand Down Expand Up @@ -189,26 +195,29 @@ export const serialize = (
presedence *
// Declarations: 8 bits = 256
(1 << 8) +
// 4: greatest precedence of properties
// if there is no property presedence this is most likely a custom property only declaration
// these have the highest presedence
((((maxPropertyPresedence || 15) & 15) << 4) |
// 4: number of declarations (descending)
(Math.max(0, 15 - numberOfDeclarations) & 15)),
// 4: number of declarations (descending)
(((Math.max(0, 15 - numberOfDeclarations) & 15) << 4) |
// 4: greatest precedence of properties
// if there is no property presedence this is most likely a custom property only declaration
// these have the highest presedence
((maxPropertyPresedence || 15) & 15)),
})
}
}

const variantPresedence = makeVariantPresedenceCalculator(theme, variants)

return (css, className, rule) => {
return (css, className, rule, layer = className ? 1 : 0) => {
// Initial presedence based on layer (base = 0, utilities = 1, components = 2)
layer <<= 28

rules = []

stringify(
[],
className ? '.' + escape(className) : '',
// If we have a rule, create starting presedence based on the variants
rule ? rule.v.reduceRight(variantPresedence, 0) : 0,
rule ? rule.v.reduceRight(variantPresedence, layer) : layer,
css,
rule && rule.i,
)
Expand Down

0 comments on commit ec79a50

Please sign in to comment.