deqaf
Version:
Decaffeinate your CSS stylesheets client-side
288 lines (216 loc) • 11 kB
Markdown
# deqaf
Decaffeinate your JS-powered CSS stylesheets client-side
> Looking to work with caffeinated stylesheets server-side? Check out [qaffeine](https://github.com/tomhodgins/qaffeine)
## About
This project provides a way to parse extended CSS in the browser and separate out the JS-powered styles from ["caffeinated" CSS stylesheets](https://github.com/tomhodgins/caffeinated-style-sheets). This allows you to write CSS stylesheets that include styles supported by JavaScript plugins.
## Plugin Usage
This project is provided as an ES module.
The easiest way to use deqaf is to import it inside a module:
```html
<script type=module>
import deqaf from 'https://unpkg.com/deqaf/index.js'
deqaf({
stylesheet: {},
rule: {}
})
</script>
```
To use deqaf, you can run the function supplying the following argument:
```js
deqaf(plugins)
```
- `plugins` is an object containing `stylesheet` and `rule` properties, optionally containing any stylesheet or rule plugins you want to make available to deqaf.
### Defining Plugins
To extend CSS with JavaScript functions, the two following possibilities exist: a rule plugin, or a stylesheet plugin.
A rule plugin accepts a CSS selector list, as well as any additional options, and lastly takes a CSS declaration list (everything inside the curly brackets `{}` after the selector list. A rule plugin must return a string that is a valid CSS stylesheet.
The other type of plugin is a stylesheet plugin, which takes 0 or more optional arguments, as well as one last argument that contains a CSS stylesheet as a string, and returns a string that is a valid CSS stylesheet.
If you had a plugin named `example()` that was loaded in the file where you're using deqaf, suppose it looks like this:
```js
example(selector, rule) {
return Math.random() > .5
? `${selector} { ${rule} }`
: ''
}
```
That function would return a rule written for the supplied selector 50% of the time, and return nothing the other 50% of the time. A function like this could be given to deqaf like this:
```js
deqaf(
{
rule: {
example
}
}
)
```
This would loop through the entire CSSOM and process any rules that include `[--example]` in the selector, and would process them with our `example()` plugin.
On the other hand if we had a simple stylesheet plugin which takes a CSS stylesheet:
```js
function example(stylesheet) {
return Math.random() > .5
? stylesheet
: ''
}
```
This function would return the supplied stylesheet 50% of the time, and return nothing the other half of the time. We could pass this into deqaf like this:
```js
deqaf(
{
stylesheet: {
example
}
}
)
```
This would process any `@supports` rules that include `--example()` in the condition, and would process them with our `example()` plugin.
By supplying plugins to deqaf through this structure we can include rule and stylesheet plugins with the same name, as well as give functions a custom name for our use with deqaf, even if the function has a different name in your JavaScript code. This makes for a flexible and comfortable stylesheet writing experience.
> To see an example of a script using deqaf, check out [index.html](https://github.com/tomhodgins/deqaf-demo/blob/master/dist/index.html#L113&L124) from the [deqaf-demo](https://github.com/tomhodgins/deqaf-demo) project
## Writing Extended Selectors for JS-Powered Rules
```css
selector, list[--custom='"extended", "selector", "here"'] { }
```
To extend a CSS rule for use with deqaf, add a custom extended selector between the normal selector list and the declaration list, effectively splitting the rule in two: everything before the extended selector is your CSS selector list, and everything after your extended selector is part of the declaration list:
```css
selector <HERE> { property: value; }
```
To this location you can add an attribute selector `[]` that's written for any name you want, as long as it starts with a double dash `--`. If we were going to extend a rule with a plugin named `demo()`, we could add `[--demo]`.
```css
h1[--demo] {
background: lime;
}
```
This would allow deqaf to parse out the selector list `h1`, as well as the declaration list `background: lime;`, and write a call to our `demo()` function like this:
```js
demo('h1', 'background: lime;')
```
> To see an example of an extended selector deqaf can read, check out [stylesheet.css](https://github.com/tomhodgins/deqaf-demo/blob/master/src/stylesheet.css#L110) from the [deqaf-demo](https://github.com/tomhodgins/deqaf-demo) project:
```css
.minwidth[--element='{"minWidth": 300}'] {
border-color: limegreen;
}
```
### Defining custom events for extended selectors
```css
--selector: ;
--events: [];
```
- `--selector` is either `window` or a CSS selector list as a string
- `--events` is an array of events quoted as strings
The default settings for jsincss (which deqaf uses to run the JS-powered rules it finds) are to listen to the load, resize, input and click events on the window object, so you could think of that like this:
```css
[--example] {
--selector: window;
--events: ["load", "resize", "input", "click"]
}
```
Instead, if you wanted a rule to reprocess only on the `paste` event, and only on `<textarea>` elements, you could set custom `--selector` and `--events` like this:
```css
[--example] {
--selector: "textarea";
--events: ["paste"];
}
```
> To see an example of an extended selector with custom events deqaf can read, check out [stylesheet.css](https://github.com/tomhodgins/deqaf-demo/blob/master/src/stylesheet.css#L297&L298) from the [deqaf-demo](https://github.com/tomhodgins/deqaf-demo) project:
```css
.min-scroll-y[--element='{"minScrollY": 50}'] {
--selector: ".min-scroll-y";
--events: ["scroll"];
border-color: limegreen;
}
```
## Writing Extended @supports Rules for JS-Powered At-rules
```css
@supports --custom('extended', 'at-rule', 'here') { }
```
To extend an `@supports` rule for use with deqaf, add a custom extended selector between the `@supports` text and the group body rule.
```css
@supports <HERE> { }
```
To this location you can add any name you want, as long as it starts with a double dash `--`, and ends with a pair of brackets `()`. If we were going to extend a rule with a plugin named `demo()`, we could add `--demo()`.
```css
@supports --demo() {
html {
background: lime;
}
}
```
This would allow deqaf to parse out the group body rule and write a call to our `demo()` function like this:
```js
demo('html { background: lime; }')
```
> To see an example of an extended selector deqaf can read, check out [stylesheet.css](https://github.com/tomhodgins/deqaf-demo/blob/master/src/stylesheet.css#L114) from the [deqaf-demo](https://github.com/tomhodgins/deqaf-demo) project:
```css
@supports --element(".minwidth", {"minWidth": 300}) {
[--self] {
background: greenyellow;
}
}
```
### Defining custom events for extended at-rules
```css
[--options] {
--selector: ;
--events: [];
}
```
- `[--options]` a custom selector for a rule that we can use to define custom events
- `--selector` is either `window` or a CSS selector list as a string
- `--events` is an array of events quoted as strings
The default settings for jsincss (which deqaf uses to run the JS-powered rules it finds) are to listen to the load, resize, input and click events on the window object, so you could think of that like this:
```css
[--options] {
--selector: window;
--events: ["load", "resize", "input", "click"]
}
```
Instead, if you wanted a rule to reprocess only on the `paste` event, and only on `<textarea>` elements, you could set custom `--selector` and `--events` like this:
```css
[--options] {
--selector: "textarea";
--events: ["paste"];
}
```
> To see an example of an extended at-rule with custom events deqaf can read, check out [stylesheet.css](https://github.com/tomhodgins/deqaf-demo/blob/master/src/stylesheet.css#L313&L316) from the [deqaf-demo](https://github.com/tomhodgins/deqaf-demo) project:
```css
@supports --element(".min-scroll-y", {"minScrollY": 50}) {
[--options] {
--selector: ".min-scroll-y";
--events: ["scroll"];
}
[--self] {
background: greenyellow;
}
}
```
## Known Compatible Stylesheet Plugins
- [jsincss-compare-attribute](https://github.com/tomhodgins/jsincss-compare-attribute)
- [jsincss-days](https://github.com/tomhodgins/jsincss-days)
- [jsincss-element-query](https://github.com/tomhodgins/jsincss-element-query)
- [jsincss-overflow](https://github.com/tomhodgins/jsincss-overflow)
- [jsincss-protocol-sniffer](https://github.com/tomhodgins/jsincss-protocol-sniffer)
- [jsincss-viewport](https://github.com/tomhodgins/jsincss-viewport)
- [qaffeine-demo: element query plugin, at-rule edition](https://github.com/tomhodgins/qaffeine-demo/blob/master/src/element-query-at-rule.js)
- [css-polyfill-patterns: dynamic values examples, at-rule examples](https://github.com/tomhodgins/css-polyfill-patterns)
## Known Compatible Rule Plugins
- [jsincss-ancestor-selector](https://github.com/tomhodgins/jsincss-ancestor-selector)
- [jsincss-auto-expand](https://github.com/tomhodgins/jsincss-auto-expand)
- [jsincss-closest-selector](https://github.com/tomhodgins/jsincss-closest-selector)
- [jsincss-custom-specificity](https://github.com/tomhodgins/jsincss-custom-specificity)
- [jsincss-elder-selector](https://github.com/tomhodgins/jsincss-elder-selector)
- [jsincss-element-units](https://github.com/tomhodgins/jsincss-element-units)
- [jsincss-first-selector](https://github.com/tomhodgins/jsincss-first-selector)
- [jsincss-frontend-variables](https://github.com/tomhodgins/jsincss-frontend-variables)
- [jsincss-has-selector](https://github.com/tomhodgins/jsincss-has-selector)
- [jsincss-last-selector](https://github.com/tomhodgins/jsincss-last-selector)
- [jsincss-parent-selector](https://github.com/tomhodgins/jsincss-parent-selector)
- [jsincss-previous-selector](https://github.com/tomhodgins/jsincss-previous-selector)
- [jsincss-regex-match](https://github.com/tomhodgins/jsincss-regex-match)
- [jsincss-scoped-eval](https://github.com/tomhodgins/jsincss-scoped-eval)
- [jsincss-string-match](https://github.com/tomhodgins/jsincss-string-match)
- [jsincss-xpath-selector](https://github.com/tomhodgins/jsincss-xpath-selector)
- [deqaf-demo: element query plugin, selector edition](https://github.com/tomhodgins/deqaf-demo/blob/master/src/element-query-selector.js)
- [css-polyfill-patterns: pseudo class examples, simple selector examples](https://github.com/tomhodgins/css-polyfill-patterns)
## More Reading
- [Qaffeine walkthrough video](https://www.youtube.com/watch?v=6pRRB1gXgPo)
- [Caffeinated Style Sheets talk slides [Web Unleashed 2018]](https://tomhodgins.com/caffeinated-style-sheets.pdf)
- [Deqaf demo project](https://github.com/tomhodgins/deqaf-demo)
- [Qaffeine demo project](https://github.com/tomhodgins/qaffeine-demo)