Hello {name()}!; document.body.appendChild(view); Surplus is a compiler and runtime to all" /> Hello {name()}!; document.body.appendChild(view); Surplus is a compiler and runtime to all" /> Hello {name()}!; document.body.appendChild(view); Surplus is a compiler and runtime to all"/>

High performance JSX web views for S.js applications

Overview

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 makes it easy to get a reference to internal nodes.

let input,
    div = <div>
            <input ref={input} type="text" />
          </div>;
input.type === "text";

The ref property fulfills a very similar role to the ref property in React, except that since nodes are created immediately in Surplus, it does not take a function but an assignable expression.

Special fn property

A fn property specifies a function to be applied to a node. It is useful for encapsulating a bit of reusable behavior or properties.

import data from 'surplus-mixin-data'; // two-way data binding utility
const value = S.data("foo"),
      input = <input type="text" fn={data(value)} />;
input.value === "foo";
// user enters "bar" in input
input.value === "bar";

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:

type SurplusFn = <N, T>(node : N, state : T | undefined) => T

The fn property may be specified multiple times for a node. Surplus provides aliases fn1, fn2, etc., in case your linter complains about the repeated properties.

Creating Child Elements

JSX defines two kinds of children, static and dynamic.

// static, created in place, never change
const div =
    <div>
        <span>a static span created in the div</span>
        Some static text
    </div>;
// { dynamic }, inserted into place, can change
const span = <span>a span to insert in the div</span>,
      text = S.data("some text that will change"),
      div =
        <div>
            {span}
            {text()}
        </div>;
text("the changed text");

With a dynamic child, the given expression is evaluated, and its result is inserted into the child nodes according to the following rules:

  • null, undefined or a boolean -> nothing
  • a DOM node -> the node
  • an array -> all items in array
  • a function -> the value from calling the function
  • a string -> a text node
  • anything else -> convert to string via .toString() and insert that

Like React, Surplus removes all-whitespace nodes, and text nodes are trimmed.

Embedded function calls, aka “Components”

JSX expressions with upper-cased tag names are syntactic sugar for embedded function calls.

<div>
    <Foo bar="1">baz</Foo>
</div>;

// ... is equivalent to ...
<div>
    {Foo({ bar: "1", children: "baz" })}
</div>

The function is called with an object of the given properties, including any children, and the return value is embedded in place.

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.

The special ref and fn properties work with embedded function calls the same way they do with nodes. They operate on the return value of the function.

Update Granularity — S computations

Surplus detects which parts of your view may change and constructs S computations to keep them up-to-date.

  1. Each element with dynamic properties or spreads gets a computation responsible for setting all properties for that node.

  2. Each fn={...} declaration gets its own computation. This allows the fn to have internal state, if appropriate.

  3. Each dynamic children expression { ... } gets a computation. This includes embedded component calls, since they are an insert of the call's result.

Surplus.* functions — not for public use

The surplus module has several functions which provide runtime support for the code emitted by the compiler. These functions can and will change, even in minor point releases. You have been warned :).

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.

Differences from React

Many React Stateless Functional Components can be dropped into Surplus with no or minimal changes. Beyond that, here is a summary of the differences:

  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.

  2. The ref property takes an assignable reference, not a function.

  3. Events are native events, not React's synthetic events.

  4. Surplus is a little more liberal in the property names it accepts, like onClick/onclick, className/class, etc.

  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.

Calling the surplus compiler

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:

import { compiler } from 'surplus/compiler';

// simple string -> string translation, no sourcemap
const out = compiler.compile(in);

// w/ appended sourcemap
const out = compiler.compile(in, { sourcemap: 'append' });

// w/ extracted sourcemap
// note that output is different, to return map and src
const { out, map } = compiler.compile(in, { sourcemap: 'extract' });

FAQs

Can I use the standard Babel or Typescript JSX compilers for Surplus?

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 -- { ... } -- 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 (.setState()) followed by a diffing phase to detect what changed.

Why real DOM nodes?

Virtual DOM is a powerful and proven approach to building a web framework. However, Surplus elects to use real DOM elements for two reasons:

