Skip to content

Commit

Permalink
allow CSS to be used in preflight and rules
Browse files Browse the repository at this point in the history
  • Loading branch information
sastan committed Jan 24, 2022
1 parent 8a4e6dd commit 0478b45
Show file tree
Hide file tree
Showing 20 changed files with 281 additions and 115 deletions.
27 changes: 27 additions & 0 deletions .changeset/friendly-pants-rush.md
@@ -0,0 +1,27 @@
---
'@twind/preset-tailwind': patch
'twind': patch
---

allow CSS to be used in preflight and rules

```js
setup({
preflight: css`
body {
background: theme(colors.gray.100);
}
`,

rules: [
[
// bg-red, bg-#ccc, bg-transparent
'bg-',
({ $$ }) =>
css`
background-color: ${$$};
`,
],
],
})
```
8 changes: 4 additions & 4 deletions package.json
Expand Up @@ -37,26 +37,26 @@
"name": "twind",
"path": "packages/twind/dist/twind.esnext.js",
"brotli": true,
"limit": "6.3kb"
"limit": "6.4kb"
},
{
"name": "twind (setup)",
"path": "packages/twind/dist/twind.esnext.js",
"import": "{ setup }",
"brotli": true,
"limit": "4.3kb"
"limit": "4.35kb"
},
{
"name": "@twind/cdn",
"path": "packages/cdn/dist/cdn.esnext.js",
"brotli": true,
"limit": "14.55kb"
"limit": "14.6kb"
},
{
"name": "@twind/tailwind",
"path": "packages/tailwind/dist/tailwind.esnext.js",
"brotli": true,
"limit": "14.45kb"
"limit": "14.5kb"
}
],
"// start — use 'BROWSER=none' to prevent vite to open a browser if within codesandbox or stackblitz": "",
Expand Down
2 changes: 1 addition & 1 deletion packages/cdn/package.json
Expand Up @@ -44,7 +44,7 @@
"name": "@twind/cdn",
"path": "dist/cdn.esnext.js",
"brotli": true,
"limit": "14.5kb"
"limit": "14.6kb"
}
],
"dependencies": {
Expand Down
20 changes: 15 additions & 5 deletions packages/preset-ext/src/preset-ext.test.json
Expand Up @@ -12,9 +12,9 @@
"siblings:underline": ".siblings\\:underline~*{text-decoration:underline}",
"sibling:underline": ".sibling\\:underline+*{text-decoration:underline}",
"override:text-center": ".override\\:text-center.override\\:text-center{text-align:center}",
"not-focus:invalid:border-red-500": ".not-focus\\:invalid\\:border-red-500:invalid:not(:focus){--tw-border-opacity:1;border-color:rgba(239,68,68,var(--tw-border-opacity))}",
"invalid:not-focus:border-red-500": ".invalid\\:not-focus\\:border-red-500:not(:focus):invalid{--tw-border-opacity:1;border-color:rgba(239,68,68,var(--tw-border-opacity))}",
"not-disabled:focus:font-bold": ".not-disabled\\:focus\\:font-bold:focus:not(:disabled){font-weight:700}",
"not-focus:invalid:border-red-500": ".not-focus\\:invalid\\:border-red-500:not(:focus):invalid{--tw-border-opacity:1;border-color:rgba(239,68,68,var(--tw-border-opacity))}",
"invalid:not-focus:border-red-500": ".invalid\\:not-focus\\:border-red-500:invalid:not(:focus){--tw-border-opacity:1;border-color:rgba(239,68,68,var(--tw-border-opacity))}",
"not-disabled:focus:font-bold": ".not-disabled\\:focus\\:font-bold:not(:disabled):focus{font-weight:700}",
"not-logged-in:hidden": "body:not(.logged-in) .not-logged-in\\:hidden{display:none}",
"not-last-child:mb-5": ".not-last-child\\:mb-5:not(:last-child){margin-bottom:1.25rem}",
"[aria-expanded='true']:font-bold": ".\\[aria-expanded\\=\\'true\\'\\]\\:font-bold[aria-expanded='true']{font-weight:700}",
Expand All @@ -30,8 +30,18 @@
"[data-lang='zh-TW']:text-purple-400": ".\\[data-lang\\=\\'zh-TW\\'\\]\\:text-purple-400[data-lang='zh-TW']{--tw-text-opacity:1;color:rgba(192,132,252,var(--tw-text-opacity))}",
"group-hocus:underline": ".group:is(:hover,:focus-visible) .group-hocus\\:underline{text-decoration:underline}",
"dark:group-hocus:underline": "@media (prefers-color-scheme:dark){.group:is(:hover,:focus-visible) .dark\\:group-hocus\\:underline{text-decoration:underline}}",
"as-dark:group-hocus:underline": ".dark .group:is(:hover,:focus-visible) .as-dark\\:group-hocus\\:underline{text-decoration:underline}",
"group-hocus:dark:underline": [
"dark:group-hocus:underline",
[
"@media (prefers-color-scheme:dark){.group:is(:hover,:focus-visible) .dark\\:group-hocus\\:underline{text-decoration:underline}}"
]
],
"group-xxx-hocus:underline": ".group-xxx:is(:hover,:focus-visible) .group-xxx-hocus\\:underline{text-decoration:underline}",
"dark:group-xxx-hocus:underline": "@media (prefers-color-scheme:dark){.group-xxx:is(:hover,:focus-visible) .dark\\:group-xxx-hocus\\:underline{text-decoration:underline}}",
"as-dark:group-xxx-hocus:underline": ".dark .group-xxx:is(:hover,:focus-visible) .as-dark\\:group-xxx-hocus\\:underline{text-decoration:underline}"
"group-xxx-hocus:dark:underline": [
"dark:group-xxx-hocus:underline",
[
"@media (prefers-color-scheme:dark){.group-xxx:is(:hover,:focus-visible) .dark\\:group-xxx-hocus\\:underline{text-decoration:underline}}"
]
]
}
5 changes: 1 addition & 4 deletions packages/preset-ext/src/preset-ext.test.ts
Expand Up @@ -9,10 +9,7 @@ import data from './preset-ext.test.json'
const tw = twind(
{
presets: [presetExt(), presetTailwind({ enablePreflight: false })],
variants: [
['as-dark', '.dark &'],
['not-logged-in', 'body:not(.logged-in) &'],
],
variants: [['not-logged-in', 'body:not(.logged-in) &']],
},
virtual(),
)
Expand Down
99 changes: 87 additions & 12 deletions packages/preset-tailwind/src/preflight.test.ts
@@ -1,19 +1,17 @@
import { assert, test, afterEach } from 'vitest'
import { assert, test } from 'vitest'

