2016 stop writing javascript frameworks by joe gregorio

Post on 12-Apr-2017

59 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Stop Writing Javascript Frameworks

Joe Gregorio@bitworking

bit.ly/zf-preso-2

● I am employed by Google

● This talk does not represent the opinion of Google

Caveats

● Code is for exposition purposes only.

Caveats

● Those expecting magic and/or cleverness.

Apologies in Advance

● Open Source https://skia.org● 2D Graphics Library

○ CPU○ GPU○ PDF

About Skia

● Chrome● Android● Firefox● Flutter.io● Sublime Text 3● ...

About Skia

● Hidden Gems○ PathOps

https://skia.org/dev/present/pathops ○ SkNx https://skia.org/dev/contrib/simd

About Skia

● Infrastructure○ Run the Tests○ Tools for Analysis

■ 500,000 Images/Commit■ 260,000 Perf Metrics/Commit

About Me

● Go● Polymer● Google Compute Engine● Open Source

○ https://skia.googlesource.com/buildbot/

About Skia Infra

● Build fast● Iterate quickly● Maintain robustly● Do things at scale

Context

Stop Writing Javascript Frameworks

http://bitworking.org/news/2014/05/zero_framework_manifesto

using hyperbole

https://xkcd.com/386/

Slightly Hyperbolic Title

Things I didn't say

● Stop innovating.

● Stop using Javascript libraries.

● Stop using tools.

● I have all the answers.

Alternate Accurate Titles

● Stop Using Javascript Frameworks

● Start writing reusable orthogonally

composable units of HTML+CSS+JS

Stop Writing Using Javascript Frameworks

Joe Gregorio@bitworking

What do I mean by "framework"?

gist -> library -> framework

gist -> library -> framework

Trouble starts here

● HTML

● CSS

● JS

● HTML

● CSS

● JS

GWT

● HTML

● CSS

● JS

There was a time for frameworks

Remember the IE box model?

Remember the IE event model?

Remember the Netscape layer tag?

<LAYER ID=layer1 TOP=20pt LEFT=5pt BGCOLOR="#CC00EE" WIDTH=200> <H1>Layer 1</H1> <P>Lots of content for this layer.</P> <IMG SRC=violets.jpg align=right> <P>Content for layer 1.</P> <P>More Content for layer 1.</P></LAYER>

http://web.archive.org/web/20040929103224/http://developer.netscape.com/docs/manuals/communicator/dynhtml/index.htm

There was no consistent model for interacting with the browser.

The situation has improved

http://caniuse.com/#feat=svg

So that weakens one reason to use a framework.

Polyfills

The "import __future__" for the web.

Framework

Polyfills

Fill in the potholes.

But what about all the other benefits a framework brings?

Abstractions are Leaky

Abstractions are Abstract

Reuse

Busy porting my Dojo widgets to Backbone.

Angular 1.0 -> 2.0

Framework

Polyfills

Fill in the potholes.

Code organization

?

Framework

Polyfills Web Components

Fill in the potholes.

Code organization

Web Components

● HTML Templates● HTML Imports● Custom Elements● Shadow DOM

Web Components

● HTML Templates● HTML Imports● Custom Elements● Shadow DOM

HTML Templates

● Now part of HTML spec

HTML Templates

HTML Templates

