react-with-mustaches · require("./grammar.js") .parse("hello, world!")...

97

Upload: others

Post on 28-Jun-2020

8 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 2: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

{

Page 3: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

👋

Page 4: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

not to scale

Anton 2007

@zemlanin

Page 5: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

not to scale

Anton 20192007

@zemlanin

Wix

Page 6: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

not to scale

Anton 20192015 2007

@zemlanin

Wix{{

Page 7: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<body> <header> <ul data-nav> <li> … </li> <li> … </li> <li> … </li> </ul> </header> <content> <ul data-products> <li> … </li> <li> … </li> <li> … </li> </ul> </content> <footer> … </footer> </body>

Page 8: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<body> <header> <ul data-nav> <li> … </li> <li> … </li> <li> … </li> </ul> </header> <content> <ul data-products> <li> … </li> <li> … </li> <li> … </li> </ul> </content> <footer> … </footer> </body>

Page 9: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

def render_navigation(): return ( "<div data-nav>" + render("navigation.tmpl") + "</div>" )

import Nav from "navigation.tmpl"

function hydrateNavigation() { for (let n of document.querySelectorAll("[data-nav]")) { ReactDOM.hydrate(<Nav />, n) } }

Page 10: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

def render_navigation(): return ( "<div data-nav>" + render("navigation.tmpl") + "</div>" )

import Nav from "navigation.tmpl"

function hydrateNavigation() { for (let n of document.querySelectorAll("[data-nav]")) { ReactDOM.hydrate(<Nav />, n) } }

"navigation.tmpl"

"navigation.tmpl"

Page 11: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<body> <header> <ul data-nav> <li> … </li> <li> … </li> <li> … </li> </ul> </header> <content> <ul data-products> <li> … </li> <li> … </li> <li> … </li> </ul> </content> <footer> … </footer> </body>

Page 12: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 13: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

anton.click/rm/spec

Page 14: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 15: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

.button { border: none }

.button.red { background-color: red }

import styles from "./styles.css" // => { button: "0bad", red: "c0de" }

Page 16: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<a href={{ href }}>{{ label }}</a>

var React = require("react") module.exports = function (props) { return React.createElement( "a", { href: props.href }, props.label ) }

{{

Page 17: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

{{mustache}} schwartzman <React>

anton.click/rm/src

Page 18: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

1. parsing 2. code generation

Page 19: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

parsing

<a href={{href}}> {{label}} </a>

Page 20: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<b>hello, <i>world</i>!</b>

<b>hello, <i>world</i>!</b>

<b> hello, <i>world</i>! </b>

<i>world</i>hello, !

Page 21: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

regex

Page 22: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

🚫

regex

Page 23: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

anton.click/rm/regex

regex

Page 24: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

custom lexer + parser

Page 25: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

custom lexer + parser

Page 26: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

Parsing Expression Grammar

grammar bitsy

node <- tag / text_node tag <- i_tag / b_tag i_tag <- "<i>" node* "</i>" b_tag <- "<b>" node* "</b>" text_node <- [^<>]+

Page 27: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

ANTLR

Page 28: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

ANTLR

PEGjs

Canopy

Page 29: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

anton.click/rm/canopy

Page 30: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

grammar Schwartzman

root <- node* %strip_whitespaces

Page 31: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

grammar Schwartzman

root <- node* %strip_whitespaces

node <- dom_node / mustache_node / text_node

Page 32: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

grammar Schwartzman

root <- node* %strip_whitespaces

node <- dom_node / mustache_node / text_node

dom_node <- (open_tag node* close_tag) %validate

Page 33: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

grammar Schwartzman

root <- node* %strip_whitespaces

node <- dom_node / mustache_node / text_node

dom_node <- (open_tag node* close_tag) %validate

open_tag <- "<" tag_name space attrs space? ">"

Page 34: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

SyntaxError: Line 1: expected [^<>], "<i>", "<b>", [^<>], "</i>" <b>beaver <i>platypus</b> duck</i> ^

require("./grammar.js") .parse("<b>beaver <i>platypus</b> duck</i>")

Page 35: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

require("./grammar.js") .parse("<b>hello, <i>world</i>!</b>")

TreeNode { text: '<b>hello, <i>world</i>!</b>', offset: 0, elements: [ TreeNode { text: '<b>', offset: 0, elements: [] }, TreeNode { text: 'hello, <i>world</i>!', offset: 3, elements: [ TreeNode { text: 'hello, ', offset: 3, elements: [Array] }, TreeNode { text: '<i>world</i>', offset: 10, elements: [Array] }, TreeNode { text: '!', offset: 22, elements: [Array] } ] }, TreeNode { text: '</b>', offset: 23, elements: [] } ] }

Page 36: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

Антону надо перевести дыхание Держите пёсиков

Page 37: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

code generationvar React = require("react")

module.exports = function (props) { return React.createElement( "a", { href: props.href }, props.label ) }

Page 38: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

function loader(content) { const tree = parse(content) const { code } = compile(tree)

return `var h = require("react").createElement

module.exports = function (props) { return ${code} }

if ( typeof process !== "undefined" && process.env && process.env.NODE_ENV === "test" ) { /* … */ }` }

Page 39: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

return `var h = require("react").createElement

module.exports = function (props) { return ${code} }

if ( typeof process !== "undefined" && process.env && process.env.NODE_ENV === "test" ) { /* … */ }` }

const tree = parse(content) const { code } = compile(tree)

function loader(content) {

Page 40: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

return `var h = require("react").createElement

module.exports = function (props) { return ${code} }

if ( typeof process !== "undefined" && process.env && process.env.NODE_ENV === "test" ) { /* … */ }` }

const tree = parse(content) const { code } = compile(tree)

function loader(content) {

Page 41: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

function compile(node) { const { tag_name, attrs, elements } = node

const children = compileChildren(elements) const compiledAttrs = compileAttrs(attrs)

return { code: `h("${tag_name}", ${compiledAttrs}, ${children})`, // … } }

Page 42: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div> {{ mark }} </div>

React.createElement( "div", null, props.mark )

Page 43: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div> {{& mark }} </div>

React.createElement("div", { dangerouslySetInnerHTML: { __html: props.mark } })

Page 44: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div> <i>{{ john }}:</i> oh, hi {{& mark }} </div>

ehh…

Page 45: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

escape by hand?<div> <i>{{ john }}:</i> oh, hi {{& mark }} </div>

Page 46: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

escape by hand?<div> <i>{{ john }}:</i> oh, hi {{& mark }} </div>

Page 47: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

escape by hand? restrict {{& }}

<div> <i>{{ john }}:</i> oh, hi {{& mark }} </div>

Page 48: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

if ( children.length === 1 && children[0].escaped ) { // add some `danger` } else if ( children.some(c => c.escaped) ) { throw new OneChildPolicy() }

escape by hand? restrict {{& }}

Page 49: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

{{^ invert }} body {{/ invert }}

{{# section }} body {{/ section }}

Page 50: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<b>{{^ great }}Not terrible{{/ great }}</b>

Page 51: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<b>{{^ great }}Not terrible{{/ great }}</b>

React.createElement( "b", null, (!props.great || !props.great.length) ? "Not terrible" : null )

{ great: false } { great: [] }

Page 52: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div>{{# user }}{{ name }}{{/ user }}</div>

Page 53: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div>{{# user }}{{ name }}{{/ user }}</div>

React.createElement( "div", null, props.user ? props.name : null )

{ user: true, name: "Anatoly Dyatlov" }

{{# if }}

Page 54: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div>{{# user }}{{ name }}{{/ user }}</div>

React.createElement( "div", null, props.user ? props.user.name : null )

{ user: { name: "Anatoly Dyatlov" } }

{{# scope }}

Page 55: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

// schwartzman/index.js

return `function scopeSearch(scopes, name) { var path = name.split(".")

for (var i = 0; i < scopes.length; i++) { // … for (var n = 0; n < path.length; n++) { // … } }

return null }`

Page 56: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div>{{# user }}{{ name }}{{/ user }}</div>

React.createElement( "div", null, ...props.user.map(u => u.name) )

{ user: [ { name: "Anatoly" }, {name: "Dyatlov"} ] }

{{# for_each }}

Page 57: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div>{{# user }}{{ name }}{{/ user }}</div>

React.createElement( "div", null, ✨ f(props.user, props, ["user"]) ✨ // => createElement("b", null, "Anatoly Dyatlov") )

{ user: () => (text, cb) => `<b>${cb(text)}</b>`, name: "Anatoly Dyatlov" }

{{# lambda }}

Page 58: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

// schwartzman/index.js

return `function section(/* … */) { var obj = scopeSearch(/* `scope` section */)

if (obj) { if (isArray(obj)) { // `for_each` section } else if (isFunction(obj)) { // `lambda` section } else { // `if` section } } }`

Page 59: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

// `if` section } } }`

} else if (isFunction(obj)) { // `lambda` section } else {

// schwartzman/index.js

return `function section(/* … */) { var obj = scopeSearch(/* `scope` section */)

if (obj) { if (isArray(obj)) { // `for_each` section

Page 60: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

// schwartzman/index.js

return `function render(/* … */) { var lowLevel = require("schwartzman").lowLevel var parsed = lowLevel.parse(/* … */) // … return eval(lowLevel.compile(parsed)) }`

Page 61: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

eval

// schwartzman/index.js

return `function render(/* … */) { var lowLevel = require("schwartzman").lowLevel var parsed = lowLevel.parse(/* … */) // … return eval(lowLevel.compile(parsed)) }`

Page 62: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

eval

// schwartzman/index.js

return `function render(/* … */) { var lowLevel = require("schwartzman").lowLevel var parsed = lowLevel.parse(/* … */) // … return eval(lowLevel.compile(parsed)) }`

:feels

-good:

Page 63: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

{{#users u}} {{ u.name }} {{/users}}

Page 64: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

{{#users u}} {{ u.name }} {{/users}}

🚫

Page 65: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 66: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 67: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

Features-- UX++

Page 68: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

anton.click/rm/demo

Page 69: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 70: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

var h = require("react").createElement

var lowLevel = require("schwartzman").lowLevel

Page 71: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

var h = require("react").createElement

var lowLevel = require("schwartzman").lowLevel

var parseQuery = require("loader-utils").parseQuery

Page 72: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

module.exports = function (content) { … }

if ( typeof process != "undefined" && process.env && process.env.NODE_ENV === "test" ) { … }

var h = ("react").createElement

var lowLevel = ("schwartzman").lowLevel

var parseQuery = ("loader-utils").parseQuery

require

require

require

Page 73: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

window.module = {} window.process = { env: { NODE_ENV: "test" } }

window.require = function (name) { if (name === "loader-utils") { return { parseQuery() { return {} } } }

if (name === "react") { return /* … */ }

if (name === "schwartzman") { return /* … */ } }

Page 74: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<script src="https://unpkg.com/react@16/umd/react.production.min.js"> </script>

<script src="https://unpkg.com/react-dom@16/umd/react-dom-server.browser.production.min.js"> </script>

<script src="https://unpkg.com/schwartzman"></script>

Page 75: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 76: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 77: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

TWO YEARS LATER…

Page 78: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

<div style="color: red;">hello world</div> ^ ^

<!-- vs -->

<div style="color:red">hello world</div> ^ ^

Page 79: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

react@15 => react@16

me: users:

:(:)

Page 80: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

react@15 => (react@15 | react@16)

react@15 => react@16

me: users:

:)

Page 81: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

react@15 => (react@15 | react@16)

react@15 => react@16

me: users:

:)❌

Page 82: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

// test/rendering.test.js

const rendered = semver.gte(React.version, "16.0.0") ? `<div style="color:red">red</div>` : `<div style="color:red;">red</div>`

assert.equal( rendered, ReactDOMServer.renderToStaticMarkup( React.createElement(tmpl, {}) ) )

Page 83: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

// package.json

"peerDependencies": { "react": "^0.14.3 || ^15.6.1 || ^16.0.0" }

Page 84: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

# .travis.yml

env: - REACT_VER=* - REACT_VER=0.14.3 - REACT_VER=15.6.1 - REACT_VER=16.0.0

Page 85: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

# .travis.yml

env: - REACT_VER=* - REACT_VER=0.14.3 - REACT_VER=15.6.1 - REACT_VER=16.0.0

install: - npm ci - npm i --no-save "react@$REACT_VER" "react-dom@$REACT_VER"

Page 86: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 87: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

not to scale

20192018 2015

schwartzman

Page 88: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

not to scale

20192015

experience

schwartzman

2018

Page 89: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 90: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 91: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 92: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

window.module = {}

window.require = function (name) { if (name === "loader-utils") { return { parseQuery() { return {} } } }

if (name === "react") { return /* … */ }

if (name === "schwartzman") { return /* … */ } }

Page 94: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

// test/rendering.test.js

const rendered = semver.gte(React.version, "16.0.0") ? `<div style="color:red">red</div>` : `<div style="color:red;">red</div>`

assert.equal( rendered, ReactDOMServer.renderToStaticMarkup( React.createElement(tmpl, {}) ) )

Page 95: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',
Page 96: react-with-mustaches · require("./grammar.js") .parse("hello, world!") TreeNode { text: 'hello, world!',

) thank you