qaffeine
Version:
Decaffeinate your JS-powered CSS stylesheets
320 lines (237 loc) • 12.3 kB
Markdown
# qaffeine
Decaffeinate your JS-powered CSS stylesheets server-side

> Looking to work with caffeinated stylesheets client-side? Check out [deqaf](https://github.com/tomhodgins/deqaf)
## About
This project provides a way to parse extended CSS on the server and separate out the plain CSS from the JS-powered styles. This allows you to write CSS stylesheets that include styles supported by JavaScript plugins.
## Installation
This project is provided as a Node formatted module.
The easiest way to install qaffeine is via npm:
```bash
npm install qaffeine
```
## Plugin Usage
Qaffeine is distributed as a CommonJS module, ready to be used with Node. The easiest way to import qaffeine into your project is with a line similar to this:
```js
const qaffeine = require('qaffeine')
qaffeine(
{
stylesheet: {},
rules: {}
},
'input.css',
'output.js',
'output.css'
)
```
### From Node
To use qaffeine in node scripts, you can run the function supplying the following arguments:
```js
qaffeine(plugins, inputCSS, outputJS, outputCSS)
```
- `plugins` is an object containing `stylesheet` and `rule` properties, optionally containing any stylesheet or rule plugins you want to make available to qaffeine.
- `inputCSS` is the filename of a CSS file you want to read
- `outputJS` is an optional argument that defines the filename of the JavaScript output
- `outputCSS` is an optional argument that defines the filename of any CSS output
#### Printing JS to the console
If the plugin is run with no `outputJS` or `outputCSS` supplied, the resulting JavaScript will be printed in the console/
#### Outputting JavaScript-only
If the plugin is run with `outputJS` defined, but no `outputCSS`, the resulting JavaScript will be written to a file at the location specified by `outputJS`, and also include a copy of all CSS styles.
#### Outputting CSS and JavaScript
This is the recommended way to use qaffeine. When both `outputJS` and `outputCSS` filenames are specified, all output JavaScript will be written to a file at the location specified by `outputJS`, and all output CSS will be written to a file at the location specified by `outputCSS`.
### 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 qaffeine, 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 qaffeine like this:
```js
qaffeine(
{
rule: {
example
}
},
'input.css'
)
```
This would load a file named `input.css`, 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 qaffeine like this:
```js
qaffeine(
{
stylesheet: {
example
}
},
'input.css'
)
```
This would load a file named `input.css`, process any `@supports` rules that include `--example()` in the condition, and would process them with our `example()` plugin.
By supplying plugins to qaffeine 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 qaffeine, 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 node script using qaffeine, check out [index.js](https://github.com/tomhodgins/qaffeine-demo/blob/master/index.js) from the [qaffeine-demo](https://github.com/tomhodgins/qaffeine-demo) project
## Writing Extended Selectors for JS-Powered Rules
```css
selector, list[--custom='"extended", "selector", "here"'] { }
```
To extend a CSS rule for use with qaffeine, 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 qaffeine 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 qaffeine can read, check out [stylesheet.css](https://github.com/tomhodgins/qaffeine-demo/blob/master/src/stylesheet.css#L110) from the [qaffeine-demo](https://github.com/tomhodgins/qaffeine-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 qaffeine 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 qaffeine can read, check out [stylesheet.css](https://github.com/tomhodgins/qaffeine-demo/blob/master/src/stylesheet.css#L297&L298) from the [qaffeine-demo](https://github.com/tomhodgins/qaffeine-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 qaffeine, 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 qaffeine 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 qaffeine can read, check out [stylesheet.css](https://github.com/tomhodgins/qaffeine-demo/blob/master/src/stylesheet.css#L114) from the [qaffeine-demo](https://github.com/tomhodgins/qaffeine-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 qaffeine 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 qaffeine can read, check out [stylesheet.css](https://github.com/tomhodgins/qaffeine-demo/blob/master/src/stylesheet.css#L313&L316) from the [qaffeine-demo](https://github.com/tomhodgins/qaffeine-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)
- [qaffeine-demo: element query plugin, selector edition](https://github.com/tomhodgins/qaffeine-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)
- [Qaffeine demo project](https://github.com/tomhodgins/qaffeine-demo)
- [Deqaf demo project](https://github.com/tomhodgins/deqaf-demo)