Virtual DOM solves a problem Surplus solves via S.js

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 input.value = "foo" 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.

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.

Surplus is built on S.js, 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.

Virtual DOM has a cost, in performance, complexity and interop

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.

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.

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.’

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.

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.

If Surplus doesn't have components, how can views have state?

The same way functions usually have state, via closures:

const Counter = init => {
    const count = S.data(init);
    return (
        <div>
            Count is: {count()}
            <button onClick={() => count(count() + 1)}>
                Increment
            </button>
        </div>
    );
};

I'm using Surplus with TypeScript, and the compiler is choking

The Surplus compiler works on javascript, not TypeScript, so be sure to do the TypeScript compilation first, passing the JSX through via the jsx: 'preserve' option. Then run Surplus on the output.

I'm using Surplus with Typescript, and I'm getting a runtime error ‘Surplus is not defined’ even though I imported it?

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 jsxFactory in your tsconfig.json:

{
    "compilerOptions": {
        ...
        "jsx": "preserve",
        "jsxFactory": "Surplus",
    }
}

Technically, we're not asking Typescript to compile our JSX, so the jsxFactory setting shouldn't matter, but it has the useful side-effect of letting Typescript know not to strip Surplus from the compiled module.

Why isn't the Surplus compiler built on Babel?

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.


© Adam Haile, 2017. MIT License.

