Render-js is library for generating html and non cascading styling in js.
It's a can be used to build simple websites or advanced frontend frameworks, alleviating typical css complexity problems.
Typically a framework starts out simple, but quickly reaches a point where it becomes hard to change anything because of ever increasing CSS entanglement.
Render-js keeps specificity extremely low by generating tachyons (single purpose classes).
The programmer simply defines what css properties an HTML element should have. You can even define media queries for responsive styling.
- Genesis
- Generation 1: HTML
- Generation 2: NCSS
- Generation 3: DOM
- Generation 4: OBJ
- Generation 5: CLASS (current generation)
- Examples
- Vision / goal
- Definition
- Compatibility
- Changelog
I started programming Render-js as an alternative to thymeleaf, but it quickly grew into much more. Everytime I got a major idea I developed it as a new variant without changing the previous one:
Use this if you only want to generate html. It should be highly performant.
An HTML element consists of three things:
- tagName
- attributes
- content
Which rather naturally gives this syntax in js:
tagName(attributes, content);
The attributes is an object with properties, while the content can be a string or a function reference or an array of them. Both the attributes and content parameter are optional.
tagName();
tagName('String');
tagName({key: 'value'});
tagName({key: 'value'}, 'String');
You build a dom by nesting html element functions.
tagName(
tagName()
);
For example:
p({
class: 'className'
}, [
'Text before ',
a({href: 'https://www.example.com'}, 'link'),
' text after.'
]);
Too avoid having to quote js property names, any dashes must be removed. This can be achieved since html attribute names are case insensitive, while js property names are case sensitive. So we use camelcase in JS and later dasherize into html attribute names.
So this attribute object in JavaScript:
{dataProp: 'value'}
Get rendered into this in html:
data-prop="value"
I have found an exception: The SVG attribute named viewBox. It is case-sensitive. So I simply don't dasherize that one. Make an issue on github if you run into any other case-sensitive HTML/SVG attributes.
CSS property names contains dashes, which JS property names also can, but needs to be quoted. This can be avoided since CSS property names are case insensitive, while JS property names are case sensitive. So we use camelcase in JS and later dasherize into CSS property name.
So this style attribute object in JavaScript:
{
color: 'white',
backgroundColor: 'black'
}
Gets rendered into this in html:
style="color:white;background-color:black"
import {p, render} from 'render-js/html.es';
render(p({style: {backgroundColor: 'white'}}, 'Hello world'));
var R = require('render-js/dist/html.js');
var render = R.render;
var p = R.p;
render(p({style: {backgroundColor: 'white'}}, 'Hello world'));
<p style="background-color:white">Hello world</p>
This is when I figured out that it would be nice to generate css too.
All the features of this generation is present in the newest generation, so you should probably use that instead.
This is when I figured out accessing and modify the dom could be useful.
All the features of this generation should be present in the newest generation, so you should probably use that instead.
import { Dom, doctype, html, head, title, style,
body, main, h1, div, p, span } from 'render-js/dom.es';
const view = new Dom([ // Things that are always the same
doctype(),
html([
head([
title(),
style({ type: 'text/css' })
]), // head
body([
main([
h1(),
div({
style: {
display: 'flex'
}
},
p(
span({
style: {
display: 'none',
'&:hover': {
color: 'red'
}
},
_media: {
minWidth480: {
display: 'block'
}
}
})
) // p
) // div
]) // main
]) // body
]) // html
]); // view
const model = { // Things that change
title: 'Title',
heading: 'Heading',
text: 'Only visible from tablet up'
}; // model
// Controller
view.head.title.add(model.title);
view.body.main.h1.add(model.heading);
view.body.main.div.p.span.add(model.text);
// This will trigger build on body only:
view.head.style.add(view.body.getCss().join('')); // pageContributions
const html = view.render(); // Will trigger build on entire DOM.
Which will give you this html (without whitespace and indentation):
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
<style type="text/css">
.d-f{display:flex}
.d-n{display:none}
.h--c-r:hover{color:red}
@media (min-width:480px) {
.d-b-w-mi-480{display:block}
}
</style>
</head>
<body>
<main>
<h1>Heading</h1>
<div class="d-f">
<p>
<span class="d-b-w-mi-480 d-n h--c-r:hover"
>Only visible from tablet up</span>
</p>
</div>
</main>
</body>
</html>
This is when I started thinking about elements with "shared" styling, such as list elements. It would be wasteful to generate the same css many times. Which meant I needed to keep some state.
All the features of this generation should be present in the newest generation, so you should probably use that instead.
This is when I started thinking about keeping the Dom object as small as possible so it could be transferred efficiently to the browser.
dependencies {
include 'com.enonic.lib:render-js:1.20.0'
}
var R = require('/lib/render-js/index.js');
exports.get = function(request) {
return {
body: R.render([
R.doctype(),
R.html([
R.head(R.title('Page Title')),
R.body([
R.main([
R.h1('Hello World!')
]) // main
]) // body
]) // html
]) // render
}; // return
} // exports.get
import R, { render, doctype, html, head, title, body, main, h1, form } from 'render-js';
import { checkbox } from 'render-js/input';
import { getPageContributions } from 'pages/default/default';
function myCustomElement(...args) {
return R.el('custom', ...args);
}
export function get() {
const {headBegin, headEnd, bodyBegin, bodyEnd} = getPageContributions();
const model = {
title: 'Title'
};
return {
body: render([
doctype(),
html([
head([
headBegin,
title(model.title)
headEnd
]), // head
body([
bodyBegin,
main([
h1(model.title),
form({method: 'post'}, [
checkbox({name: 'name'}),
R.button({type: 'submit'}, 'Button text')
]), // form
myCustomElement({ attr: 'value'}, 'Content')
]), // main
bodyEnd
]) // body
]) // html
]), // render
contentType: 'text/html'
}; // return
} // function get
I would like to create a render library that can be used both server/client -side. Which means it must be javascript.
It will be written using es2015 which makes for much nicer code with for instance template strings.
See doc/server-side.es6 for an example of how I want a render to look like.
Autogenerate "css" from element "styling" and pageContribute it. Which means every element/component function must return both the html string and an (unique?) array of generated "css". The array must propagate upwards. In terms of extendability, returning an object with named properties should be the way to go.
- All element methods take between, zero and two params.
- There are three types of allowed param types:
- String
- Array of String
- Object
- If you call it using two params, the first one should be of type Object.
Lib version | XP version |
---|---|
1.x.x | 6.12.2 |
0.x.x | 6.12.2 |
- FEATURE encodeHtmlEntity() and decodeHtmlEntity()
- FEATURE options (doesn't seem to improve speed)
- abbreviateCssPropertyNames = true
- abbreviateCssPropertyValues = true
- addDefaultUnits = true
- Profiled toClassName()
- FEATURE: CSS abbreviations for:
- animation
- animation-delay
- animation-direction
- animation-duration
- animation-fill-mode
- animation-iteration-count
- animation-name
- animation-play-state
- animation-timing-function
- FEATURE: CSS abbreviations for:
- list-style
- list-style-image
- list-style-position
- list-style-type
- pointer-events
- FEATURE: CSS abbreviations for: cursor, margin, padding
- BUG: Wrong key used to lookup CSS value abbreviations (prop -> dashProp)
- FEATURE: CSS value abbreviations for:
- border-style
- box-sizing
- clear
- fill
- font-weight
- line-height
- outline
- outline-color
- outline-style
- outline-width
- overflow
- overflow-wrap
- overflow-x
- overflow-y
- position
- text-decoration
- text-decoration-color
- text-decoration-line
- text-decoration-style
- transition
- transition-delay
- transition-duration
- transition-property
- transition-timing-function
- vertical-align
- white-space
- BUG: VOID_ELEMENTS was not part of ELEMENTS
- BUG: img is an empty element https://developer.mozilla.org/en-US/docs/Glossary/Empty_element
- CLASS FEATURE: chainable build()
- CLASS FEATURE: src/class/reset/comlock.es Personal flavour css defaults
- CLASS FEATURE: src/class/reset.es Elements with reset css
- Class FEATURE: access()
- FEATURE: Abbreviations for alignContent, alignItems, gridColumnGap, gridRowGap, gridTemplateColumns, gridTemplateRows, justifyContent, justifyItems
- BUG: Add /lib to npm package.
- CLASS BUG: domPath() did not support elements without content.
- CLASS BUG: domPath() did not support an array as first parameter.
- addStyle()
- Transpile to and include dist folder (JavaScript 1.6)
- Webpack to and include lib folder (ECMAscript 2015)
- Refactor index.js -> html.es -> src/html.es -> lib/html.js -> dist/util.js
- Refactor util.es -> src/util.es -> lib/util.js -> dist/util.js
- Refactor input.js -> src/html/input.es -> lib/html/input.js -> dist/html/input.js
- Make tests use the lib folder.
- Documentation improvements.
- Class FEATURE: addClass()
- Class FEATURE: Chainable setters
- Class BUG: domPath() must be external since clone doesn't rebuild self-references.
- Class BUG: setAttribute() failed when no previous attributes
- Deepmerge is a runtime dependancy.
- Class syntax (semantic, path, style, build, clone, content, render)
- Remove useless space in html style attribute after property colon.
- BUG: src/obj.es and src/svg.es wasn't transpiled in gradle.build.
- SVG elements
- Handle SVG´s case sensitive viewBox attribute
- Recursive modifyStyleAndMediaToClassAndCss()
- modifyStyleAndMediaToClassAndCss()
- Function to object syntax.
- Obj
- cdata function (https://www.w3.org/TR/REC-xml/#dt-chardata)
- Default units in style attribute when using index.es
- Deprecated objectToCssDeclarations in favour of objToStyleAttr
- Profiling led to sortedUniqStr
- Fix #9 Css order
- Limited support for nested selectors (&:hover div)
- Apply default units to css property values using jss-default-unit.
- You can now pass a function as content. Or an array of functions++.
- html, head and body accessible as property on any node
- Parent and root
- Path -- Makes it possible to access and manipulate the DOM.
- Node.add() -- Makes it possible to manipulate the DOM.
- Nested pseudo styling
- Comment out autoprefixer as it introduced many problems.
- Autoprefixer
- Generate CSS from style too, so !important can be avoided.
- Some Dom fixes
- Dom
- Non Cascading Scaleable Styling
- Dasherize attribute names.
- Style attribute from object. Keep property order. Dasherize property keys.
- Javascript 1.6 Example
- Input types.
- Icon.
- Use as Enonic XP lib too.
- Void elements.
- Handle javascript types in attribute values.
- Basic functionality. KISS!