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, no SELECT, DELETE or anything else
  • There's only one table - styles
  • The SET clause is required, while WHERE 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
⚠ Don't forget the semicolon at the end!
I'm hooked, let's try it right away!
Go to play ground

Supported 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 playground

Internal working

SQSS is a transpiler and pretty much follows the typical architecture of such one

Overview

Let's zoom in

Sqss module

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

sqss 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

sqss 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


And that's all there is to it!
Go to playground