Generate html by writing css.
npm i html-by-css
import generate from 'html-by-css';
const source = `
ul#list {
list-style: none;
margin: 0;
padding: 0;
& li.item*3 {
padding: .5rem;
:is(a[href="#"]) {
content: Link;
color: inherit;
}
}
}
`;
const { html, css } = generate(source);
HTML
<ul id="list">
<li class="item">
<a href="#">Link</a>
</li>
<li class="item">
<a href="#">Link</a>
</li>
<li class="item">
<a href="#">Link</a>
</li>
</ul>
CSS
/* legacy: false (default) */
ul#list {
list-style: none;
margin: 0;
padding: 0;
& li.item {
padding: .5rem;
:is(a[href="#"]) {
color: inherit;
}
}
}
/* legacy: true */
ul#list {
list-style: none;
margin: 0;
padding: 0;
}
ul#list li.item {
padding: .5rem;
}
ul#list li.item :is(a[href="#"]) {
color: inherit;
}
- Use
postcss
to parse and walk through source. - On target nodes, process data as HTML:
- Find
content
on non-pseudo elements and inject as text. - Find
emmet
syntax and duplicate elements. - Parse selector using
css-what
. - Transform selector to
himalaya
schema.
- Find
- Process source as valid CSS:
- Remove
content
on non-pseudo elements. - Remove
emmet
syntax. - Optionally, use
postcss-nesting
plugin to transform into legacy, non-nested CSS. - Apply additional plugins as provided.
- Remove
- Return object with
{ html, css }
.
The nesting should be prepared using the current w3 CSS Nesting specification. The most important concept is that a nested selector must start with a symbol.
.foo {
/* ❌ invalid */
span {
color: hotpink;
}
/* ✅ valid */
& span {
color: hotpink;
}
/* ❌ invalid */
span & {
color: hotpink;
}
/* ✅ valid */
:is(span) & {
color: hotpink;
}
}
If you wish to collapse the nesting for the CSS output, set legacy: true
in the options. This uses postcss-nesting
with the default options.
const { html, css } = generate(source, { legacy: true });
If you want to set your own options, provide your own version of the postcss-nesting
plugin and configure.
import nesting from 'postcss-nesting';
import generate from 'html-by-css';
const postcssPlugins = [nesting({
noIsPseudoSelector: true
})];
const { html, css } = generate(source, { plugins: postcssPlugins });
Warning
Do not use
legacy: true
with your ownpostcss-nesting
configuration. The internal (legacy
) usage will run first. Either do not declare or explicitly setlegacy: false
. This is only when using a custompostcss-nesting
, all other plugins can be used withlegacy: true
.
You can include additional postcss
plugins. Example below helps with removing duplicate declarations.
import dedupe from 'postcss-discard-duplicates';
import generate from 'html-by-css';
const postcssPlugins = [dedupe()];
const { html, css } = generate(source, { plugins: postcssPlugins });
This is helpful if you have several similar elements with different contents.
ul#list {
& li.item {
& a*0 {
color: inherit;
}
& a[href="/home"] {
content: Home;
}
& a[href="/about"] {
content: About;
}
& a[href="/contact"] {
content: Contact
}
}
}
Note
The use of
a*0
says write the styles found here, but don't write HTML. When you use this, the represented node and it's children will not be written as HTML.
To create multiple elements, use emmet
syntax.
ul {
li*5 {
/* Makes 5 <li/> elements */
}
}
This li*5
selector is not valid CSS and is transformed during processing to li
.
To add text content, use the content
property on non-pseudo elements.
main {
& h1 {
content: Hello world!;
}
}
<main>
<h1>Hello world!</h1>
</main>
Note
There are no quotes around the string. Adding quotes would include the quotes in the output.
The content
property is not valid on non-pseudo elements and is removed from these declarations during processing.
npm t
- Using
cheerio
to traverse HTML in tests. - Using
@projectwallace/css-analyzer
to analyze returned CSS.
There's definitely some cases not covered in the tests yet.
-
content
prop and nested selector (both text and children). - Other pseudo-selectors (
:nth-child()
,:checked
).
Your scientists were so preoccupied with whether they could, they didn't stop to think if they should.
There's a few projects out there that are HTML preprocessors (Haml, Pug) which have their own (sometimes CSS-like) syntax. I wondered if we could get closer to just writing CSS to produce HTML. With the new nesting specification and the power of postcss
, it looks like we can!