SkillAgentSearch skills...

Surplus

High performance JSX web views for S.js applications

Install / Use

/learn @adamhaile/Surplus
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Surplus

const name = S.data("world"),
      view = <h1>Hello {name()}!</h1>;
document.body.appendChild(view);

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.

const div = <div/>;
// ... compiles to ...
const div = document.createElement("div");

// more complicated expressions are wrapped in a function
const input = <input type="text"/>;
// ... compiles to ...
const input = (() => {
    var __ = document.createElement("input");
    __.type = "text";
    return __;
})();

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.

const className = S.data("foo"),
      div = <div className={className()}/>;
// ... compiles to ...
const className = S.data("foo"),
      div = (() => {
          var __ = document.createElement("div");
          S(() => {
              __.className = className(); // re-runs if className() changes
          });
          return __;
      })();

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.

import * as Surplus from 'surplus';

const div = <div {...props} />;
// ... compiles to ...
const div = (() => {
    var __ = document.createElement("div");
    Surplus.spread(__, props);
    return __;
})();

Installation

> npm install --save surplus s-js

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.

import * as Surplus from 'surplus'; // ES2015 modules
const Surplus = require('surplus');   // CommonJS modules

Compiler

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

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:

const
    Todo = t => ({               // our Todo constructor
       title: S.data(t.title),   // properties are S data signals
       done: S.data(t.done)
    }),
    todos = SArray([]),          // our todos, using SArray
    newTitle = S.data(""),       // title for new todos
    addTodo = () => {            // push new title onto list
       todos.push(Todo({ title: newTitle(), done: false }));
       newTitle("");             // clear new title
    };

const view =                     // declarative main view
    <div>
        <h2>Minimalist ToDos in Surplus</h2>
        <input type="text" fn={data(newTitle)}/>
        <a onClick={addTodo}> + </a>
        {todos.map(todo =>       // insert todo views
            <div>
                <input type="checkbox" fn={data(todo.done)}/>
                <input type="text" fn={data(todo.title)}/>
                <a onClick={() => todos.remove(todo)}>&times;</a>
            </div>
        )}
    </div>;

document.body.appendChild(view); // add view to document

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:

if (localStorage.todos) { // load stored todos on start
    todos(JSON.parse(localStorage.todos).map(Todo));
}

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

More examples of Surplus programs:

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

const div       = <div></div>, // an HTMLDivElement
      input     = <input/>;    // an HTMLInputElement
      // ... etc

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

const svg       = <svg></svg>, // SVGSVGElement
      svgCircle = <circle/>,   // SVGCircleElement
      svgLine   = <line/>;     // SVGLineElement
      // ... etc

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 -- <a>, <font>, <title>, <script> and <style> -- Surplus creates an HTML element.

const title = <title></title>; // an HTMLTitleElement

Children of SVG elements are also SVG elements, unless their parent is the <foreignObject> element, in which case they are DOM elements again.

const svg =
    <svg>
        <text>an SVGTextElement</text>
        <foreignObject>
            <div>an HTMLDivElement</div>
        </foreignObject>
    </svg>;

To create the SVG version of an ambiguous tag name, put it under a known SVG tag and extract it.

const svg      = <svg><title>an SVGTitleElement</title></svg>,
      svgTitle = svg.firstChild;

Setting properties

JSX allows static, dynamic and spread properties:

// static
const input1 = <input type="text" />;

// dynamic
const text   = "text",
      input2 = <input type={text} />;

// spread
const props  = { type: "text" },
      input3 = <input {...props} />;

Since Surplus creates DOM elements, the property names generally refer to DOM element properties, although there are a few special cases:

  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.
  2. Some properties have aliases. See below.
  3. The properties ref and fn are special. See below.

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:

const input = <input myProperty={true} />;
input.myProperty === true;

Property aliases

In order to provide greater source compatibility with React and HTML, Surplus allows some properties to be referenced via alternate names.

  1. For compatibility with React, Surplus allows the React alternate property names as aliases for the corresponding DOM property. So onClick is an alias for the native onclick.
  2. For compatibility with HTML, Surplus allows class and for as aliases for the className and htmlFor properties.

For static and dynamic properties, aliases are normalized at compile time, for spread properties at runtime.

Property precedence

If the same property is set multiple times on a node, the last one takes precedence:

const props = { type: "radio" },
      input = <input {...props} type="text" />;
input.type === "text";

Special ref property

A ref property specifies a variable to which the given node is assigned. This

View on GitHub
GitHub Stars650
CategoryDevelopment
Updated22d ago
Forks25

Languages

JavaScript

Security Score

80/100

Audited on Mar 18, 2026

No findings