Skip to content

jonaswalden/oj

Repository files navigation

OJ

Server side rendering of JSX / React like elements and pure functional components into HTML.

const oj = require("oj");

oj.render.toString(
  <Document title="JSX with OJ">
    <h1>JSX with OJ</h1>
    <p>In Node</p>
  </Document>
);

function Document(props) {
  return <>
    {"<!doctype html>"}
    <title>{props.title}</title>
    {props.children}
  </>;
}

API

React parts

Only a small portion of the React API needed for templating is implemented.

  • createElement
  • Fragment
  • createContext
  • useContext

Rendering

Render an element and its children.

renderFunction(element, context = {})
  • element - Result of createElement
  • context - A mutable object passed into all functional components

See notes on Simple contexts further down

render.toString

Returns a promise resolving with an HTML string

render.toStream

Returns a readable stream of HTML element parts and content

render.toObjectStream

Returns a readable stream of objects describing HTML element parts and content

  • { name, attrs } - An open tag
  • { name } - A close tag
  • { text } - A text node

Transforming

Transform contents of a rendering stream

renderingStream.pipe(transformFunction())

transform.fromObjectStream

Returns a transform stream transforming objects from toObjectStream into HTML element parts and content

Setup

Compile JSX templates with Babel and a React setup. Use the following configuration for the transform react jsx plugin:

{
  "pragma": "oj.createElement",
  "pragmaFrag": "oj.Fragment"
}

Example

npm install oj @babel/core @babel/register @babel/preset-react eslint eslint-plugin-react
require("./setupViews.js");

const oj = require("oj");
const Template = require("./template.jsx");

const element = oj.createElement(Template, {props: "here"});
const html = await oj.render.toString(element, {context: "here"});
// setupViews.js

require("@babel/register")({
  extensions: [".jsx"],
  presets: [
    ["@babel/preset-react", {
      pragma: "oj.createElement",
      pragmaFrag: "oj.Fragment"
    }]
  ]
});
// template.jsx

const oj = require("oj");

module.exports = function Template(props) {
  return <h1>{props.title}</h1>;
};
// eslintrc.js
{
  extends: ["plugin:react/recommended"],  
  settings: {
    react: {
      pragma: "oj"
    }
  },
  rules: {
    "react/prop-types": "off",
    "react/jsx-key": "off",
    "react/no-unknown-property": "off"
  }
}

Differences from React

Apart from very limited support, see API, there some differences.

Markup is closer to HTML

React DOM uses camelCase property naming convention instead of HTML attribute names. OJ doesn't. Attributes are output as authored.

Input / output

<p class="preamble">...</p>
<label for="search-field">Search</label>
<input id="search-field" type="search" data-attr="value" />
<div className="no-effect">className is not an attribute</div>

For Eslint you might want to turn off the rule react/no-unknown-property

No keys needed

When rendering lists React uses the key attribute is used for efficient diffing when updating a component. OJ only renders markup server side, so there is no need for this.

For Eslint you might want to turn off the rule react/jsx-key

No typechecking with PropTypes

None whatsoever.

For Eslint you might want to turn off the rule react/prop-types

Output is not escaped

Currently there are no escaping mechanisms for anything. Content is output as authored.

Input

<>
  {"<!doctype html>"}
  <div>
    {"<p>html content</p>"}
  </div>
</>

Output

<!doctype html>
<div>
  <p>html content</p>
</div>

*Whitespace for readability

Simple contexts

All functional components are passed an additional argument, apart from props, context. It is a mutable object. Initial value is derived from the second argument of any rendering function.

Input

const oj = require("oj");

oj.render.toString(<Recursive />, { prop: 1 });

function Recursive(props, context) {
  if (context.props > 3) return;

  return <>
    <i>{context.props++}</i>
    <Recursive />
  </>;
}

Output

<i>1</i><i>2</i><i>3</i>

React style contexts

Although the syntax is the same the behavior is different.

React rendering is synchronous. OJ rendering is asynchronous.

In React a context consumer will retrieve the value from the closest provider in the document tree. In OJ contexts are unaware of the document tree. A provider will push its value into a list, render its children then pop the list. A consumer will get the latest value available.

This means contexts existing independent of the rendering cycle could share its values with providers from parallel cycles.

Workaround

Attach contexts to the rendering cycle context object. It is passed to all functional components.

const oj = require("oj");

function ParentComponent(props, context) {
  const SomeContext = oj.createContext();
  context.SomeContext = SomeContext;
  return <>
    <SomeContext.Provider value={1}>
      <DeepComponent />
    </SomeContext.Provider>
  </>;
}

function DeepComponent(props, context) {
  const value = oj.useContext(context.SomeContext);
  return <div>{value}</div>
}

Similar projects

Probably a couple?

  • vhtml - Renders markup inside out which makes contexts and avoiding unwanted escaping impossible(?)

About

OJ (Oscar, Jonas templating language) - Templates built on top of JSX

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors