Getting started
Installation
To use SQSS directly on your website, add the following lines:
<!-- To use the latest version-->
<script src="https://unpkg.com/@dthung1602/sqss/dist/bundle/index.js"></script>
<!-- Or to specify a version -->
<script src="https://unpkg.com/@dthung1602/sqss@<version go here>/dist/bundle/index.js"></script>
<!-- `transpileSQSSToCSS` is now available in your global scope!-->
<!-- To add style to your website, use the following snippet-->
<script>
const sqlString = `
-- All of your styles go here
UPDATE styles
SET "background" = 'blue'
WHERE class = 'target';
`;
const cssString = transpileSQSSToCSS(sqlString);
document.head.innerHTML += `<style>${cssString}</style>`;
</script>
To install with npm
$ npm i @dthung1602/sqss
# or if you prefer yarn
$ yarn add @dthung1602/sqss
To use it in your project, call the transpile function in your build script:
const sqss = require("@dthung1602/sqss");
const fs = require("fs");
const sqlString = fs.readFileSync("path/to/your/stylesheet.sql", "utf-8");
const cssString = sqss.transpileSQSSToCSS(sqlString);
fs.writeFileSync("path/to/your/build/folder/output.css", cssString);
TypeScript is supported
import { transpileSQSSToCSS } from "@dthung1602/sqss";
const css: string = transpileSQSSToCSS(`UPDATE styles SET color = 'blue' where id = 'target';`);
Basic syntax
Let's have a look at this code
UPDATE styles
SET "background" = 'blue',
"color" = 'white' -- just a comment
WHERE id = 'target';
This will transpiled to
#target {
background: blue;
color: white;
}
A few note here:
- Only
UPDATE
statement is supported, noSELECT
,DELETE
or anything else - There's only one table -
styles
- The
SET
clause is required, whileWHERE
is optional (this will result in the*
CSS selector) - If the column name contains the special characters,
border-radius
for example, it must be double-quoted - Comments are allowed, but will not be transpiled
- Keywords and functions are case-insensitive, while tables & columns are not
I'm hooked, let's try it right away!
Go to play groundSupported features
Select by id
UPDATE styles
SET "background" = 'blue',
"color" = 'white',
"font-family" = '"Droid Sans", serif'
WHERE id = 'target';
#target {
background: blue;
color: white;
font-family: "Droid Sans", serif;
}
Select by class
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE class = 'target';
.target {
background: blue;
color: white;
}
Select by element
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE element = 'div';
div {
background: blue;
color: white;
}
Select by attribute
Use "[attribute-name]"
to select the desired attribute
Remember, the double quote is required
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE "[title]" IS NOT NULL;
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE "[title]" = 'help test';
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE "[title]" LIKE '%help test';
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE "[title]" LIKE 'help test%';
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE "[title]" LIKE '%help test%';
[title] {
background: blue;
color: white;
}
[title="help test"] {
background: blue;
color: white;
}
[title^="help test"] {
background: blue;
color: white;
}
[title$="help test"] {
background: blue;
color: white;
}
[title*="help test"] {
background: blue;
color: white;
}
Select by pseudo element & pseudo class
SQSS supports all pseudo-elements and classes that do not require parenthesis.
For the full list of pseudo-elements and classes, see here and here
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE ":hover" = TRUE;
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE "::after" = TRUE;
:hover {
background: blue;
color: white;
}
::after {
background: blue;
color: white;
}
Negation
Negation is supported for every selector type
UPDATE styles
SET "background" = 'blue'
WHERE id != 'target';
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE "[title]" NOT LIKE '%help text';
UPDATE styles
SET "background" = 'blue'
WHERE ":hover" IS NOT FALSE;
:not(#target) {
background: blue;
}
:not([title^="help text"]) {
background: blue;
color: white;
}
:hover {
background: blue;
}
Select by first child, last child
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE IS_FIRST_CHILD(node) = TRUE;
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE IS_LAST_CHILD(node) = TRUE;
:first-child {
background: blue;
color: white;
}
:last-child {
background: blue;
color: white;
}
Select by n-th child, n-th last child
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE IS_NTH_CHILD(node, 2) = TRUE;
UPDATE styles
SET "background" = 'blue',
"color" = 'white'
WHERE IS_NTH_LAST_CHILD(node, 6) = TRUE;
:nth-child(2) {
background: blue;
color: white;
}
:nth-last-child(6) {
background: blue;
color: white;
}
And
We can add multiple conditions to the WHERE
clause. Parenthesis are also allowed.
For multiple classes, just add more class = '...'
. This is not applied to id nor element
UPDATE styles
SET "background" = 'blue'
WHERE (element = 'h3' AND class = 'center')
AND (id = 'target' AND class = 'page-title');
h3#target.center.page-title {
background: blue;
}
Or
OR
is pretty much the same as AND
UPDATE styles
SET "background" = 'blue'
WHERE (element = 'h3' OR class = 'center') OR id = 'target';
h3, .center, #target {
background: blue;
}
Mixing AND
with OR
is also supported
Keep in mind that AND
has higher precedence than OR
.
The SQSS transpiler will flatten the condition using boolean algebra
UPDATE styles
SET "background" = 'yellow'
WHERE (element = 'h3' OR id = 'target') AND class = 'center'
OR (class = 'page-title')
OR ("::before" = TRUE OR "[title]" = 'some text' AND ":hover" = TRUE);
h3.center, #target.center, .page-title, ::before, :hover[title="some text"] {
background: yellow;
}
Parent & ancestor selector
Use the JOIN
clause to describe CSS parent/child, ancestor/descendant relationship
UPDATE styles
JOIN styles AS ancestor ON IS_ANCESTOR(ancestor.node, styles.node) = TRUE
SET "background" = 'blue',
"color" = 'white'
WHERE class = 'inside'
AND ancestor.class = 'outside';
UPDATE styles
JOIN styles AS prt ON IS_PARENT(prt.node, node) = TRUE
SET "background" = 'blue',
"color" = 'white'
WHERE class = 'inside'
AND prt.class = 'outside';
.outside .inside {
background: blue;
color: white;
}
.outside > .inside {
background: blue;
color: white;
}
There's only one styles
table, but we can join/reference it using aliases.
Aliases, of course, can be any valid name and not limited to prt
,
ancestor
as in the example
All style updates are made to the first styles
table
(i.e. no updates to prt
or ancestor
)
Alias to the to-be-updated styles
table is not supported (yet)
Any column without a table is implicitly understood as a column in the styles
table
Sibling selector
Sibling selectors are pretty much the same as parent/ancestor selector
UPDATE styles
JOIN styles AS pre ON IS_PREV(pre.node, node) = true
SET "background" = 'blue',
"color" = 'white'
WHERE class = 'target'
AND pre.class = 'before';
UPDATE styles
JOIN styles AS pre ON COMES_BEFORE(pre.node, node) = true
SET "background" = 'blue',
"color" = 'white'
WHERE class = 'target'
AND pre.class = 'before';
.before + .target {
background: blue;
color: white;
}
.before ~ .target {
background: blue;
color: white;
}
Joining multiple tables is possible
UPDATE styles
JOIN styles AS style1 ON IS_ANCESTOR(style1.node, node) = true
JOIN styles AS style2 ON IS_PARENT(style2.node, style1.node) = true
JOIN styles AS style3 ON IS_PREV(style3.node, style2.node) = true
JOIN styles AS style4 ON COMES_BEFORE(style4.node, style3.node) = true
SET "background" = 'blue',
"color" = 'white'
WHERE class = 'inside'
AND style1.id = 'outside'
AND style3.element = 'div'
AND "style4.[title]" IS NOT NULL;
[title] ~ div + * > #outside .inside {
background: blue;
color: white;
}
The joins must form a chain:
styles
joins style1
,
style1
joins style2
,
style2
joins style3
,
style3
joins style4
Note that in this example, we do not specify any condition for style2
,
hence the *
in the result CSS
The join condition cannot be FALSE
Feel like trying it yet?
Go to playgroundInternal working
SQSS
is a transpiler and pretty much follows the typical architecture of such one
Let's zoom in
Sqss module
The Lexer breaks the raw string to tokens, like UPDATE
, SET
, (
)
, 'blue'
, "styles.[title]"
, IS_FIRST_CHILD
, etc.
TokenStream is just an abstraction over the tokens so that the Parser can easily manipulate them
Most of the hard work is done in the Parser. Usually, a parser is generated from a grammar by some tool like GNU Bison or ANTLR.
Since the grammar of sqss is quite small, the parser is handwritten.
The SemanticAnalyzer makes sure that the SQL actually make senses and meet all the constraints. For example:
- The table name must always be
styles
- The join conditions must form a chain
- The function arguments are of correct types
- ...
Should any is not right, the SemanticAnalyzer throws an exception
Transpiler module
Before the transpiling magic happens, we need to modify the SQL abstract syntax tree first to make our life easier:
- The NegationSimplifier takes a double negation and converts it to a positive one
- The ExpressionFlattener performs all the necessary boolean algebra to the condition and outputs a
flattened expression. Basically, it distributes all the ANDs to the ORs
(A or B) and (C or D) = (A and C) or (A and D) or (B and C) or (B and D)
Css module
At this point, most of the hard work is already done
Validator just checks that the CSS properties have valid names before the Generator generates CSS string from the CSS abstract syntax tree