import { twind, virtual } from 'twind'
import { css, twind, virtual } from 'twind'

import tailwind from '.'

const tw = twind(
{
presets: [tailwind()],
},
virtual(),
)

afterEach(() => tw.clear())

test('preflight on first inject', () => {
const tw = twind(
{
presets: [tailwind()],
},
virtual(),
)

assert.deepEqual(tw.target, [])

assert.strictEqual(tw('underline'), 'underline')
Expand Down Expand Up @@ -60,4 +58,81 @@ test('preflight on first inject', () => {
])
})

test.todo('custom preflight')
test('custom preflight', () => {
const tw = twind(
{
presets: [tailwind()],
preflight: css`
html,
body,
#__next {
@apply: h-screen w-screen p-0 m-0 overflow-x-hidden overflow-y-auto bg-gray-100;
font-size: 14px;
}
p {
@apply shadow;
}
* {
scrollbar-color: theme(colors.gray.500);
&::-webkit-scrollbar,
& scrollbar {
width: 1rem;
height: 1rem;
}
}
`,
},
virtual(),
)

assert.deepEqual(tw.target, [])

assert.strictEqual(tw('underline'), 'underline')

assert.deepEqual(tw.target, [
'*{--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}',
'*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}',
"::before,::after{--tw-content:''}",
'html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;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"}',
'body{margin:0;line-height:inherit}',
'hr{height:0;color:inherit;border-top-width:1px}',
'abbr:where([title]){text-decoration:underline dotted}',
'h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}',
'a{color:inherit;text-decoration:inherit}',
'b,strong{font-weight:bolder}',
'code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}',
'small{font-size:80%}',
'sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}',
'sub{bottom:-0.25em}',
'sup{top:-0.5em}',
'table{text-indent:0;border-color:inherit;border-collapse:collapse}',
'button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}',
'button,select{text-transform:none}',
"button,[type='button'],[type='reset'],[type='submit']{-webkit-appearance:button;background-color:transparent;background-image:none}",
':-moz-focusring{outline:auto}',
':-moz-ui-invalid{box-shadow:none}',
'progress{vertical-align:baseline}',
'::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}',
"[type='search']{-webkit-appearance:textfield;outline-offset:-2px}",
'::-webkit-search-decoration{-webkit-appearance:none}',
'::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}',
'summary{display:list-item}',
'blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}',
'fieldset{margin:0;padding:0}',
'legend{padding:0}',
'ol,ul,menu{list-style:none;margin:0;padding:0}',
'textarea{resize:vertical}',
'input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}',
'button,[role="button"]{cursor:pointer}',
':disabled{cursor:default}',
'img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}',
'img,video{max-width:100%;height:auto}',
'[hidden]{display:none}',
'html,body,#__next{height:100vh;width:100vw;padding:0px;margin:0px;overflow-x:hidden;overflow-y:auto;--tw-bg-opacity:1;background-color:rgba(243,244,246,var(--tw-bg-opacity));font-size:14px}',
'p{--tw-shadow:0 1px 3px 0 rgba(0,0,0,0.1), 0 1px 2px -1px rgba(0,0,0,0.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}',
'*{scrollbar-color:#6b7280}',
'*::-webkit-scrollbar,* scrollbar{width:1rem;height:1rem}',
'.underline{text-decoration:underline}',
])
})
24 changes: 12 additions & 12 deletions packages/preset-tailwind/src/rules.test.json
Expand Up @@ -789,10 +789,10 @@
".font-bold{font-weight:700}",
".hover\\:text-purple-500:hover{--tw-text-opacity:1;color:rgba(168,85,247,var(--tw-text-opacity))}",
".hover\\:text-center:hover{text-align:center}",
".hover\\:active\\:ring:active:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}",
".active\\:hover\\:text-lg:hover:active{font-size:1.125rem;line-height:1.75rem}",
".hover\\:active\\:underline:active:hover{text-decoration:underline}",
".hover\\:active\\:ring-opacity-5:active:hover{--tw-ring-opacity:0.05}"
".hover\\:active\\:ring:hover:active{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}",
".active\\:hover\\:text-lg:active:hover{font-size:1.125rem;line-height:1.75rem}",
".hover\\:active\\:underline:hover:active{text-decoration:underline}",
".hover\\:active\\:ring-opacity-5:hover:active{--tw-ring-opacity:0.05}"
]
],
"\n text-4xl underline\n font-(bold sans)\n transition\n hover:(rotate-6 scale-125 cursor-pointer)\n active:(-rotate-12 scale-150)\n": [
Expand Down Expand Up @@ -900,11 +900,11 @@
[
"@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){@media (min-width:640px){.md\\:container{max-width:640px}}}",
"@media (min-width:640px){@media (min-width:768px){.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}}}",
"@media (min-width:768px){@media (min-width:1280px){.md\\:container{max-width:1280px}}}",
"@media (min-width:768px){@media (min-width:1536px){.md\\:container{max-width:1536px}}}"
"@media (min-width:1024px){@media (min-width:768px){.md\\:container{max-width:1024px}}}",
"@media (min-width:1280px){@media (min-width:768px){.md\\:container{max-width:1280px}}}",
"@media (min-width:1536px){@media (min-width:768px){.md\\:container{max-width:1536px}}}"
]
],
"hover:md:text-center": [
Expand Down Expand Up @@ -972,14 +972,14 @@
"hover:!text-center hover:focus:!font-bold",
[
".hover\\:\\!text-center:hover{text-align:center !important}",
".hover\\:focus\\:\\!font-bold:focus:hover{font-weight:700 !important}"
".hover\\:focus\\:\\!font-bold:hover:focus{font-weight:700 !important}"
]
],
"!hover:(text-center focus:font-bold)": [
"hover:!text-center hover:focus:!font-bold",
[
".hover\\:\\!text-center:hover{text-align:center !important}",
".hover\\:focus\\:\\!font-bold:focus:hover{font-weight:700 !important}"
".hover\\:focus\\:\\!font-bold:hover:focus{font-weight:700 !important}"
]
],
"!(text-xl underline)": [
Expand Down Expand Up @@ -1321,7 +1321,7 @@
"hover:~(!text-3xl,!text-center,!underline,italic,focus:not-italic)",
[
".hover\\:\\~\\(\\!text-3xl\\,\\!text-center\\,\\!underline\\,italic\\,focus\\:not-italic\\):hover{font-size:1.875rem !important;line-height:2.25rem !important;text-align:center !important;text-decoration:underline !important;font-style:italic}",
".hover\\:\\~\\(\\!text-3xl\\,\\!text-center\\,\\!underline\\,italic\\,focus\\:not-italic\\):focus:hover{font-style:normal}"
".hover\\:\\~\\(\\!text-3xl\\,\\!text-center\\,\\!underline\\,italic\\,focus\\:not-italic\\):hover:focus{font-style:normal}"
]
],
"text-center ~(text-5xl) text-3xl": [
Expand Down Expand Up @@ -1550,7 +1550,7 @@
".focus-within\\:m-9:focus-within{margin:2.25rem}",
".hover\\:m-10:hover{margin:2.5rem}",
".focus\\:m-11:focus{margin:2.75rem}",
".hover\\:focus\\:m-12:focus:hover{margin:3rem}",
".hover\\:focus\\:m-12:hover:focus{margin:3rem}",
".focus-visible\\:m-14:focus-visible{margin:3.5rem}",
".active\\:m-16:active{margin:4rem}",
".disabled\\:m-20:disabled{margin:5rem}",
Expand Down
4 changes: 2 additions & 2 deletions packages/twind/package.json
Expand Up @@ -44,14 +44,14 @@
"name": "twind",
"path": "dist/twind.esnext.js",
"brotli": true,
"limit": "6.35kb"
"limit": "6.4kb"
},
{
"name": "twind (setup)",
"path": "dist/twind.esnext.js",
"import": "{ setup }",
"brotli": true,
"limit": "4.3kb"
"limit": "4.35kb"
},
{
"name": "twind (twind + cssom)",
Expand Down
32 changes: 22 additions & 10 deletions packages/twind/src/css.ts
Expand Up @@ -6,6 +6,7 @@ import { hash } from './utils'
import { Layer } from './internal/precedence'
import { interleave } from './internal/interleave'
import { removeComments } from './internal/parse'
import { merge } from './internal/merge'

export type CSSValue = string | number | bigint | Falsey

Expand All @@ -17,7 +18,7 @@ export function css(
strings: CSSObject | string | TemplateStringsArray,
...interpolations: readonly CSSValue[]
): string {
const { label = 'css', ...ast } = Array.isArray(strings)
const ast = Array.isArray(strings)
? astish(
interleave(strings as TemplateStringsArray, interpolations, (interpolation) =>
interpolation != null && typeof interpolation != 'boolean'
Expand All @@ -27,11 +28,16 @@ export function css(
)
: typeof strings == 'string'
? astish(strings)
: (strings as CSSObject)
: [strings as CSSObject]

const className = label + hash(JSON.stringify(ast))
const className = (ast.find((o) => o.label)?.label || 'css') + hash(JSON.stringify(ast))

return register(className, (rule, context) => serialize(ast, rule, context, Layer.o))
return register(className, (rule, context) =>
merge(
ast.flatMap((css) => serialize(css, rule, context, Layer.o)),
className,
),
)
}

// Based on https://github.com/cristianbote/goober/blob/master/src/core/astish.js
Expand All @@ -40,22 +46,28 @@ const newRule = / *(?:(?:([\u0080-\uFFFF\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|
/**
* Convert a css style string into a object
*/
function astish(css: string): CSSObject {
function astish(css: string): CSSObject[] {
css = removeComments(css)

const tree: CSSObject[] = [{}]
const rules: CSSObject[] = []
const tree: string[] = []
let block: RegExpExecArray | null

while ((block = newRule.exec(css))) {
// Remove the current entry
if (block[4]) tree.shift()
if (block[4]) tree.pop()

if (block[3]) {
tree.unshift((tree[0][block[3]] = (tree[0][block[3]] as CSSObject) || {}))
// new nested
tree.push(block[3])
} else if (!block[4]) {
tree[0][block[1]] = block[2]
rules.push(
tree.reduceRight((css, block) => ({ [block]: css }), {
[block[1]]: block[2],
} as CSSObject),
)
}
}

return tree[0]
return rules
}
5 changes: 2 additions & 3 deletions packages/twind/src/internal/define.ts
@@ -1,7 +1,6 @@
import type { Falsey, TwindRule } from '../types'
import type { Falsey } from '../types'
import type { ParsedRule } from './parse'
import { merge } from './merge'
import { convert, moveToLayer } from './precedence'
import { convert } from './precedence'
import { register } from './registry'
import { translateWith } from './translate'

Expand Down

0 comments on commit 0478b45

Please sign in to comment.