react-jade

A compiler for jade that produces a react component

slides.forbesl.co.uk

Jade Introduction

Templating Language

  • Produces HTML
  • Supports dynamic code

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Jade</title>
  </head>
  <body>
    <h1>Jade - node template engine</h1>
    <article>
      <p>Jade is a terse and simple
         templating language with a
         strong focus on performance
         and powerful features.</p>
    </article>
  </body>
</html>

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Jade
  <body>
    <h1>Jade - node template engine
    <article>
      <p>Jade is a terse and simple
         templating language with a
         strong focus on performance
         and powerful features.

JADE

doctype html
html(lang="en")
  head
    title Jade
  body
    h1 Jade - node template engine
    article
      p.
        Jade is a terse and simple
        templating language with a
        strong focus on performance
        and powerful features.

Buffering Output

= jsVariable
!= jsVariable
var fn = jade.compile(template);
var html = fn({jsVariable: '<foo>'});
&lt;foo&gt;
<foo>

Buffering Output

function template(locals) {
  var buf = [], jade_interp;
  var locals_ = locals || {}, jsVariable = locals_.jsVariable;
  buf.push(jade.escape(null == (jade_interp = jsVariable) ?
                       "" : jade_interp)
        + (null == (jade_interp = jsVariable) ? "" : jade_interp));
  return buf.join("");
}

Dynamic Attributes

a(href="mailto:" + email)= email
var fn = jade.compile(template);
var html = fn({email:'[email protected]'});
<a href="mailto:[email protected]">[email protected]</a>

Loops and Conditionals

ul
  each value in list
    li= value
if awesome
  p You are awesome
else
  p You could use improvement
var fn = jade.compile(template)
var html = fn({list: [1, 2], awesome: true});
<ul>
  <li>1</li>
  <li>2</li>
</ul>
<p>You are awesome</p>

Custom JavaScript

ul
  - list.forEach(function (value) {
    li= value
  - });
var fn = jade.compile(template);
var html = fn({list: [1, 2]});
<ul>
  <li>1</li>
  <li>2</li>
</ul>

Includes

//- root.jade
article
  include ./child.jade
//- child.jade
h2 An Article
p Blah Blah Blah
var fn = jade.compileFile('root.jade');
var html = fn();
<article>
  <h2>An Article</h2>
  <p>Blah Blah Blah</p>
</article>

React Jade

react-jade

npm install react-jade --save
var React = require('react');
var jade = require('react-jade');
var fn = jade.compileFile(__dirname + '/view.jade');
var html = React.renderComponentToString(fn({
  articleText: 'Hello World'
}));

view.jade

h1 Jade - node template engine
article
  p= articleText

output

<div data-reactid=".4j4gsqknwg" data-react-checksum="1164730533">
  <h1 data-reactid=".4j4gsqknwg.0">
    <span data-reactid=".4j4gsqknwg.0.0">
      Jade - node template engine
    </span>
  </h1>
  <article data-reactid=".4j4gsqknwg.1">
    <p data-reactid=".4j4gsqknwg.1.0">
      <span data-reactid=".4j4gsqknwg.1.0.0">
        Hello World
      </span>
    </p>
  </article>
</div>

compileFileClient

var jade = require('react-jade');
var js = jade.compileFileClient(__dirname + '/view.jade');
(function (React) {
  var jade_globals_articleText=typeof articleText === "undefined" ?
    undefined : articleText;
  return function (locals) {
    var articleText = "articleText" in locals ?
      locals.articleText : jade_globals_articleText;
    return React.DOM.div({}, [
      React.DOM.h1({}, ["Jade - node template engine"]),
      React.DOM.article({}, [React.DOM.p({}, [articleText])])
    ]);
  };
}(typeof React !== "undefined" ? React : require("react")))

Browserify

server.js

var browserify = require('browserify-middleware');
app.get('/client.js', browserify(__dirname + '/client.js', {
  transform: [require('react-jade')]
});

client.js

var React = require('react');
var jade = require('react-jade');
var fn = jade.compileFile(__dirname + '/view.jade');

var html = React.renderComponent(fn({
  articleText: 'Hello World'
}), document.getElementById('container'));

Browserify

var React = require('react');
var fn = (function (React) {
  var jade_globals_articleText=typeof articleText === "undefined" ?
    undefined : articleText;
  return function (locals) {
    var articleText = "articleText" in locals ?
      locals.articleText : jade_globals_articleText;
    return React.DOM.div({}, [
      React.DOM.h1({}, ["Jade - node template engine"]),
      React.DOM.article({}, [React.DOM.p({}, [articleText])])
    ]);
  };
}(typeof React !== "undefined" ? React : require("react")));

var html = React.renderComponent(fn({
  articleText: 'Hello World'
}), document.getElementById('container'));

How it works

Lexer & Parser

var Parser = require('jade/lib/parser.js');
var jade = require('jade/lib/runtime.js');
var parser = new Parser(str, options.filename, options);
var ast = parser.parse();

Compiler

Compiler.prototype.visitTag = function (tag) {
  this.buf.push('tags.push(React.DOM.' + tag.name + '(');
  this.buf.push(getAttributes(tag.attrs));
  if (tag.block && tag.block.nodes.length) {
    this.buf.push(', (function () { var tags = [];');
    this.visit(tag.block);
    this.buf.push('return tags;}())');
  }
  this.buf.push('))');
};
function getAttributes(attrs){
  var buf = [];
  attrs.forEach(function(attr){
    buf.push(JSON.stringify(attr.name) + ': ' + attr.val);
  });
  return '{' + buf.join(',') + '}';
}

Compressor

Compressor.prototype.after = function(node) {
  var isSelfCallingFnWithJustReturn = isSelfCallingFunction(node) &&
    node.args.length === 0 &&
    node.expression.body.length > 0 &&
    node.expression.body[0].TYPE === 'Return';

  if (isSelfCallingFnWithJustReturn) {
    return node.expression.body[0].value;
  }

  if (node.TYPE === 'Function') {
    return collapseToReturnStatement(node);
  }
};

Compressor - getReturnStatement

function collapseToReturnStatement(node) {
  var declaration = node.body[0];
  var pushStatements = node.body.slice(1, node.body.length - 1);
  var returnStatement = node.body[node.body.length - 1];
  if (!isArrayVariableDefinition(declaration) return;
  var name = declaration.definitions[0].name.name;
  var array = declaration.definitions[0].value;
  var isCollapsible = pushStatements.every(isArrayPush(name)) &&
                      isReturn(returnStatement, name);
  if (!isCollapsible) return;
  returnStatement.value = array;
  array.elements = pushStatements.reduce(function (elements, push) {
    elements.concat(push.body.args);
  }, array.elements);
  node.body = [returnStatement];
}

Roadmap

github.com/ForbesLindesay/react-jade

  • mixins
  • attribute extension/merging (via &attributes)
  • case/when
  • using each to iterate over keys of an object (rather than over items in an array)
  • interpolation
  • attribute interpollation
  • special handling of data-attributes
  • outputting unescaped html results in an extra wrapper div and doesn't work for attributes

Forbes Lindesay

slides.forbesl.co.uk

Social Networks

Twitter: @ForbesLindesay

GitHub: @ForbesLindesay

Blog: www.forbeslindesay.co.uk

Open Source

Jade

Browserify Middleware

readable-email.org

brcdn.org

tempjs.org