Surplus

High performance JSX web views for S.js applications

README

Surplus


  1. ```jsx
  2. const name = S.data("world"),
  3.       view = <h1>Hello {name()}!</h1>;
  4. document.body.appendChild(view);
  5. ```

Surplus is a compiler and runtime to allow S.js applications to create high-performance web views using JSX.  Thanks to JSX, your views are clear, declarative definitions of your UI.  Thanks to Surplus' compiler, they are converted into direct DOM instructions that run fast.  Thanks to S, they react automatically and efficiently as your data changes.

The Gist


Surplus treats JSX like a macro language for native DOM instructions.

  1. ```jsx
  2. const div = <div/>;
  3. // ... compiles to ...
  4. const div = document.createElement("div");

  5. // more complicated expressions are wrapped in a function
  6. const input = <input type="text"/>;
  7. // ... compiles to ...
  8. const input = (() => {
  9.     var __ = document.createElement("input");
  10.     __.type = "text";
  11.     return __;
  12. })();
  13. ```

These DOM instructions create real DOM nodes that match your JSX.  There's no virtual DOM middle layer standing between your JSX and the DOM.

DOM updates are handled by S computations.

  1. ```jsx
  2. const className = S.data("foo"),
  3.       div = <div className={className()}/>;
  4. // ... compiles to ...
  5. const className = S.data("foo"),
  6.       div = (() => {
  7.           var __ = document.createElement("div");
  8.           S(() => {
  9.               __.className = className(); // re-runs if className() changes
  10.           });
  11.           return __;
  12.       })();
  13. ```

The computations perform direct, fine-grained changes to the DOM nodes.  Updates run fast while keeping the DOM in sync with your JSX.

Finally, Surplus has a small runtime to help with more complex JSX features, like property spreads and variable children.

  1. ```jsx
  2. import * as Surplus from 'surplus';

  3. const div = <div {...props} />;
  4. // ... compiles to ...
  5. const div = (() => {
  6.     var __ = document.createElement("div");
  7.     Surplus.spread(__, props);
  8.     return __;
  9. })();
  10. ```

Installation


  1. ```sh
  2. > npm install --save surplus s-js
  3. ```

Like React, Surplus has two parts, a compiler and a runtime.

Runtime

The Surplus runtime must be imported as Surplus into any module using Surplus JSX views.

  1. ```javascript
  2. import * as Surplus from 'surplus'; // ES2015 modules
  3. const Surplus = require('surplus');   // CommonJS modules
  4. ```

Compiler


The easiest way to run the Surplus compiler is via a plugin for your build tool:

- Webpack: surplus-loader
- Gulp: gulp-surplus
- Browserify: surplusify
- Parcel: parcel

If you aren't using one of these tools, or if you want to write your own plugin, see Calling the surplus compiler.

Example


Here is a minimalist ToDo application, with a demo on CodePen:
  1. ```jsx
  2. const
  3.     Todo = t => ({               // our Todo constructor
  4.        title: S.data(t.title),   // properties are S data signals
  5.        done: S.data(t.done)
  6.     }),
  7.     todos = SArray([]),          // our todos, using SArray
  8.     newTitle = S.data(""),       // title for new todos
  9.     addTodo = () => {            // push new title onto list
  10.        todos.push(Todo({ title: newTitle(), done: false }));
  11.        newTitle("");             // clear new title
  12.     };

  13. const view =                     // declarative main view
  14.     <div>
  15.         <h2>Minimalist ToDos in Surplus</h2>
  16.         <input type="text" fn={data(newTitle)}/>
  17.         <a onClick={addTodo}> + </a>
  18.         {todos.map(todo =>       // insert todo views
  19.             <div>
  20.                 <input type="checkbox" fn={data(todo.done)}/>
  21.                 <input type="text" fn={data(todo.title)}/>
  22.                 <a onClick={() => todos.remove(todo)}>&times;</a>
  23.             </div>
  24.         )}
  25.     </div>;

  26. document.body.appendChild(view); // add view to document
  27. ```

Note that there is no .mount() or .render() command.  Since the JSX returns real nodes, we can attach them to the page with standard DOM commands, document.body.appendChild(view).

Note also that there's no code to handle updating the application: no .update() command, no .setState(), no change event subscription.  Other than a liberal sprinkling of ()'s, this could be static code.  

This is because S is designed to enable declarative programming, where we focus on defining how things should be and S handles updating the app from one state to the next as our data changes.  

Surplus lets us extend that model to the DOM.  We write JSX definitions of what the DOM should be, and Surplus generates runtime code to maintain those definitions.