● <template>...</template>● Content is parsed by browser.● Content is not rendered (invisible to user).● Content is hidden (invisible to

querySelector).● Content is inert. (JS doesn't run)

<template id=t> <div>Hello World!</div></template>

<script> var node = document.importNode( document.querySelector('#t').content, true); document.body.appendChild(node);</script>

<template id=t> <div>Hello World!</div></template>

<script> var node = document.importNode( document.querySelector('#t').content, true); document.body.appendChild(node);</script>

<template id=t> <div>Hello World!</div></template>

<script> var node = document.importNode( document.querySelector('#t').content, true); document.body.appendChild(node);</script>

<template id=t> <div>Hello World!</div></template>

<script> var node = document.importNode( document.querySelector('#t').content, true); document.body.appendChild(node);</script>

<template id=t> <div>Hello World!</div></template>

<script> var node = document.importNode( document.querySelector('#t').content, true); document.body.appendChild(node);</script>

bit.ly/zf-template

HTML Imports● <link rel="import" href="">

● Oddly slightly contentious.

● Brilliant for code organization, keep HTML, CSS, and JS together in one file.

HTML Imports● Are de-dup'd

● Can contain anything an HTML file can contain (HTML, CSS, JS)

● Scripts are executed in the context of the window that imported them

<template id=t> <div> Hello World! </div></template>

<script> (function() { var doc = document.currentScript.ownerDocument; var node = document.importNode( doc.querySelector( '#t').content, true); document.body.appendChild(node); })();</script>

import.html

<template id=t> <div> Hello World! </div></template>

<script> (function() { var doc = document.currentScript.ownerDocument; var node = document.importNode( doc.querySelector('#t').content, true); document.body.appendChild(node); })();</script>

import.html

<template id=t> <div> Hello World! </div></template>

<script> (function() { var doc = document.currentScript.ownerDocument; var node = document.importNode( doc.querySelector('#t').content, true); document.body.appendChild(node); })();</script>

import.html

<template id=t> <div> Hello World! </div></template>

<script> (function() { var doc = document.currentScript.ownerDocument; var node = document.importNode( doc.querySelector('#t').content, true); document.body.appendChild(node); })();</script>

import.html

<template id=t> <div> Hello World! </div></template>

<script> (function() { var doc = document.currentScript.ownerDocument; var node = document.importNode( doc.querySelector('#t').content, true); document.body.appendChild(node); })();</script>

import.html

<html> <head> <script src="webcomponents.js"></script> <link rel="import" href="import.html"> </head> <body> </body></html>

bit.ly/zf-html-imports-tmpl

index.html

<html> <head> <script src="webcomponents.js"></script> <link rel="import" href="import.html"> </head> <body> </body></html>

bit.ly/zf-html-imports-tmpl

index.html

http://webcomponents.org

<html> <head> <script src="webcomponents-lite.js"></script> <link rel="import" href="import.html"> </head> <body> </body></html>

bit.ly/zf-html-imports-tmpl

index.html

<html> <head> <script src="webcomponents.js"></script> <link rel="import" href="import.html"> </head> <body> </body></html>

bit.ly/zf-html-imports-tmpl

index.html

Context = function() { this.doc = (document.currentScript||document._currentScript).ownerDocument;};

Context.prototype. import = function(id) { return document.importNode(this.doc.querySelector( '#'+id).content, true);};

util.js

<template id=t> <div class=greeting> Hello World! </div></template>

<script> (function() { var ctx = new Context(); document.body.appendChild(ctx. import('t')); })();</script>

bit.ly/zf-import-context

import.html

Custom Elements

● Oddly uncontentious

● Only talking about creating new elements, not subclassing existing elements.

var proto = Object.create(HTMLElement.prototype);

proto.createdCallback = function() { this.textContent = 'Hello World!';};

proto.addMoreText = function(s) { this.textContent = this.textContent + s;};

document.registerElement('hello-element', { prototype: proto});

var proto = Object.create(HTMLElement.prototype);

proto.createdCallback = function() { this.textContent = 'Hello World!';};

proto.addMoreText = function(s) { this.textContent = this.textContent + s;};

document.registerElement('hello-element', { prototype: proto});

var proto = Object.create(HTMLElement.prototype);

proto.createdCallback = function() { this.textContent = 'Hello World!';};

proto.addMoreText = function(s) { this.textContent = this.textContent + s;};

document.registerElement('hello-element', { prototype: proto});

Custom Element Lifecycle Callbacks

createdCallback

attachedCallback

detachedCallback

attributeChangedCallback

var proto = Object.create(HTMLElement.prototype);

proto.createdCallback = function() { this.textContent = 'Hello World!';};

proto.addMoreText = function(s) { this.textContent = this.textContent + s;};

document.registerElement('hello-element', { prototype: proto});

var proto = Object.create(HTMLElement.prototype);

proto.createdCallback = function() { this.textContent = 'Hello World!';};

proto.addMoreText = function(s) { this.textContent = this.textContent + s;};

document.registerElement('hello-element', { prototype: proto});

<html><body>

<hello-element></hello-element></body></html>

bit.ly/zf-custom-element

Context = function() { this.doc = (document.currentScript||document._currentScript).ownerDocument;};

Context.prototype. import = function(id) { return document.importNode(this.doc.querySelector( '#'+id).content, true);};

function newElement(name, proto) { var ep = Object.create(HTMLElement.prototype); Object.keys(proto).forEach( function(key) { ep[key] = proto[key]; }); document.registerElement(name, {prototype : ep});}

util.js

Context = function() { this.doc = (document.currentScript||document._currentScript).ownerDocument;};

Context.prototype. import = function(id) { return document.importNode( this.doc.querySelector('#'+id).content, true);};

function newElement(name, proto) { var ep = Object.create(HTMLElement.prototype); Object.keys(proto).forEach(function(key) { ep[key] = proto[key]; }); document.registerElement(name, {prototype: ep});}

util.js

newElement('hello-element', {

createdCallback: function() { this.textContent = 'Hello World!'; },

addMoreText: function(s) { this.textContent = this.textContent + s; },});

bit.ly/zf-newelement

Putting it all together

A motivating example

<template id=t> <button>Hide</button></template>

<script> (function() { var ctx = new Context();

newElement('toggle-next', { createdCallback: function() { this.appendChild(ctx.import('t')); this.button = this.querySelector('button'); this.button.addEventListener('click', this._buttonClick.bind(this)); },

_buttonClick: function() { var current = this.nextElementSibling.style.visibility; if (current == 'collapse') { this.nextElementSibling.style.visibility = 'visible'; this.button.innerText = 'Hide'; } else { this.nextElementSibling.style.visibility = 'collapse'; this.button.innerText = 'Show'; } } }) })();</script>

<template id=t> <button>Hide</button></template>

<script> (function() { var ctx = new Context();

newElement('toggle-next', { createdCallback: function() { this.appendChild(ctx.import('t')); this.button = this.querySelector('button'); this.button.addEventListener('click', this._buttonClick.bind(this)); },

_buttonClick: function() { var current = this.nextElementSibling.style.visibility; if (current == 'collapse') { this.nextElementSibling.style.visibility = 'visible'; this.button.innerText = 'Hide'; } else { this.nextElementSibling.style.visibility = 'collapse'; this.button.innerText = 'Show'; } } }) })();</script>

<template id=t> <button>Hide</button></template>

<script> (function() { var ctx = new Context();

newElement('toggle-next', { createdCallback: function() { this.appendChild(ctx.import('t')); this.button = this.querySelector('button'); this.button.addEventListener('click', this._buttonClick.bind(this)); },

_buttonClick: function() { var current = this.nextElementSibling.style.visibility; if (current == 'collapse') { this.nextElementSibling.style.visibility = 'visible'; this.button.innerText = 'Hide'; } else { this.nextElementSibling.style.visibility = 'collapse'; this.button.innerText = 'Show'; } } }) })();</script>

<template id=t> <button>Hide</button></template>

<script> (function() { var ctx = new Context();

newElement('toggle-next', { createdCallback: function() { this.appendChild(ctx.import('t')); this.button = this.querySelector('button'); this.button.addEventListener('click', this._buttonClick.bind(this)); },

_buttonClick: function() { var current = this.nextElementSibling.style.visibility; if (current == 'collapse') { this.nextElementSibling.style.visibility = 'visible'; this.button.innerText = 'Hide'; } else { this.nextElementSibling.style.visibility = 'collapse'; this.button.innerText = 'Show'; } } }) })();</script>

<template id=t> <button>Hide</button></template>

<script> (function() { var ctx = new Context();

newElement('toggle-next', { createdCallback: function() { this.appendChild(ctx.import('t')); this.button = this.querySelector('button'); this.button.addEventListener('click', this._buttonClick.bind(this)); },

_buttonClick: function() { var current = this.nextElementSibling.style.visibility; if (current == 'collapse') { this.nextElementSibling.style.visibility = 'visible'; this.button.innerText = 'Hide'; } else { this.nextElementSibling.style.visibility = 'collapse'; this.button.innerText = 'Show'; } } }) })();</script>

<template id=t> <button>Hide</button></template>

<script> (function() { var ctx = new Context();

NewElement('toggle-next', { createdCallback: function() { this.appendChild(ctx.import('t')); this.button = this.querySelector('button'); this.button.addEventListener('click', this._buttonClick.bind(this)); },

_buttonClick: function() { var current = this.nextElementSibling.style.visibility; if (current == 'collapse') { this.nextElementSibling.style.visibility = 'visible'; this.button.innerText = 'Hide'; } else { this.nextElementSibling.style.visibility = 'collapse'; this.button.innerText = 'Show'; } } }) })();</script>

<template id=t> <button>Hide</button></template>

<script> (function() { var ctx = new Context();

newElement('toggle-next', { createdCallback: function() { this.appendChild(ctx.import('t')); this.button = this.querySelector('button'); this.button.addEventListener('click', this._buttonClick.bind(this)); },

_buttonClick: function() { var current = this.nextElementSibling.style.visibility; if (current == 'collapse') { this.nextElementSibling.style.visibility = 'visible'; this.button.innerText = 'Hide'; } else { this.nextElementSibling.style.visibility = 'collapse'; this.button.innerText = 'Show'; } } }) })();</script>

<!DOCTYPE html><html>

<head> <script src="webcomponents.js"></script> <script src="util.js"></script> <link rel="import" href="toggle.html"></head>

<body> <toggle-next></toggle-next> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi dignissim, nisi non rhoncus condimentum, felis ante porta eros, ut tincidunt odio nisl a libero. </p></body>

</html>

<!DOCTYPE html><html>

<head> <script src="webcomponents.js"></script> <script src="util.js"></script> <link rel="import" href="toggle.html"></head>

<body> <toggle-next></toggle-next> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi dignissim, nisi non rhoncus condimentum, felis ante porta eros, ut tincidunt odio nisl a libero. </p></body>

</html>

<!DOCTYPE html><html>

<head> <script src="webcomponents.js"></script> <script src="util.js"></script> <link rel="import" href="toggle.html"></head>

<body> <toggle-next></toggle-next> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi dignissim, nisi non rhoncus condimentum, felis ante porta eros, ut tincidunt odio nisl a libero. </p></body>

</html>

<!DOCTYPE html><html>

<head> <script src="webcomponents.js"></script> <script src="util.js"></script> <link rel="import" href="toggle.html"></head>

<body> <toggle-next></toggle-next> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi dignissim, nisi non rhoncus condimentum, felis ante porta eros, ut tincidunt odio nisl a libero. </p></body>

</html>

<!DOCTYPE html><html>

<head> <script src="webcomponents.js"></script> <script src="util.js"></script> <link rel="import" href="toggle.html"></head>

<body> <toggle-next></toggle-next> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi dignissim, nisi non rhoncus condimentum, felis ante porta eros, ut tincidunt odio nisl a libero. </p></body>

</html>

bit.ly/zf-toggle

So how did this work?

Adjacency in the DOM tree.

Framework

Polyfills Web Components

Fill in the potholes.

Code organization

Organizing around the surfaces already present in the browser.

● Attributes

● Events

● DOM Structure

<button id=submit>Submit</button>

document.querySelector('#submit').id

document.querySelector('#submit').click()

Attributes

<button id=submit>Submit</button>

document.querySelector('#submit').id

document.querySelector('#submit').click()

Attributes

<button id=submit>Submit</button>

document.querySelector('#submit').id

document.querySelector('#submit').click()

Attributes

<button id=submit>Submit</button>

document.querySelector('#submit').id

document.querySelector('#submit').click()

Attributes

ele.addEventListener('some-event', function(e) { /* */ });

var e = new CustomEvent('some-event', {details: details});this.dispatchEvent(e);

Events

ele.addEventListener('some-event', function(e) { /* */ });

var e = new CustomEvent('some-event', {details: details});this.dispatchEvent(e);

Events

ele.addEventListener('some-event', function(e) { /* */ });

var e = new CustomEvent('some-event', {details: details});this.dispatchEvent(e);

Events

<button> <img src=""></button>

<div> <p class=foo></p> <p class=foo></p> <p class=foo></p> <p class=bar></p></div>

DOM Structure

<button> <img src=""> Parent/Child</button>

<div> <p class=foo></p> <p class=foo></p> <p class=foo></p> <p class=bar></p></div>

DOM Structure

<button> <img src=""></button>

<div> <p class=foo></p> <p class=foo></p> CSS Selectors <p class=foo></p> querySelector('.foo') <p class=bar></p></div>

DOM Structure

A more detailed example

<sort-stuff data-target="#sortable"> <button data-key=pop>Sort on Population</button> <button data-key=density>Sort on Density</button> <button data-key=change>Sort on % Change</button></sort-stuff>

<ul id=sortable> <li data-pop=8.4 data-density=27000 data-change=3.86>New York</li> <li data-pop=3.9 data-density=8092 data-change=3.59>Los Angeles</li> <li data-pop=2.7 data-density=11842 data-change=0.99>Chicago</li> <li data-pop=2.2 data-density=3501 data-change=6.63>Houston</li> <li data-pop=1.5 data-density=11379 data-change=2.25>Philadelphia</li></ul>

<sort-stuff data-target="#sortable"> <button data-key=pop>Sort on Population</button> <button data-key=density>Sort on Density</button> <button data-key=change>Sort on % Change</button></sort-stuff>

<ul id=sortable> <li data-pop=8.4 data-density=27000 data-change=3.86>New York</li> <li data-pop=3.9 data-density=8092 data-change=3.59>Los Angeles</li> <li data-pop=2.7 data-density=11842 data-change=0.99>Chicago</li> <li data-pop=2.2 data-density=3501 data-change=6.63>Houston</li> <li data-pop=1.5 data-density=11379 data-change=2.25>Philadelphia</li></ul>

<sort-stuff data-target="#sortable"> <button data-key=pop>Sort on Population</button> <button data-key=density>Sort on Density</button> <button data-key=change>Sort on % Change</button></sort-stuff>

<ul id=sortable> <li data-pop=8.4 data-density=27000 data-change=3.86>New York</li> <li data-pop=3.9 data-density=8092 data-change=3.59>Los Angeles</li> <li data-pop=2.7 data-density=11842 data-change=0.99>Chicago</li> <li data-pop=2.2 data-density=3501 data-change=6.63>Houston</li> <li data-pop=1.5 data-density=11379 data-change=2.25>Philadelphia</li></ul>

<sort-stuff data-target="#sortable"> <button data-key=pop>Sort on Population</button> <button data-key=density>Sort on Density</button> <button data-key=change>Sort on % Change</button></sort-stuff>

<ul id=sortable> <li data-pop=8.4 data-density=27000 data-change=3.86>New York</li> <li data-pop=3.9 data-density=8092 data-change=3.59>Los Angeles</li> <li data-pop=2.7 data-density=11842 data-change=0.99>Chicago</li> <li data-pop=2.2 data-density=3501 data-change=6.63>Houston</li> <li data-pop=1.5 data-density=11379 data-change=2.25>Philadelphia</li></ul>

<sort-stuff data-target="#sortable"> <button data-key=pop>Sort on Population</button> <button data-key=density>Sort on Density</button> <button data-key=change>Sort on % Change</button></sort-stuff>

<ul id=sortable> <li data-pop=8.4 data-density=27000 data-change=3.86>New York</li> <li data-pop=3.9 data-density=8092 data-change=3.59>Los Angeles</li> <li data-pop=2.7 data-density=11842 data-change=0.99>Chicago</li> <li data-pop=2.2 data-density=3501 data-change=6.63>Houston</li> <li data-pop=1.5 data-density=11379 data-change=2.25>Philadelphia</li></ul>

newElement('sort-stuff', { createdCallback : function() { this.addEventListener( 'click', this._clickHandler.bind( this)); },

_clickHandler : function(e) { ... }, });

newElement('sort-stuff', { createdCallback : function() { this.addEventListener( 'click', this._clickHandler.bind( this)); },

_clickHandler: function(e) { ... }, });

_clickHandler: function(e) { var key = e.target.dataset.key; if (!key) { return; } var target = document.querySelector(this.dataset.target); var elements = []; var children = target.children; for (var i=0; i<children.length; i++) { var ele = children[i]; elements.push({ value: +ele.dataset[key], node: ele, }); } elements.sort(function(x, y) { return (x.value == y.value ? 0 : (x.value < y.value ? 1 : -1)); }); elements.forEach(function(i) { target.appendChild(i.node); }); },

_clickHandler: function(e) { var key = e.target.dataset.key; if (!key) { return; } var target = document.querySelector(this.dataset.target); var elements = []; var children = target.children; for (var i=0; i<children.length; i++) { var ele = children[i]; elements.push({ value: +ele.dataset[key], node: ele, }); } elements.sort(function(x, y) { return (x.value == y.value ? 0 : (x.value < y.value ? 1 : -1)); }); elements.forEach(function(i) { target.appendChild(i.node); }); },

_clickHandler: function(e) { var key = e.target.dataset.key; if (!key) { return; } var target = document.querySelector(this.dataset.target); var elements = []; var children = target.children; for (var i=0; i<children.length; i++) { var ele = children[i]; elements.push({ value: +ele.dataset[key], node: ele, }); } elements.sort(function(x, y) { return (x.value == y.value ? 0 : (x.value < y.value ? 1 : -1)); }); elements.forEach(function(i) { target.appendChild(i.node); }); },

_clickHandler: function(e) { var key = e.target.dataset.key; if (!key) { return; } var target = document.querySelector(this.dataset.target); var elements = []; var children = target.children; for (var i=0; i<children.length; i++) { var ele = children[i]; elements.push({ value: +ele.dataset[key], node: ele, }); } elements.sort(function(x, y) { return (x.value == y.value ? 0 : (x.value < y.value ? 1 : -1)); }); elements.forEach(function(i) { target.appendChild(i.node); }); },

● sort-stuff has 'data-target' [Attribute] that selects [DOM Structure] the element

to sort.

● The target children [DOM Structure] have data attributes [Attribute] that

elements are sorted on.

● sort-stuff registers for a 'click' [Events] from its children [DOM Structure].

● sort-stuff children [DOM Structure] have data attributes [Attribute] that

determine how the target children will be sorted.

Surfaces Used

● sort-stuff doesn't care what its children are, only that they generate 'click'

events and the 'click' generating elements have the 'data-key' attributes.

● sort-stuff doesn't care what the elements found at data-target are, only that it

has children that have data-"key" attributes.

Only Surfaces Are Used

● Change the children of <sort-stuff>

● Change the children of sort-stuff.data-target.

Only Surfaces Are Used

<sort-stuff data-target="#sortable"> <p><input type=radio name=sort data-key=pop /> Population</p> <p><input type=radio name=sort data-key=density /> Density</p> <p><input type=radio name=sort data-key=change /> % Change</p> <sort-stuff>

<table> <tbody id=sortable> <tr data-pop=8.4 data-density=27000 data-change=3.86><td>New York</td></tr> <tr data-pop=3.9 data-density=8092 data-change=3.59><td>Los Angeles</td></tr> <tr data-pop=2.7 data-density=11842 data-change=0.99><td>Chicago</td></tr> <tr data-pop=2.2 data-density=3501 data-change=6.63><td>Houston</td></tr> <tr data-pop=1.5 data-density=11379 data-change=2.25><td>Philadelphia</td></tr> </tbody> </table>

Roll in toggle-next

Framework

Polyfills Web Components

Packaging: HTML ImportsOrganization:

EventsAttributesDOM Structure

Summary

Start writing reusable orthogonally composable units of HTML+CSS+JS

http://bit.ly/100-line-template

Data Binding

<template id=t> <div> <p><a href="{{ url }}">{{ foo.bar.baz }} {{ quux }}</a>!</p> <!-- Loop over arrays. Use data attributes of data-repeat-[name]="{{ x.y.z }}" and reference the values iterated over using {{ [name] }}. For arrays the 'i' state variable is also set. --> <ul data-repeat-num="{{ list }}"> <li>{{ num }} {{ i }}</li> </ul> <!-- Loop over Objects. For objects the 'key' state variable is also set. --> <ul data-repeat-o="{{ anobj }}"> <li>{{ key }}={{ o.name }}</li> </ul> </div> </template>

var clone = ctx.import('t');

Expand(clone, data);

document.body.appendChild(clone);

Extra Benefits

● Enabling mix and match (our use of D3.js).

● Better than native. (support and styling).○ Ever tried to style a checkbox?○ details/summary

■ http://caniuse.com/#feat=details

Missing Pieces

● CSS Variables for Shadow DOM

● Better Compilers

There are dozens of us!

● You (probably) don't need a JavaScript framework https://slack-files.com/T03JT4FC2-F151AAF7A-13fe6f98da

fin

Appendix

● http://webcomponents.org/polyfills/○ You only need webcomponents-lite.js for all the content in this slide

deck. ● http://bitworking.org/news/2014/05/zero_framework_manifesto

○ The rant that launched it all. ● http://vanilla-js.com/

But what about React Native?“learn once, write anywhere.“

https://en.wikipedia.org/wiki/List_of_platform-independent_GUI_libraries

top related