Comments
  • Proposal for attribute+signal shorthand

    Proposal for attribute+signal shorthand

    Right now we can put signals in as attribute values as so:

    const inputType = S.value('text');
    const Foo = () => (<input type={inputType()} />);
    inputType('password');
    

    However would it be possible/better to allow direct usages of signals to be deduced and allowed?

    const inputType = S.value('text');
    const Foo = () => (<input type=inputType />);
    inputType('password');
    

    While fill=white traditionally worked with HTML, Surplus already (rightfully, IMO) throws as an error, so adding this wouldn't be breaking anyone. It'd simply be a nice shorthand.

    Just a thought!

    opened by Qix- 14
  • Unidirectional dataflow doesn't work well

    Unidirectional dataflow doesn't work well

    I see surplus-fn-data does 2 way bindings, but I don't think that way. I tried to do unidirectional dataflow with surplus.

    const Name = (props) => {
      const {name, onchange} = props
      return <input type="text" value={name} onKeyUp={onchange} />
    }
    
    const name = S.data('');
    function updateName({ target: { value } }) {
      name(value);
    }
    const view = S.root(() => (
      <div>
        name:{name()}
        <br />
        <Name name={name()} onchange={updateName} />
      </div>
    ));
    document.body.appendChild(view);
    

    loss-focus

    Problem with this code is after I type a character into the input, it loses focus. I think what is happening is the view is being regenerated and a new input field is replacing the old one. vdom solves this by patching....

    Is this just how surplus works, or can I do unidirectional dataflow?

    opened by Pyrolistical 10
  • Dynamic attributes vs static attributes

    Dynamic attributes vs static attributes

    Hi @adamhaile only small problem in beta3 npm install && npm run build & npm run test cannot be work from fresh copy. PR is not required please add "s-js": "^0.4.5" and as dependencies and "jasmine-core": "^2.6.0" to devDependencies in package.json.

    Besides about this issue here is an example:

    <input
            className={style({ border: 'none' })} //it uses https://typestyle.github.io/#/core/-style-
            type="text"
            onInput={updateInputData}
            value={props.value()}
          />
    

    Current output:

        __input1 = Surplus.createElement('input', null, __);
       Surplus.S(function () {
            __input1.className = lib_1.style({ border: 'none' });
            __input1.type = "text";
            __input1.oninput = updateInputData;
            __input1.value =props.value();
        }
    

    className and onInput is looks dynamic if we look at it by JSX but it is static if we look at by surplus. Only value={props.value()} is dynamic. type="text" is hardly static.

    Correct output may be:

        __input1 = Surplus.createElement('input', null, __);
       __input1.className = lib_1.style({ border: 'none' });
       __input1.type = "text";
       __input1.oninput = updateInputData;
       Surplus.S(function () {
            __input1.value =props.value();
        }
    
    opened by ismail-codar 9
  • Unexpected component recreation

    Unexpected component recreation

    Hi, I've been having an issue with components that are recreated after a signal updates, when they should be reusing the DOM and updating attributes instead.

    I've made a simplified example on jsfiddle to show the issue. I found this issues while using an array of JSX elements as a return type. I think I've seen this a few times before when dealing with sibling JSX. It has caused odd problems in places where I've used props.children if there are multiple sibling used as children.

    Demo: http://jsfiddle.net/zd37ahbo/16/

    I tried to show the issue with console logging in the {Broken,Fixed}Component. If you open the console, you'll see the message "Should only see this message once" appear each time you click the button. If you replace the usage of BrokenComponent with FixedComponent, you will no longer see that message in the console logs.

    I would expect both Broken- and FixedComponent to log just once on creation because the signal is evaluated on an attribute of its return JSX.

    I hope this was enough information. Let me know if there's anything I can clarify.

    opened by derekhawker 8
  • export 'appendChild' (imported as 'Surplus') was not found in 'surplus'

    export 'appendChild' (imported as 'Surplus') was not found in 'surplus'

    I'm trying to run the example near the top of the README. Getting this error:

    WARNING in ./src/index.jsx
    45:16-35 "export 'appendChild' (imported as 'Surplus') was not found in 'surplus'
     @ ./src/index.jsx
     @ multi (webpack)-dev-server/client?http://localhost:8080 webpack/hot/dev-server ./src/index.jsx
    

    surplus is beta version ^0.5.0-beta6 (no problem on 0.4). My code:

    import * as Surplus from 'surplus';
    import S from 's-js';
    import SArray from 's-array';
    
    if (module.hot) {
      module.hot.accept();
    }
    
    var 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
    },
    view = S.root(() => {                      // 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
    
    opened by andyjessop 7
  • Question: Using Surplus without JSX

    Question: Using Surplus without JSX

    Is it possible to use Surplus, but without writing JSX?

    This would be analogous to using React with manually written React.createElement() calls. The benefit is being able to transpile from languages that don't support (and can't preserve) JSX.

    opened by vp2177 7
  • Bug:

    Bug: "class" attribute on SVG is incorrectly output as "className"

    It took me a while for my eyes to spot just why the heck my styles weren't getting applied. Turns out my element starts as:

                <svg
                    version="1.1"
                    class={ $style.callout__quotation }
                    x="0px"
                    y="0px"
                    viewBox="0 0 48 42.1"
                >
                    <polygon points="48,0 48,11.6 39.6,19.5 48,19.5 48,42.1 27.5,42.1 27.5,19.7" />
                    <polygon points="20.5,0 20.5,11.6 12,19.5 20.5,19.5 20.5,42.1 0,42.1 0,19.7" />
                </svg>
    

    and ends up as:

    <svg version="1.1" className="_callout__quotation_kxemy_95" x="0px" y="0px" viewBox="0 0 48 42.1"><polygon points="48,0 48,11.6 39.6,19.5 48,19.5 48,42.1 27.5,42.1 27.5,19.7"></polygon><polygon points="20.5,0 20.5,11.6 12,19.5 20.5,19.5 20.5,42.1 0,42.1 0,19.7"></polygon></svg>
    

    So, class becomes className, which of course doesn't apply the CSS class.

    opened by matthew-dean 7
  • Server-side rendering?

    Server-side rendering?

    This project looks awesome, and I came across it by looking at js-frameworks-benchmark. One thing that's not clear: since Surplus creates native DOM elements, does that mean it can't pre-render an HTML page and "re-hydrate" it, like other libs (reducing time of first-paint)?

    That is, other related questions: what does the root page look like? How is it attached to a document? How are these components consumed? Would be good to have some ideas of how it works in practice. Thanks.

    opened by matthew-dean 6
  • Typescript transpiled code support for input statement

    Typescript transpiled code support for input statement

    Example Typescript code.

    import { MdButton } from "md-components/button";
    
    export = (ctrl: any) =>
        <MdButton>Button 1</MdButton>
    

    Outputs to (in typescript 2.3.4 and jsx=preserve settings)

    var button_1 = require("md-components/button");
    module.exports = function (ctrl) {
        return <button_1.MdButton>Button 1</button_1.MdButton>;
    };
    

    The above code is failing: Error: unrecognized content in begin tag at line 3 col 17: ``.MdButton>Button 1</button_1.M''

    But using const MdButton = require("md-components/button").MdButton instead of import is working.

    opened by ismail-codar 6
  • Basics of Surplus Array rendering

    Basics of Surplus Array rendering

    I have yet another question about fundamentals that my slow brain is not quite getting. I come from the React virtual dom world where I always put a key on every item in a dynamic list so that React's virtual dom implementation can safely do a keyed list reconciliation. I notice no use of keys here in Surplus. So in a list situation on the first render, Surplus is going to emit a set of real DOM nodes, and on subsequent renders it will do the same and reconcile them. I see from the source that reconciliation is done by being equable. 2 nodes are the same if they are strictly === or if they are string or Text nodes that can be reused. Now I also notice that in most of the examples that typically SArray maps are being used, which I believe memoize computations in items in the underlying array that haven't changed. Is that the trick to doing the reconciliation without keys since the SArray map will produce a node list that is a blend of old and new nodes? Should Surplus then only be used with SArray?

    opened by mrjjwright 5
  • if/else or ternary statements

    if/else or ternary statements

    This doesn't work.

    export interface Greeting {
        msg: string
    }
    export const GreetingV = ({pojo}: {pojo: Greeting}) =>
    <div>
      <b>{pojo.msg}</b>
    </div>
    
    export const HelloV = ({pojo}: {pojo: Greeting}) =>
    <div>
      <span>{pojo.msg}</span>
    </div>
    
    <div>
    ${p.disabled ? <HelloV pojo={p.greeting} /> : <GreetingV pojo={p.greeting} />}
    </div>
    

    Would also be cool if the compiler lazily creates it (and caches) on the expression that evals to true.

    opened by dyu 5
  • reconcileArrays fails on parent.insertBefore

    reconcileArrays fails on parent.insertBefore

    I am using Surplus.content directly to manipulate the content of an element not created or controlled by the Surplus compiler. But the same error can be reproduced with JSX syntax. The following snippet reproduces the error.

    import * as Surplus from "surplus";
    
    const a = ["foo ", span("["), "bar", span("]")];
    const b = ["foo ", span("{"), "bar", span("}")];
    let state = []; 
    const body = document.createElement("div");
    state = Surplus.content(body, a, state);
    state = Surplus.content(body, b, state);
    
    // Failed to execute 'insertBefore' on 'Node': 
    // The node before which the new node is to be 
    // inserted is not a child of this node.
    
    
    
    // utility
    function span(s: string) {
      const span = document.createElement("span");
      span.innerText = s;
      return span;
    }
    

    The exception is thrown from https://github.com/adamhaile/surplus/blob/master/src/runtime/content.ts#L302. It appears that inserting the right-most <span>}</span> is skipped, due to being marked as NOINSERT. Then, when going to insert the Text node "bar", it attempts to insert relative to the <span> which was not inserted.

    I'm not sure why the right-most <span> is marked as NOINSERT by this line in the text node reuse algorithm. Is it failing to mark the "bar" node and mis-marking the <span> node?

    opened by webstrand 0
  • Not compatible with S.subclock

    Not compatible with S.subclock

    Hi Adam! It seems like S.subclock isn't compatible with surplus... which is a shame!

    When I substituted s-js in my project for s-js/dist/withsubclocks, the signals from Surplus and the signals from S no longer belong to the same S, and so the dependency detection breaks. So, I tried several things to try fix it. First I tried Surplus.S = S, but then I found out the architecture for subclocks S is quite different from the regular S; surplus promptly complains because S doesn't have S.effect defined. Checking the source code, I assumed S.effect performs a similar function as a regular computation, so I tried adding Surplus.S.effect = S, but then performance basically dies, alas.

    Hope you could help clear things up! Cheers

    opened by irisjae 2
  • Question: Surplus + TypeScript - debugging experience (issue with source maps?)

    Question: Surplus + TypeScript - debugging experience (issue with source maps?)

    Hi, First I want to say thank you for so great library (both Surplus and s-js).

    I am trying to use Surplus together with TypeScript. And everything works fine except the debugging. It is almost not possible to set breakpoints in the .ts and .tsx files. It looks like the source maps are not properly updated. With .js and .jsx files everything works fine.

    This issue can be easily reproduced with original example in parcel-plugin-surplus. Just try to put breakpoint in index.tsx file.

    Do you have any suggestions how to improve/solve that?

    opened by MotorHeat 5
  • Fragments?

    Fragments?

    Hello, really nice library. I was just playing around and realized that fragments are not supported. Is there any way to implement views without a parent node?

    opened by xluk9 7
  • SArray.slice() versus slicing the update value

    SArray.slice() versus slicing the update value

    I'm currently playing around with S/Surplus because I really like its approach / philosophy. I have a keyed list implemented as Map. I want to show the entries of this map filtered and in a specific order. My idea is to use an SArray containing all keys to render the relevant entries only and in the desired order. In addition, I only want to show a reduced number of entries, but that can be extended by the user. Here's an (almost) minimal example:

    import * as Surplus from 'surplus';
    import SArray from 's-array';
    import data from 'surplus-mixin-data';
    import S from 's-js'
    
    var content = new Map([[0,"a"], [1,"b"], [2,"c"], [3,"d"]]);
    
    // initial view: reversely-ordered list, max. 3 entries shown
    const showInds = SArray(Array.from(content.keys()).reverse());
    var tableLength = S.data(3);
    
    // function to sort the list (alphabetically)
    const sortList = () => {
      let newInds = Array.from(content).sort().map(c => c[0]);
      showInds(newInds.slice(0,tableLength())); // <-- limiting number of entries here
    }
    
    const view =
      <div>
        <div>
          {showInds.map(key => // <-- mapping over the entire SArray
              <div onClick={ev => deleteOne(key)}>{content.get(key)}</div>)}
        </div>
        <button onclick={(ev) => sortList()}>sort</button>
        <input type="number" min="0" step="1" fn={data(tableLength)}/>
      </div>
    
    document.body.appendChild(view);
    

    That works fine, but a changed number-of-entries setting only takes effect when sorting the list again. So I thought to slice the SArray itself right before mapping over it in the view, i.e. in the sortList function, just use showInds(newInds); and, in the view, use

    showInds.slice(0,tableLength()).map(...
    

    This works fine before hitting the sort button. Once sorted, changing the number of entries to be shown (up or down) results in the runtime error

    image In main.js, that's on the last line of this code snippet:

        function reconcileArrays(parent, ns, us) {
            var ulen = us.length, 
            // n = nodes, u = updates
            // ranges defined by min and max indices
            nmin = 0, nmax = ns.length - 1, umin = 0, umax = ulen - 1, 
            // start nodes of ranges
            n = ns[nmin], u = us[umin], 
            // end nodes of ranges
            nx = ns[nmax], ux = us[umax], 
            // node, if any, just after ux, used for doing .insertBefore() to put nodes at end
            ul = nx.nextSibling, i, j, k, loop = true;
    

    Could very well be that I'm trying to do something in a way it's not meant to be done? However, I was surprised that the two seemingly equivalent solutions don't work the same. Is this a fundamental limitation of the core idea or a bug / missing feature?

    opened by asprionj 1
Owner
Adam Haile
Adam Haile
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
🖼️ Tiny JSX helper function for high-DPI (Retina Display) images.

img-srcx Tiny JSX helper function for high-DPI (Retina Display) images. 394 bytes minified and gzipped for ESM (830 bytes for ES5). Installation npm i

Chariz 4 Oct 6, 2022
GameLand is an online gaming web application that allows users to view different kind of games available and share their views on each game.

GameLand is an online gaming web application that allows users to view different kind of games available and share their views on each game.Users can like and make reservations to play online. Built with HTML/CSS , JAVASCRIPT,API.

tarike bouari 6 Sep 9, 2022
Crawler Crypto using NodeJS for performance with Elasticsearch DB for high efficiency.

Coin crawler - Coingecko version Crawler using NodeJS for performance with Elasticsearch DB for high efficiency. Requirements For development, you wil

Minh.N.Pham 1 Jan 20, 2022
High performance (de)compression in an 8kB package

fflate High performance (de)compression in an 8kB package Why fflate? fflate (short for fast flate) is the fastest, smallest, and most versatile pure

null 1.4k Dec 28, 2022
startupDB is an Express middleware function implementing a high-performance in-memory database

startupDB startupDB is a database designed to create REST APIs. It is implemented as an Express middleware function and allows for easy implementation

Jeroen de Vries 8 Jul 26, 2022
high performance、complex interaction table

功能描述 1、高性能、满足复杂交互的编辑表格 2、基于: antd4(https://ant.design/index-cn) ag-grid(https://www.ag-grid.com/) 3、基于原生ag-grid 的API进行封装 一、主要功能 将按下列顺序逐步迭代 1、通用编辑功能 ??

wheel-team 4 Feb 15, 2022
High performance JavaScript templating engine

art-template English document | 中文文档 art-template is a simple and superfast templating engine that optimizes template rendering speed by scope pre-dec

糖饼 9.7k Jan 3, 2023
🔑 Keagate is an open-source, high-performance alternative to popular cryptocurrency payment gateways such as Coinbase Commerce, CoinGate, BitPay, NOWPayments, CoinRemitter, CoinsPaid and more.

⛩️ Keagate – A High-Performance Cryptocurrency Payment Gateway ?? This project is actively in development ?? Table of Contents About the Project Purpo

null 76 Jan 3, 2023
An ultra-high performance stream reader for browser and Node.js

QuickReader An ultra-high performance stream reader for browser and Node.js, easy-to-use, zero dependency. Install npm i quickreader Demo import {Quic

EtherDream 156 Nov 28, 2022
The brand new @shopify/flash-list high performance list component can be used on TV as well as on phones!

FlashListTV The brand new @shopify/flash-list high performance list component can be used on TV as well as on phones! Quick start: Clone this repo Cha

Douglas Lowder 4 Oct 27, 2022
A TypeScript implementation of High-Performance Polynomial Root Finding for Graphics (Yuksel 2022)

Nomial Nomial is a TypeScript implementation of Cem Yuksel's extremely fast, robust, and simple root finding algorithm presented in the paper "High-Pe

Peter Boyer 10 Aug 3, 2022
High performance and SEO friendly lazy loader for images (responsive and normal), iframes and more, that detects any visibility changes triggered through user interaction, CSS or JavaScript without configuration.

lazysizes lazysizes is a fast (jank-free), SEO-friendly and self-initializing lazyloader for images (including responsive images picture/srcset), ifra

Alexander Farkas 16.6k Jan 1, 2023
👑 A tiny yet powerful tool for high-performance color manipulations and conversions

Colord is a tiny yet powerful tool for high-performance color manipulations and conversions. Features ?? Small: Just 1.7 KB gzipped (3x+ lighter than

Vlad Shilov 1.2k Jan 3, 2023
A scalable, high-performance feature management and progressive experimentation platform

Introduction & Our Philosophy FeatBit is a scalable, high-performance Feature Management and Progressive Experimentation platform. Feature Management

null 345 Jan 1, 2023
High performance personalization & a/b testing example using Next.js, Edge Middleware, and Builder.io

Next.js + Builder.io Personalization & A/B Testing with Edge Middleware This is a fork of Next.js Commerce with Builder.io integrated and using Edge M

Builder.io 7 Sep 6, 2022
A simple yet feature-rich counter that allows you to count the views of your Github README files and profile page

View Counter Purpose This is a simple yet feature-rich counter that allows you to count the views of your github README files and profile page. Featur

Toby Hagan 4 Nov 10, 2022