Declarative programs aren't just clear, they're also flexible.  Because they aren't written with any specific changes in mind, they can often adapt easily to new behaviors.  For instance, we can add localStorage persistence with zero changes to the code above and only a handful of new lines:

  1. ```javascript
  2. if (localStorage.todos) { // load stored todos on start
  3.     todos(JSON.parse(localStorage.todos).map(Todo));
  4. }

  5. S(() => {                // store todos whenever they change
  6.     localStorage.todos = JSON.stringify(todos().map(t =>
  7.         ({ title: t.title(), done: t.done() })));
  8. });
  9. ```

More examples of Surplus programs:
- The standard TodoMVC in Surplus, which you can run here
- Surplus Demos, a collection of tiny Surplus example apps, from Hello World to Asteroids.
- The Realworld Demo in Surplus, a full-fledged Medium-like app demonstrating routing, authentication and server interaction, based on the Realworld spec.

Benchmarks


Direct DOM instructions plus S.js's highly optimized reactivity means that Surplus apps generally place at or near the top of various performance benchmarks.

For example, Surplus is currently the top framework in Stefan Krause's js-framework-benchmark:

js-framework-benchmark results

Documentation


Creating HTML Elements


  1. ```jsx
  2. const div       = <div></div>, // an HTMLDivElement
  3.       input     = <input/>;    // an HTMLInputElement
  4.       // ... etc
  5. ```

JSX expressions with lower-cased tags create elements.  These are HTML elements, unless their tag name or context is known to be SVG (see next entry).

There are no unclosed tags in JSX: all elements must either have a closing tag `` or end in `/>`,

Creating SVG Elements


  1. ```jsx
  2. const svg       = <svg></svg>, // SVGSVGElement
  3.       svgCircle = <circle/>,   // SVGCircleElement
  4.       svgLine   = <line/>;     // SVGLineElement
  5.       // ... etc
  6. ```

If the tag name matches a known SVG element, Surplus will create an SVG element instead of an HTML one. For the small set of tag names that belong to both -- ``, ``, ``, `<script>` and `<style>` -- Surplus creates an HTML element.<div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">title</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">title</span><span class="js-operator">><</span><span class="js-reg">/title>; /</span><span>/</span><span> </span><span class="js-variable">an</span><span> </span><span class="js-func">HTMLTitleElement</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div>Children of SVG elements are also SVG elements, unless their parent is the `<foreignObject>` element, in which case they are DOM elements again.<div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">svg</span><span> </span><span class="js-operator">=</span></li><li><span>    </span><span class="js-operator"><</span><span class="js-variable">svg</span><span class="js-operator">></span></li><li><span>        </span><span class="js-operator"><</span><span class="js-variable">text</span><span class="js-operator">></span><span class="js-variable">an</span><span> </span><span class="js-func">SVGTextElement</span><span class="js-operator"><</span><span>/</span><span class="js-variable">text</span><span class="js-operator">></span></li><li><span>        </span><span class="js-operator"><</span><span class="js-variable">foreignObject</span><span class="js-operator">></span></li><li><span>            </span><span class="js-operator"><</span><span class="js-variable">div</span><span class="js-operator">></span><span class="js-variable">an</span><span> </span><span class="js-func">HTMLDivElement</span><span class="js-operator"><</span><span>/</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>        </span><span class="js-operator"><</span><span>/</span><span class="js-variable">foreignObject</span><span class="js-operator">></span></li><li><span>    </span><span class="js-operator"><</span><span>/</span><span class="js-variable">svg</span><span class="js-operator">></span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">To create the SVG version of an ambiguous tag name, put it under a known SVG tag and extract it.</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">svg</span><span>      </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">svg</span><span class="js-operator">><</span><span class="js-variable">title</span><span class="js-operator">></span><span class="js-variable">an</span><span> </span><span class="js-func">SVGTitleElement</span><span class="js-operator"><</span><span class="js-reg">/title></</span><span class="js-variable">svg</span><span class="js-operator">></span><span>,</span></li><li><span>      </span><span class="js-variable">svgTitle</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">svg</span><span class="js-variable">.firstChild</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base"><h3>Setting properties</h3></div><div class="base"><br></div><div class="base">JSX allows static, dynamic and spread properties:</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span>// static</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">input1</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">input</span><span> </span><span class="js-variable">type</span><span class="js-operator">=</span><span class="js-string">"text"</span><span> </span><span>/</span><span class="js-operator">></span><span>;</span></li><li><br></li><li><span>// dynamic</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">text</span><span>   </span><span class="js-operator">=</span><span> </span><span class="js-string">"text"</span><span>,</span></li><li><span>      </span><span class="js-variable">input2</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">input</span><span> </span><span class="js-variable">type</span><span class="js-operator">=</span><span>{</span><span class="js-variable">text</span><span>}</span><span> </span><span>/</span><span class="js-operator">></span><span>;</span></li><li><br></li><li><span>// spread</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">props</span><span>  </span><span class="js-operator">=</span><span> </span><span>{</span><span> </span><span class="js-variable">type</span><span>:</span><span> </span><span class="js-string">"text"</span><span> </span><span>}</span><span>,</span></li><li><span>      </span><span class="js-variable">input3</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">input</span><span> </span><span>{</span><span class="js-variable">.</span><span class="js-variable">.</span><span class="js-variable">.props</span><span>}</span><span> </span><span>/</span><span class="js-operator">></span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">Since Surplus creates DOM elements, the property names generally refer to DOM element properties, although there are a few special cases:</div><div class="base"><br></div>1. If Surplus can tell that the given name belongs to an attribute not a property, it will set the attribute instead. Currently, the heuristic used to distinguish attributes from properties is “does it have a hyphen.” So `<div aria-hidden="true">` will set the `aria-hidden` attribute.<div class="base">2. Some properties have aliases.  See below.</div><div class="base">4. The properties <span class="edit-backquote">ref</span> and <span class="edit-backquote">fn</span> are special.  See below.</div><div class="base"><br></div><div class="base">You can set a property with an unknown name, and it will be assigned to the node, but it will have no effect on the DOM:</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">input</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">input</span><span> </span><span class="js-variable">myProperty</span><span class="js-operator">=</span><span>{</span><span class="js-keyword">true</span><span>}</span><span> </span><span>/</span><span class="js-operator">></span><span>;</span></li><li><span class="js-variable">input</span><span class="js-variable">.myProperty</span><span> </span><span class="js-operator">===</span><span> </span><span class="js-keyword">true</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base"><h3>Property aliases</h3></div><div class="base"><br></div><div class="base">In order to provide greater source compatibility with React and HTML, Surplus allows some properties to be referenced via alternate names.</div><div class="base"><br></div><div class="base">1. For compatibility with React, Surplus allows the React alternate property names as aliases for the corresponding DOM property.  So <span class="edit-backquote">onClick</span> is an alias for the native <span class="edit-backquote">onclick</span>.</div><div class="base">2. For compatibility with HTML, Surplus allows <span class="edit-backquote">class</span> and <span class="edit-backquote">for</span> as aliases for the <span class="edit-backquote">className</span> and <span class="edit-backquote">htmlFor</span> properties.</div><div class="base"><br></div><div class="base">For static and dynamic properties, aliases are normalized at compile time, for spread properties at runtime.</div><div class="base"><br></div><div class="base"><h3>Property precedence</h3></div><div class="base"><br></div><div class="base">If the same property is set multiple times on a node, the last one takes precedence:</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">props</span><span> </span><span class="js-operator">=</span><span> </span><span>{</span><span> </span><span class="js-variable">type</span><span>:</span><span> </span><span class="js-string">"radio"</span><span> </span><span>}</span><span>,</span></li><li><span>      </span><span class="js-variable">input</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">input</span><span> </span><span>{</span><span class="js-variable">.</span><span class="js-variable">.</span><span class="js-variable">.props</span><span>}</span><span> </span><span class="js-variable">type</span><span class="js-operator">=</span><span class="js-string">"text"</span><span> </span><span>/</span><span class="js-operator">></span><span>;</span></li><li><span class="js-variable">input</span><span class="js-variable">.type</span><span> </span><span class="js-operator">===</span><span> </span><span class="js-string">"text"</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base"><h3>Special <span class="edit-backquote">ref</span> property</h3></div><div class="base"><br></div><div class="base">A <span class="edit-backquote">ref</span> property specifies a variable to which the given node is assigned.  This makes it easy to get a reference to internal nodes.</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">let</span><span> </span><span class="js-variable">input</span><span>,</span></li><li><span>    </span><span class="js-variable">div</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>            </span><span class="js-operator"><</span><span class="js-variable">input</span><span> </span><span class="js-variable">ref</span><span class="js-operator">=</span><span>{</span><span class="js-variable">input</span><span>}</span><span> </span><span class="js-variable">type</span><span class="js-operator">=</span><span class="js-string">"text"</span><span> </span><span>/</span><span class="js-operator">></span></li><li><span>          </span><span class="js-operator"><</span><span>/</span><span class="js-variable">div</span><span class="js-operator">></span><span>;</span></li><li><span class="js-variable">input</span><span class="js-variable">.type</span><span> </span><span class="js-operator">===</span><span> </span><span class="js-string">"text"</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">The <span class="edit-backquote">ref</span> property fulfills a very similar role to the <span class="edit-backquote">ref</span> property in React, except that since nodes are created immediately in Surplus, it does not take a function but an assignable expression.</div><div class="base"><br></div><div class="base"><h3>Special <span class="edit-backquote">fn</span> property</h3></div><div class="base"><br></div><div class="base">A <span class="edit-backquote">fn</span> property specifies a function to be applied to a node.  It is useful for encapsulating a bit of reusable behavior or properties.</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">import</span><span> </span><span class="js-variable">data</span><span> </span><span class="js-keyword">from</span><span> </span><span class="js-string">'surplus-mixin-data'</span><span>;</span><span> </span><span>// two-way data binding utility</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">value</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-func">S</span><span class="js-func">.data</span><span>(</span><span class="js-string">"foo"</span><span>)</span><span>,</span></li><li><span>      </span><span class="js-variable">input</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">input</span><span> </span><span class="js-variable">type</span><span class="js-operator">=</span><span class="js-string">"text"</span><span> </span><span class="js-variable">fn</span><span class="js-operator">=</span><span>{</span><span class="js-func">data</span><span>(</span><span class="js-variable">value</span><span>)</span><span>}</span><span> </span><span>/</span><span class="js-operator">></span><span>;</span></li><li><span class="js-variable">input</span><span class="js-variable">.value</span><span> </span><span class="js-operator">===</span><span> </span><span class="js-string">"foo"</span><span>;</span></li><li><span>// user enters "bar" in input</span></li><li><span class="js-variable">input</span><span class="js-variable">.value</span><span> </span><span class="js-operator">===</span><span> </span><span class="js-string">"bar"</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">The function may take an optional second parameter, which will contain any value returned by the previous invocation, aka a ‘reducing’ pattern.  In typescript, the full signature looks like:</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">typescript</span></li><li><span class="js-variable">type</span><span> </span><span class="js-func">SurplusFn</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-func">N</span><span>,</span><span> </span><span class="js-func">T</span><span class="js-operator">></span><span>(</span><span class="js-variable">node</span><span> </span><span>:</span><span> </span><span class="js-func">N</span><span>,</span><span> </span><span class="js-variable">state</span><span> </span><span>:</span><span> </span><span class="js-func">T</span><span> </span><span>|</span><span> </span><span class="js-value">undefined</span><span>)</span><span> </span><span class="js-operator">=></span><span> </span><span class="js-func">T</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">The <span class="edit-backquote">fn</span> property may be specified multiple times for a node.  Surplus provides aliases <span class="edit-backquote">fn1</span>, <span class="edit-backquote">fn2</span>, etc., in case your linter complains about the repeated properties.</div><div class="base"><br></div><div class="base"><h3>Creating Child Elements</h3></div><div class="base"><br></div><div class="base">JSX defines two kinds of children, static and dynamic.</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span>// static, created in place, never change</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">div</span><span> </span><span class="js-operator">=</span></li><li><span>    </span><span class="js-operator"><</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>        </span><span class="js-operator"><</span><span class="js-variable">span</span><span class="js-operator">></span><span class="js-variable">a</span><span> </span><span class="js-variable">static</span><span> </span><span class="js-variable">span</span><span> </span><span class="js-variable">created</span><span> </span><span class="js-keyword">in</span><span> </span><span class="js-variable">the</span><span> </span><span class="js-variable">div</span><span class="js-operator"><</span><span>/</span><span class="js-variable">span</span><span class="js-operator">></span></li><li><span>        </span><span class="js-func">Some</span><span> </span><span class="js-variable">static</span><span> </span><span class="js-variable">text</span></li><li><span>    </span><span class="js-operator"><</span><span>/</span><span class="js-variable">div</span><span class="js-operator">></span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span>// { dynamic }, inserted into place, can change</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">span</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-operator"><</span><span class="js-variable">span</span><span class="js-operator">></span><span class="js-variable">a</span><span> </span><span class="js-variable">span</span><span> </span><span class="js-variable">to</span><span> </span><span class="js-variable">insert</span><span> </span><span class="js-keyword">in</span><span> </span><span class="js-variable">the</span><span> </span><span class="js-variable">div</span><span class="js-operator"><</span><span>/</span><span class="js-variable">span</span><span class="js-operator">></span><span>,</span></li><li><span>      </span><span class="js-variable">text</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-func">S</span><span class="js-func">.data</span><span>(</span><span class="js-string">"some text that will change"</span><span>)</span><span>,</span></li><li><span>      </span><span class="js-variable">div</span><span> </span><span class="js-operator">=</span></li><li><span>        </span><span class="js-operator"><</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>            </span><span>{</span><span class="js-variable">span</span><span>}</span></li><li><span>            </span><span>{</span><span class="js-func">text</span><span>(</span><span>)</span><span>}</span></li><li><span>        </span><span class="js-operator"><</span><span>/</span><span class="js-variable">div</span><span class="js-operator">></span><span>;</span></li><li><span class="js-func">text</span><span>(</span><span class="js-string">"the changed text"</span><span>)</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">With a dynamic child, the given expression is evaluated, and its result is inserted into the child nodes according to the following rules:</div><div class="base"><br></div><div class="base">- <span class="edit-backquote">null</span>, <span class="edit-backquote">undefined</span> or a boolean -> nothing</div><div class="base">- a DOM node -> the node</div><div class="base">- an array -> all items in array</div><div class="base">- a function -> the value from calling the function</div><div class="base">- a string -> a text node</div><div class="base">- anything else -> convert to string via .toString() and insert that</div><div class="base"><br></div><div class="base">Like React, Surplus removes all-whitespace nodes, and text nodes are trimmed.</div><div class="base"><br></div><div class="base"><h3>Embedded function calls, aka “Components”</h3></div><div class="base"><br></div><div class="base">JSX expressions with <span class="edit-star-italic">upper-cased</span> tag names are syntactic sugar for embedded function calls.</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-operator"><</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>    </span><span class="js-operator"><</span><span class="js-func">Foo</span><span> </span><span class="js-variable">bar</span><span class="js-operator">=</span><span class="js-string">"1"</span><span class="js-operator">></span><span class="js-variable">baz</span><span class="js-operator"><</span><span>/</span><span class="js-func">Foo</span><span class="js-operator">></span></li><li><span class="js-operator"><</span><span>/</span><span class="js-variable">div</span><span class="js-operator">></span><span>;</span></li><li><br></li><li><span>// ... is equivalent to ...</span></li><li><span class="js-operator"><</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>    </span><span>{</span><span class="js-func">Foo</span><span>(</span><span>{</span><span> </span><span class="js-variable">bar</span><span>:</span><span> </span><span class="js-string">"1"</span><span>,</span><span> </span><span class="js-variable">children</span><span>:</span><span> </span><span class="js-string">"baz"</span><span> </span><span>}</span><span>)</span><span>}</span></li><li><span class="js-operator"><</span><span>/</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base">The function is called with an object of the given properties, including any children, and the return value is embedded in place.</div><div class="base"><br></div><div class="base">Like with any programming, it is good practice to break a complex DOM view into smaller, re-usable functions.  Upper-cased JSX expressions provide a convenient way to embed these functions into their containing views.</div><div class="base"><br></div><div class="base">The special <span class="edit-backquote">ref</span> and <span class="edit-backquote">fn</span> properties work with embedded function calls the same way they do with nodes.  They operate on the return value of the function.</div><div class="base"><br></div><div class="base"><h3>Update Granularity — S computations</h3></div><div class="base"><br></div><div class="base">Surplus detects which parts of your view may change and constructs S computations to keep them up-to-date.</div><div class="base"><br></div><div class="base">1. Each element with dynamic properties or spreads gets a computation responsible for setting all properties for that node.</div><div class="base"><br></div><div class="base">2. Each <span class="edit-backquote">fn={...}</span> declaration gets its own computation.  This allows the <span class="edit-backquote">fn</span> to have internal state, if appropriate.</div><div class="base"><br></div><div class="base">3. Each dynamic children expression <span class="edit-backquote">{ ... }</span> gets a computation.  This includes embedded component calls, since they are an insert of the call's result.</div><div class="base"><br></div><div class="base"><h3>Surplus.* functions — not for public use</h3></div><div class="base"><br></div><div class="base">The surplus module has several functions which provide runtime support for the code emitted by the compiler.  These functions <span class="edit-star-italic">can and will change</span>, even in minor point releases.  You have been warned :).</div><div class="base"><br></div><div class="base">A corollary of this is that the runtime only supports code compiled by the same version of the compiler.  Switching to a new version of Surplus requires re-compiling your JSX code.</div><div class="base"><br></div><div class="base"><h3>Differences from React</h3></div><div class="base"><br></div><div class="base">Many React Stateless Functional Components can be dropped into Surplus with no or minimal changes.  Beyond that, here is a summary of the differences:</div><div class="base"><br></div><div class="base">1. The two big differences already stated above: Surplus makes real DOM elements, not virtual, and they update automatically.  This removes most of the React API.  There are no components, no virtual elements, no lifecycle, no setState(), no componentWillReceiveProps(), no diff/patch, etc etc.</div><div class="base"><br></div><div class="base">2. The <span class="edit-backquote">ref</span> property takes an assignable reference, not a function.</div><div class="base"><br></div><div class="base">3. Events are native events, not React's synthetic events.</div><div class="base"><br></div><div class="base">4. Surplus is a little more liberal in the property names it accepts, like <span class="edit-backquote">onClick</span>/<span class="edit-backquote">onclick</span>, <span class="edit-backquote">className</span>/<span class="edit-backquote">class</span>, etc.</div><div class="base"><br></div>5. If you set an unknown field with a name that doesn't contain a dash, like `<div mycustomfield="1" />`, React will set an attribute while Surplus will set a property.<div class="base"><br></div><div class="base"><h3>Calling the surplus compiler</h3></div><div class="base"><br></div><div class="base">If one of the build tools listed above doesn't work for you, you may need to work the surplus compiler into your build chain on your own.  The compiler has a very small API:</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">javascript</span></li><li><span class="js-keyword">import</span><span> </span><span>{</span><span> </span><span class="js-variable">compiler</span><span> </span><span>}</span><span> </span><span class="js-keyword">from</span><span> </span><span class="js-string">'surplus/compiler'</span><span>;</span></li><li><br></li><li><span>// simple string -> string translation, no sourcemap</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">out</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">compiler</span><span class="js-func">.compile</span><span>(</span><span class="js-keyword">in</span><span>)</span><span>;</span></li><li><br></li><li><span>// w/ appended sourcemap</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-variable">out</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">compiler</span><span class="js-func">.compile</span><span>(</span><span class="js-keyword">in</span><span>,</span><span> </span><span>{</span><span> </span><span class="js-variable">sourcemap</span><span>:</span><span> </span><span class="js-string">'append'</span><span> </span><span>}</span><span>)</span><span>;</span></li><li><br></li><li><span>// w/ extracted sourcemap</span></li><li><span>// note that output is different, to return map and src</span></li><li><span class="js-keyword">const</span><span> </span><span>{</span><span> </span><span class="js-variable">out</span><span>,</span><span> </span><span class="js-variable">map</span><span> </span><span>}</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">compiler</span><span class="js-func">.compile</span><span>(</span><span class="js-keyword">in</span><span>,</span><span> </span><span>{</span><span> </span><span class="js-variable">sourcemap</span><span>:</span><span> </span><span class="js-string">'extract'</span><span> </span><span>}</span><span>)</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base"><h2>FAQs</h2></div><div class="base"><br></div><div class="base"><h3>Can I use the standard Babel or Typescript JSX compilers for Surplus?</h3></div><div class="base"><br></div><div class="base">No. Surplus isn't just the runtime library, it's the library+compiler, which are tightly coupled.  It's not just cosmetic differences.  The standard JSX compilation format was designed for virtual DOM and doesn't have the features Surplus needs.  In particular, Surplus wraps dynamic expressions -- <span class="edit-backquote">{ ... }</span> -- in lambdas, so that it can create S computations that detect when the expressions change and make targeted updates.  The usual format doesn't do that, since it relies on external notification of change (<span class="edit-backquote">.setState()</span>) followed by a diffing phase to detect what changed.</div><div class="base"><br></div><div class="base"><h3>Why real DOM nodes?</h3></div><div class="base"><br></div><div class="base">Virtual DOM is a powerful and proven approach to building a web framework.  However, Surplus elects to use real DOM elements for two reasons:</div><div class="base"><br></div><div class="base"><h4>Virtual DOM solves a problem Surplus solves via <a href="https://github.com/adamhaile/S" title="S.js" target="_blank" rel="nofollow noopener noreferrer">S.js</a></h4></div><div class="base"><br></div><div class="base">Virtual DOM is sometimes described as a strategy to make the DOM reactive, but this isn't exactly true.  The DOM is already reactive: browsers have sophisticated dirty-marking and update-scheduling algorithms to propagate changes made via the DOM interface to the pixels on the screen.  That is what allows us to set a property like <span class="edit-backquote">input.value = "foo"</span> and maintain the abstraction that we're “setting a value directly,” when in fact there are many layers and much deferred execution before that change hits the screen.</div><div class="base"><br></div><div class="base">What isn't reactive is Javascript, and virtual DOM is better understood as a strategy for making Javascript more reactive.  Javascript lacks the automatic, differential-update capabilities of a reactive system.  Instead, virtual DOM libraries have apps build and re-build a specification for what the DOM should be, then use diffing and reconciling algorithms to update the real DOM.  Virtual DOM libraries thus build on one of Javascript's strengths — powerful idioms for object creation — to address one of its weaknesses — reactivity.</div><div class="base"><br></div><div class="base">Surplus is built on <a href="https://github.com/adamhaile/S" title="S.js" target="_blank" rel="nofollow noopener noreferrer">S.js</a>, and it takes advantage of S's fine-grained dependency tracking and deterministic update scheduling.  Adding a virtual DOM layer on top of S would stack two reactive strategies with no additional gain.  Ideally, Surplus provides an abstraction much like the DOM: we manipulate the data with the expectation that the downstream layers will update naturally and transparently.</div><div class="base"><br></div><div class="base"><h4>Virtual DOM has a cost, in performance, complexity and interop</h4></div><div class="base"><br></div><div class="base">Performance: virtual DOM libraries throw away information about what has changed, then reconstruct it in the diff phase.  Some smart engineers have made diffing surprisingly fast, but the cost can never be zero.</div><div class="base"><br></div><div class="base">Complexity: the separation between a virtual and a real layer brings with it a host of abstractions, such as component ‘lifecycle’ and ‘refs,’ which are essentially hooks into the reconciler's work.  The standard programming model of values and functions gets mirrored with a parallel layer of virtual values and function-like components.</div><div class="base"><br></div><div class="base">Interop: communication between different virtual DOM libraries, or between virtual values and your own code, is complicated by the fact that the common layer upon which they operate, the DOM, is held within the library.  The library only allows access to the DOM at certain moments and through certain ports, like ‘refs.’</div><div class="base"><br></div><div class="base">In comparison, S's automatic dependency graph tracks exactly which parts of the DOM need to be updated when data changes.  Surplus takes the React claim that it's “just Javascript” one step further, in that Surplus “components” are just functions, and its views just DOM nodes.  Interop with them is obvious.</div><div class="base"><br></div><div class="base">Surplus does have its own tradeoffs, the largest of which is that automatic updates of the DOM require that the changing state be held in S data signals.  The second largest is that declarative reactive programming is unfamiliar to many programmers who are already well versed in a procedural “this happens then this happens then ...” model of program execution.  Finally, Surplus trades the performance cost of diffing with the performance cost of bookkeeping in the S dependency graph.</div><div class="base"><br></div><div class="base"><h3>If Surplus doesn't have components, how can views have state?</h3></div><div class="base"><br></div><div class="base">The same way functions usually have state, via closures:</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>`</span><span>`</span><span>`</span><span class="js-variable">jsx</span></li><li><span class="js-keyword">const</span><span> </span><span class="js-func">Counter</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-variable">init</span><span> </span><span class="js-operator">=></span><span> </span><span>{</span></li><li><span>    </span><span class="js-keyword">const</span><span> </span><span class="js-variable">count</span><span> </span><span class="js-operator">=</span><span> </span><span class="js-func">S</span><span class="js-func">.data</span><span>(</span><span class="js-variable">init</span><span>)</span><span>;</span></li><li><span>    </span><span class="js-keyword">return</span><span> </span><span>(</span></li><li><span>        </span><span class="js-operator"><</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>            </span><span class="js-func">Count</span><span> </span><span class="js-variable">is</span><span>:</span><span> </span><span>{</span><span class="js-func">count</span><span>(</span><span>)</span><span>}</span></li><li><span>            </span><span class="js-operator"><</span><span class="js-variable">button</span><span> </span><span class="js-variable">onClick</span><span class="js-operator">=</span><span>{</span><span>(</span><span>)</span><span> </span><span class="js-operator">=></span><span> </span><span class="js-func">count</span><span>(</span><span class="js-func">count</span><span>(</span><span>)</span><span> </span><span class="js-operator">+</span><span> </span><span class="js-variable">1</span><span>)</span><span>}</span><span class="js-operator">></span></li><li><span>                </span><span class="js-func">Increment</span></li><li><span>            </span><span class="js-operator"><</span><span>/</span><span class="js-variable">button</span><span class="js-operator">></span></li><li><span>        </span><span class="js-operator"><</span><span>/</span><span class="js-variable">div</span><span class="js-operator">></span></li><li><span>    </span><span>)</span><span>;</span></li><li><span>}</span><span>;</span></li><li><span>`</span><span>`</span><span>`</span></li></ol></div><div class="base"><br></div><div class="base"><h3>I'm using Surplus with TypeScript, and the compiler is choking</h3></div><div class="base"><br></div><div class="base">The Surplus compiler works on javascript, not TypeScript, so be sure to do the TypeScript compilation first, passing the JSX through via the <span class="edit-backquote">jsx: 'preserve'</span> option.  Then run Surplus on the output.</div><div class="base"><br></div><div class="base"><h3>I'm using Surplus with Typescript, and I'm getting a runtime error ‘Surplus is not defined’ even though I imported it?</h3></div><div class="base"><br></div><div class="base">Typescript strips imports that aren't referenced in your code.  Since the references to Surplus haven't been made when Typescript runs (see question above) it removes the import.  The workaround is to tell Typescript that Surplus is your <span class="edit-backquote">jsxFactory</span> in your tsconfig.json:</div><div class="base"><br></div><div class="code"><ol start="0"><li><span>```json</span></li><li><span>{</span></li><li><span>    </span><span class="js-variable">"compilerOptions":</span><span> {</span></li><li><span>        ...</span></li><li><span>        </span><span class="js-variable">"jsx":</span><span> </span><span class="js-string">"preserve"</span><span>,</span></li><li><span>        </span><span class="js-variable">"jsxFactory":</span><span> </span><span class="js-string">"Surplus"</span><span>,</span></li><li><span>    }</span></li><li><span>}</span></li><li><span>```</span></li></ol></div><div class="base">Technically, we're not asking Typescript to compile our JSX, so the <span class="edit-backquote">jsxFactory</span> setting shouldn't matter, but it has the useful side-effect of letting Typescript know not to strip Surplus from the compiled module.</div><div class="base"><br></div><div class="base"><h3>Why isn't the Surplus compiler built on Babel?</h3></div><div class="base"><br></div><div class="base">Mostly for historical reasons: Surplus was originally started about 4 years ago, before Babel had become the swiss army knife of JS extension.  Surplus therefore has its own hand-written compiler, a fairly classic tokenize-parse-transform-compile implementation.  Surplus may switch to Babel in the future.  The current compiler only parses the JSX expressions, not the JS code itself, which limits the optimizations available.</div><div class="base"><br></div><div class="base">© Adam Haile, 2017.  MIT License.</div><div class="base"><br></div></div></div></div><div id="footer"><div class="footer-box"><div class="footer-info"><div class="fi-logo"><img src="/img/footer-logo.png" width="65"/></div><div class="fi-intro"><strong>为什么是最好的 GitHub 项目?</strong><p>技术的发展速度比以往任何时候都快,技术正在全速创新。几乎每天都会发布惊人的开源项目。</p><p><br/></p><strong>如何了解最新趋势?</strong><p>如何快速检查真正重要的项目,现在而不是 6 个月前?</p><p>探客时代每天拍摄 GitHub stars 的 “快照”,以获得 2000 多个项目的精选列表,以检测过去几个月的趋势。</p><p><br/></p><strong>你想要更多的项目吗?</strong><p>我们不会扫描 GitHub 上的所有现有项目,而是根据我们的经验和我们在 Internet 上阅读的内容,专注于我们发现 “有趣” 的精选项目列表。想要了解的项目可以提交给我们。</p></div><div class="fi-qrcode"><h3>关注公众号</h3><img src="/img/qrcode.jpg" width="272" height="272"/></div></div><div class="footer-copyright">© 2022, 北京探客时代网络科技有限公司 <br/><a href="http://beian.miit.gov.cn" target="_blank" rel="nofollow noopener noreferrer">京 ICP 备 2022008592 号</a> <a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11011402012574" target="_blank" rel="nofollow noopener noreferrer">京公网安备 11011402012574 号</a> <a href="contact.html" target="_blank" rel="nofollow noopener noreferrer">信息举报</a></div></div></div><script>function request(t,e,n){$.ajax({url:t,type:"get",data:e,dataType:"json",success:function(t){n(t.data)}})}function levenshtein(t,e){t=t.toLowerCase(),e=e.toLowerCase();for(var n=t.length,o=e.length,i=[],a=0;a<n+1;a++)i.push(new Array(o+1));for(a=0;a<=n;a++)i[a][0]=a;for(a=0;a<=o;a++)i[0][a]=a;var l,s=t.split(""),r=e.split("");for(a=1;a<=n;a++)for(var c=1;c<=o;c++){l=s[a-1]==r[c-1]?0:1;var u=i[a-1][c-1]+l,h=i[a][c-1]+1,f=i[a-1][c]+1;i[a][c]=Math.min(u,h,f)}return 1-i[n][o]/Math.max(t.length,e.length)}$(function(){var t=[],e=[];request("https://api.tkcnn.com/gh/tag/all",null,function(e){t=e;var n=window.location.href.match(/([\w-]+)\.html/);if(n){var o=t.find(t=>t.code===n[1]);o&&$(".select-set").val(o.name)}}),$(".select-set").on("click input",function(){var n=$(this).val();if(n=n.replace(/(^\s+|\s+$)/g,""),e=[],""===n.replace(/s/g,""))e=t;else{for(var o,i=0;i<t.length;i++)(o=levenshtein(n,t[i].name))>=.1&&e.push({name:t[i].name,code:t[i].code,count:t[i].count,similar:o});e.sort(function(t,e){return e.similar-t.similar})}var a="";for(i=0;i<e.length;i++)a+=`<button>${e[i].name} (${e[i].count})</button>`;$(".select-list").html(a)}),$(".select-list").on("click","button",function(t){$(".select-list").hide(),setTimeout(function(){$(".select-list").css("display","")},200),window.location.href=window.location.origin+`/repo/${e[$(this).index()].code}.html`})});</script></body></html>