adamhaile / surplus Goto Github PK
View Code? Open in Web Editor NEWHigh performance JSX web views for S.js applications
High performance JSX web views for S.js applications
I figured out that I can't do this:
<Callout slash-color="light" text-color="dark">
This is some copy text.
</Callout>
Since I'm used to working with Vue, I figured that either these attributes would be mapped to:
{Callout({ slashColor: "light", textColor: "dark", children: [ "This is some copy text" ] })}
OR
{Callout({ "slash-color": "light", "text-color": "dark", children: [ "This is some copy text" ] })}
.
Instead, Surplus does neither. It fails parsing because of the hyphens. ๐ข The main reason I wanted to do this is to reflect attributes in the rendered DOM depending on hyphenated value. But the other reason to do it is to keep the markup consistent. Camelcase attributes look wrong. If I had a preference, I might choose the former, but really either option other than failing parsing would work for me.
I'm working on an 0.5 release, but haven't been particularly transparent about what that entails, so this is a feature list and punchlist for what remains on the 0.5 release.
New features.
{...fn}
are now split off to the fn={...}
property. {... }
is now only for spread properties. I'm not thrilled with the fn=
syntax, but it's important to have an explicit split b/w spread objects and functions, and this is the best option I could construct while maintaining JSX syntax. Other opinions welcome. B/c this is a breaking change, I'm bumping the version number to 0.5.To do:
Could you explain a bit what does the compiler do, why is it needed, etc?
As the last (I believe) breaking API change before 0.5 goes to release, I've changed the behavior of the props.children property passed to subcomponents. Previously, Surplus always created an array with as many elements as there were children. Now it behaves like props.children in React, where if there are no children, props does not contain a 'children' property, if there is one, then it contains that one child, and if there are more than one, then it contains an array of all children.
<Foo /> // props has no 'children' property
<Foo>1</Foo> // props.children === 1
<Foo>1<span>2</span>3</Foo> // props.children === [1, <span>2</span>, 3]
This has the advantage of a) being consistent with React, b) being consistent with all React-conforming tools, like Typescript, which expect this behavior, c) avoiding creating an array when not strictly necessary. The downside is that props.children becomes polymorphic, in that it may be undefined, a single element, or an array.
Also, previously any 'children' attribute took precedence over the actual JSX children, so <Foo children={1}>2</Foo>
would resolve to props.children === 1
. Now precedence is like React, in that JSX children take precedence, and props.children === 2
.
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);
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?
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.
The Surplus parser failed with this HTML:
<svg version="1.1"
class={ $style.callout__quotation }
x="0px"
y="0px"
viewBox="0 0 48 42.1"
hidden={ hideQuotes }
>
<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>
I discovered that it couldn't handle properties on multiple lines, which was unexpected. The following parsed fine:
<svg version="1.1" class={ $style.callout__quotation } x="0px" y="0px" viewBox="0 0 48 42.1" hidden={ hideQuotes }>
<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>
In the little README.md todo demo the ร entity shows as ×
in a text node. This also happens in my own Surplus code. My workaround has been to substitute ×
with {String.fromCharCode(255)}
etc. so it's pretty low priority.
Todomvc was very useful for me. Thanks for @adamhaile
Surplus and S.js made me very excited. I think this is the best frontend component library for me and others. It allready fastest
I found very few bugs after recent commits.
In the example below App1 and App2 must be the same.
https://facebook.github.io/react/docs/jsx-in-depth.html#spread-attributes
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = { firstName: 'Ben', lastName: 'Hector' };
return <Greeting {...props} />;
}
Here is the rendering result. App2 rendering incomplete.
function App1() {
return Greeting({
firstName: "Ben",
lastName: "Hector",
children: [
]});
}
function App2() {
var props = { firstName: 'Ben', lastName: 'Hector' };
return Greeting({
children: [
]});
}
Using styles like:
<div style={{ width: '100%' } as CSSStyleDeclaration}>test</div>
throws Uncaught TypeError: Surplus.staticStyle is not a function
in runtime.
And this input style.width="50%" screenshot is following in vscode editor.
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.
Given something like this:
return <div class={ $style.host } {...attr}>
<div class={ $style.callout__content }>
<svg version="1.1" class={ $style.callout__quotation } x="0px" y="0px" viewBox="0 0 48 42.1" hidden={ hideQuotes }>
<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>
<p class={ $style.callout__copy + ' d3' }>
{ children }
</p>
</div>
</div>
The actual output is:
return (function () {
var __, __div1, __div1_svg1, __div1_svg1_polygon1, __div1_svg1_polygon2, __div1_p2, __div1_p2_insert1;
__ = createElement('div');
__div1 = createElement('div');
__div1_svg1 = createElement('svg');
__div1_svg1.version = "1.1";
__div1_svg1.x = "0px";
__div1_svg1.y = "0px";
__div1_svg1.viewBox = "0 0 48 42.1";
__div1_svg1_polygon1 = createElement('polygon');
__div1_svg1_polygon1.points = "48,0 48,11.6 39.6,19.5 48,19.5 48,42.1 27.5,42.1 27.5,19.7";
appendChild(__div1_svg1, __div1_svg1_polygon1);
__div1_svg1_polygon2 = createElement('polygon');
__div1_svg1_polygon2.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";
appendChild(__div1_svg1, __div1_svg1_polygon2);
appendChild(__div1, __div1_svg1);
__div1_p2 = createElement('p');
__div1_p2_insert1 = createTextNode('');
appendChild(__div1_p2, __div1_p2_insert1);
appendChild(__div1, __div1_p2);
appendChild(__, __div1);
__div1_svg1.class = $style.callout__quotation ;
__div1_svg1.hidden = hideQuotes ;
insert({ start: __div1_p2_insert1, end: __div1_p2_insert1 }, children );
__div1_p2.class = $style.callout__copy + ' d3' ;
__div1.class = $style.callout__content ;
__.class = $style.host ;
S(function (__state) { return (attr)(__, __state); });
return __;
})()
With this output, no attributes are actually created. This seems completely contrary to the documentation which is that a) "Surplus allows class and for as aliases for the className and htmlFor properties", which doesn't seem to happen (it sets an arbitrary class
property), and b) there's no real support for SVG, which does not have hyphenated properties. If only hyphenated values get turned into attributes, how is that useful at all?
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();
}
Hi there, nice approach, I'm just trying it out...
After some trouble to start I made the example in the README of S.js work except by the ×
, it shows exactly like that and not as "ร".
Anyway, I went forward and moved to 0.5 beta and now the data bindings for the inputs are not working anymore!!! the only change was the library version!
Thanks, and again, congratulations! I hope it does thrive!
1 import S from 's-js';
2 import SArray from 's-array';
3 import * as Surplus from 'surplus';
4 import data from 'surplus-mixin-data';
5
6 var Todo = t => ({ // our Todo constructor
7 title: S.data(t.title), // properties are data signals
8 done: S.data(t.done)
9 }),
10 todos = SArray([]), // our array of todos
11 newTitle = S.data(""), // title for new todos
12 addTodo = () => { // push new title onto list
13 todos.push(Todo({ title: newTitle(), done: false }));
14 newTitle(""); // clear new title
15 },
16 view = S.root(() =>
17 <div> // declarative main view
18 <input type="text" {...data(newTitle)}/>
19 <a onClick={addTodo}>+</a>
20 {todos.map(todo => // insert todo views
21 <div>
22 <input type="checkbox" {...data(todo.done)}/>
23 <input type="text" {...data(todo.title)}/>
24 <a onClick={() => todos.remove(todo)}>×</a>
25 </div>)}
26 </div>);
27
28 window.onload = () => {
29 document.body.appendChild(view); // add view to document
30 };
1 import minify from 'rollup-plugin-babel-minify';
2 import nodeResolve from 'rollup-plugin-node-resolve';
3 import surplus from 'rollup-plugin-surplus';
4
5 export default {
6 entry: 'main.js',
7 dest: 'bundle.js',
8 'format': 'iife',
9 'plugins': [
10 surplus(),
11 process.env.ENV === "production" ? minify({
12 "comments": false,
13 "sourceMap": false,
14 }) : undefined,
15 nodeResolve(),
16 ].filter(x => x !== undefined),
17 }
import { MdButton } from "md-components/button";
export = (ctrl: any) =>
<MdButton>Button 1</MdButton>
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.
In the virtual dom environment component life cycle events can be easier. But surplus is different. At least component attached to DOM and component removed from DOM events is required in real world.
I have developed a solution for now but mozilla this page says it unperformed. Because DOMNodeRemoved is deprecated and performance problematic. https://github.com/SilentCicero/throw-down is may be another solution.
I think it would be better to solve this problem in the surplus infrastructure.
I'm doing some material design components experiments right now.
import * as Surplus from 'surplus';
Surplus;
const lifeCycle = (options: { created: (node: HTMLElement) => void, removed: (node: HTMLElement) => void }): (node: HTMLElement) => void => {
return (node) => {
setTimeout(() => {
options.created.call(options, node)
node.parentElement.addEventListener("DOMNodeRemoved", (e) => {
options.removed.call(options, node)
})
})
}
}
const buttonCreated = (node: HTMLElement) => {
this.ripple = window["mdc"].ripple.MDCRipple.attachTo(node);
}
const buttonRemoved = (node: HTMLElement) => {
this.ripple.destroy();
}
export const MdButton = (props) => {
return <button className="mdc-button mdc-ripple-surface" {...lifeCycle({ created: buttonCreated, removed: buttonRemoved }) }>
{props.children[0]}
</button>
}
Something like a basic todo list example or something would be awesome.
Hi. Your project looks really great. The S package is super performant -- congrats!
We're somewhat stuck on the Handlebars approach to templating, and were wondering if your architecture support Handlebars instead of/in addition to JSX for the templates?
For example, incremental-bars implements Handlebars but uses incremental-dom as its backend, instead of strings.
But I like your approach of going straight to the DOM. It feels similar to Ractive , which has the Handlebars goodness, but, alas, not the speed of S
.
I guess one possibility would be for Ractive to leverage S in place of its current data/computed model? But another possibility would be for Surplus to offer Handlebars support.
Of course, we could just learn JSX instead... :-)
> require('surplus');
{ insert: [Function: insert$$1],
S:
{ [Function: S]
root: [Function: root],
on: [Function: on],
data: [Function: data],
value: [Function: value],
freeze: [Function: freeze],
sample: [Function: sample],
cleanup: [Function: cleanup] },
createElement: [Function: createElement],
createComment: [Function: createComment],
createTextNode: [Function: createTextNode],
appendChild: [Function: appendChild] }
> require('surplus/compiler');
Error: Cannot find module 'surplus/compiler'
at Function.Module._resolveFilename (module.js:536:15)
at Function.Module._load (module.js:466:25)
at Module.require (module.js:579:17)
at require (internal/module.js:11:18)
at repl:1:1
at ContextifyScript.Script.runInThisContext (vm.js:50:33)
at REPLServer.defaultEval (repl.js:240:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:441:10)
> require('surplus/compiler');
Error: Cannot find module 'surplus/compiler'
at Function.Module._resolveFilename (module.js:536:15)
at Function.Module._load (module.js:466:25)
at Module.require (module.js:579:17)
at require (internal/module.js:11:18)
at repl:1:1
at ContextifyScript.Script.runInThisContext (vm.js:50:33)
at REPLServer.defaultEval (repl.js:240:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:441:10)
>
```
Hi @adamhaile https://github.com/adamhaile/surplus/blob/master/src/preprocessor/compile.ts#L167 is problematic. As a possibility https://github.com/adamhaile/surplus/blob/master/src/preprocessor/compile.ts#L312
Currently textField is considered to svg tag.
Because /text/.test("textField")
returns true. Usage should be as follows.
function exactTest (rx, str) {
var ex = rx.exec(str)
return ex != null && ex[0] === str
}
exactTest(SvgOnlyTagRx, tag)
It looks to me like the most important runtime functions in this project are in dom.ts
. Looking at https://github.com/adamhaile/surplus-todomvc/blob/master/dist/main.js it does not seem to use functions from any of the other modules in this repository.
I am curious if anyone thought about how this wonderful library might be used with custom elements, etc. I am thinking in comparison to react, as in a few articles I found:
Surplus' original syntax did not have spread expressions, but to support easy transition to/from JSX, I intend to add them.
Three's a design question regarding how spreads should work in a "real DOM" scenario: spreads make perfect sense with vdom, as we're constructing a property object, which spread expressions extend. But in surplus, we're building real DOM nodes and setting their properties directly, so there is no property object to start with.
Another design question is how to support what were previously called "mixins" in Surplus: expressions that return a function which is then called with the current node.
The intended design is to support both vdom-style property objects and mixin-style functions by:
providing runtime support to translate vdom-style property objects into node property sets. This will likely be a new method, Surplus.spread(...)
. It will need to account for runtime property name translation, to support things like onClick -> onclick, which is currently done at compile time.
supporting mixins by making Surplus.spread(...)
check whether it's being passed a property object (vdom-like translation) or a function (mixin-style call-with-node).
Tasks:
Hi @adamhaile now I see that {...mixin()}
changes to fn={mixin}
. But this version seems to not typescript friendly!
Example code:
<a fn={mixin1} fn={mixin2} />
This error can be fix with editing interface IntrinsicElements
TS2559: Type '{ fn: (el: any) => string; }' has no properties in common with type 'HTMLAttributes<HTMLAnchorElement>'.
But this seems to be a bigger problem.
TS17001: JSX elements cannot have multiple attributes with the same name
First, thanks for sharing. I'm very appreciate and use your code in own projects.
Now, JSX compiler emits nodes with perfectly readable code
view = function () {
var __, ...
__ = createElement(...
...
return __;
}() // here it call function end evaluate element. I'd like to remove this call.
I can mount view document.body.appendChild(view)
only once because of DOM rules
If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position (there is no requirement to remove the node from its parent node before appending it to some other node).
Maybe will be meaningful to leave view() been a function but not evaluate it. This allow instantiate document.body.appendChild(view())
any times needed. Also it allows to easily write functional components
const Component = ((props) => <div>{props.content}<div>)
and use em like a template
var view = <div>
<Component content={S(some_computation)}>
</div>;
If you agree this can make sense, than I can pull request, or you can do it yourself - just remove )()
call in compiler's codegen(). I done it and test in my local repo and things work as expected.
In general I think this is really awesome work and will do as much as I can (within reason) to promote it.
That said, I suspect it should be possible to take advantage of more widely used JSX processors such as Acorn JSX (https://github.com/RReverser/acorn-jsx) or perhaps Babel transform-react-jsx. More widely used, better documented, etc.
I would be happy to give it a try when I get a chance.
It would also be cool to support something like https://github.com/choojs/hyperx (which can be interpreted or compiled) or adaptation of https://github.com/hyperapp/html API for those who may want a more pure JavaScript API (maybe better for CodePen as well).
I really like the concept of this library! Great work!
One thing that came to mind was that the inline dom statements might quickly add up because they're not minified. Is that something to worry about? If so is there a way to wrap those in minify-able helper functions?
Example minified code snippet of the TODO MVC:
c=function(n){return function(){var e,c,s,f,d,p,h,m,v,g,w,y,C,E,S,x,b,N,T,k,A,O,L,M,P,R,j,B,F,_,z;return e=r.createElement("section"),c=r.createElement("section"),c.className="todoapp",s=r.createElement("header"),s.className="header",f=r.createElement("h1"),f.innerText="todos",r.appendChild(s,f),d=r.createElement("input"),d.className="new-todo",d.placeholder="What needs to be done?",r.appendChild(s,d),r.appendChild(c,s),p=r.createElement("section"),p.className="main",h=r.createElement("input"),h.className="toggle-all",h.type="checkbox",r.appendChild(p,h),m=r.createElement("label"),m.htmlFor="toggle-all",m.innerText="Mark all as complete",r.appendChild(p,m),v=r.createElement("ul"),v.className="todo-list",g=r.createTextNode(""),r.appendChild(v,g),r.appendChild(p,v),r.appendChild(c,p),w=r.createElement("footer"),w.className="footer",y=r.createElement("span"),y.className="todo-count",C=r.createElement("strong"),E=r.createTextNode(""),r.appendChild(C,E),r.appendChild(y,C),r.appendChild(y,r.createTextNode(" item")),S=r.createTextNode(""),r.appendChild(y,S),r.appendChild(y,r.createTextNode(" left")),r.appendChild(w,y),x=r.createElement("ul"),x.className="filters",b=r.createElement("li"),N=r.createElement("a"),N.href="#/",N.innerText="All",r.appendChild(b,N),r.appendChild(x,b),T=r.createElement("li"),k=r.createElement("a"),k.href="#/active",k.innerText="Active",r.appendChild(T,k),r.appendChild(x,T),A=r.createElement("li"),O=r.createElement("a"),O.href="#/completed",O.innerText="Completed",r.appendChild(A,O),r.appendChild(x,A),r.appendChild(w,x),L=r.createElement("button"),L.className="clear-completed",L.innerText="Clear completed",r.appendChild(w,L),r.appendChild(c,w),r.appendChild(e,c),r.appendChild(e,r.createTextNode(", ")),M=r.createElement("footer"),M.className="info",P=r.createElement("p"),P.innerText="Double-click to edit a todo",r.appendChild(M,P),R=r.createElement("p"),R.innerText="Template by ",j=r.createElement("a"),j.href="http://sindresorhus.com",j.innerText="Sindre Sorhus",r.appendChild(R,j),r.appendChild(M,R),B=r.createElement("p"),B.innerText="Created by ",F=r.createElement("a"),F.href="https://github.com/adamhaile",F.innerText="Adam Haile",r.appendChild(B,F),r.appendChild(M,B),_=r.createElement("p"),_.innerText="Part of ",z=r.createElement("a"),z.href="http://todomvc.com",z.innerText="TodoMVC",r.appendChild(_,z),r.appendChild(M,_),r.appendChild(
Hi there, got some time to work on my little project again... ๐
And I think I found some kind of a bug!
With this code (boilerplate removed):
<input type="text" fn={ data(login.user, "keyup") } />
<input type="password" fn={ data(login.password, "keyup") } />
If I type admin
as user and then use the tab
key I get only admi
in login.user
, it happens in more than half the tries (if you just type without pauses). The input field has the full admin
though.
Thanks, Grieb
Will you consider turning Sur+ into a mono repo with something like Lerna?
Happy to help.
I had a hidden attribute like hidden={ hideQuotes }
. I knew in my demo app that hideQuotes would default to undefined, which I hoped (spoiled by Vue and Polymer, I know) would just remove the hidden
attribute.
I tried hidden={ hideQuotes }
(casting to boolean), but ended up with hidden="false"
which, in HTML, means hidden == true
in terms of the property. HTML specifies that boolean attributes should be absent when false, but would be nice if they were also absent when falsey.
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.
Hi there,
I just found something different from mithril.js! It's probably by design but I would like your suggestion for a workaround...
I have a input field, normal fn={ data(Svalue) }
, and it has some computations to show a error message underneath it when it's empty.
Using Surplus, after something is entered on the input field the message only goes away after some change in focus.
Using mithiril.js the message goes away when the first character is entered.
Is there a way to make Surplus behave like mithril.js?
Thanks, Grieb
I have an
The spellCheck attribute is being omitted.
When I look through both the generated code for the minimalist ToDos example in CodePen [with help from console.log(comple(...))] and surplus-todomvc (https://github.com/adamhaile/surplus-todomvc/blob/master/dist/main.js) I do not see where the code from content.ts
would actually be used. Any pointers?
I also noticed an internal reconcileArrays
function in content.js
, unfortunately do not really understand the purpose; wondering if it may suffer from VDOM-like performance issues under certain circumstances. It would be great if you can explain this a little and maybe document how we can avoid this? Any chance we can move the array processing into another module or perhaps find a way to get rid of it?
An example with 0.5.0-beta1:
const dv = (
<div fn={() => {}}>
1<br />
2<br />
</div>
);
console.log(dv.outerHTML); // output is <div> 1</div>
But same example without fn it outputs correctly.
const dv = (
<div>
1<br />
2<br />
</div>
);
console.log(dv.outerHTML); // output is <div> 1<br> 2<br></div>
Hi @adamhaile, me again...
I have just stumbled with a situation where I need some html entities, I need to include a
to get tings on the correct place...
I know why it doesn't work: it is considered as text and all text nodes are created with document.createTextNode(value)
which does treat everything as pure text...
I tried using unicode, \u00A0
, but them the slash got escaped as it should be... If I insert the unicode char it simple vanishes during compilation...
Any suggestions/ideas?
Thanks,
Grieb.
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.
Hey guys,
Awesome library, just wondering what the browser support is like?
Do Surplus and S require any polyfills for old browsers?
Thanks
Hello!
I've recently started using Surplus and it's really great! But I think I've found a bug in the compiler:
// controller.ts
export function update(text : string) : string {
return text.replace(/<br>+/g, '\n');
}
// view.tsx
export const TestEditor = () => {
const text = S.data('Hello'),
onkeyup = ({ target: { value } } : any) => {
text(clean(value));
};
return (
<div>
Text is: {text()}
<input value={text()} onKeyUp={onkeyup} />
</div>
);
}
// main.ts
S.root(() => {
const testEditor = TestEditor();
document.body.insertAdjacentElement('afterbegin', testEditor);
});
This produces the error:
ERROR in ./public/js/controller.ts
Module build failed: Error: element missing close tag at line 19 col 25: ``
+/g, '\n');}
''
at ERR (/node_modules/surplus/compiler/index.js:354:15)
at jsxElement (/node_modules/surplus/compiler/index.js:162:17)
at program (/node_modules/surplus/compiler/index.js:91:31)
at parse (/node_modules/surplus/compiler/index.js:83:12)
at Object.compile (/node_modules/surplus/compiler/index.js:1444:45)
at Object.preprocess (/node_modules/surplus-loader/index.js:7:33)
@ ./public/js/main.ts 3:7-49
I think the problem lies in the surplus-loader. Compiling the regex with Babel works fine when I test that!
Edit: I'm running the 0.5 beta of surplus-loader.
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)}>×</a>
</div>
)}
</div>;
});
document.body.appendChild(view); // add view to document
This works
const Footer = (props: { leftCount, children?}) => {
return <div>{props.leftCount()}</div>
}
But this is not working. Error: element missing close tag at line......
DataSignal<number>
is confused with tags.
const Footer = (props: { leftCount:DataSignal<number>, children?}) => {
return <div>{props.leftCount()}</div>
}
First of all this is a really cool project, thanks for making it open source. I think I may have stumbled upon a bug in the compiler.
I am trying to put together a SSR'd surplus project using undom and I use es6 template literal in the render.jsx that I am passing to the surplus-loader. I am going to work around this by making the template literal into a function and moving it into it's own file, so it's not a show stopper but I thought you might be interested to know about this behavior none the less. Below is the error message I got from the build.
ERROR in ./src/server/render.jsx
Module build failed: Error: unexepected value for JSX property at line 118 col 17: ``en> <head>
<m''
at ERR (/mnt/data/Work/github/andyrj/idiom/node_modules/surplus-preprocessor/index.js:443:15)
at property (/mnt/data/Work/github/andyrj/idiom/node_modules/surplus-preprocessor/index.js:311:20)
at htmlElement (/mnt/data/Work/github/andyrj/idiom/node_modules/surplus-preprocessor/index.js:217:33)
at codeTopLevel (/mnt/data/Work/github/andyrj/idiom/node_modules/surplus-preprocessor/index.js:185:31)
at parse (/mnt/data/Work/github/andyrj/idiom/node_modules/surplus-preprocessor/index.js:177:12)
at Object.preprocess (/mnt/data/Work/github/andyrj/idiom/node_modules/surplus-preprocessor/index.js:846:45)
at Object.preprocess (/mnt/data/Work/github/andyrj/idiom/node_modules/surplus-loader/index.js:7:37)
@ ./src/server/index.js 13:0-30
@ multi ./src/server
And here is a snippet from the code causing the above in the compiler.
const route = router.match(path);
const View = route.component;
document.body.appendChild(<View params={route.params} />);
/* eslint-disable */
const html = `
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset="utf-8" />
<meta name="referrer" content="origin" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${meta.title}</title>
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Slab:400,700|Material+Icons" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css" />
${styles}
${meta.reduce((acc, val) => acc + val)}
<script src='${vendor}' defer></script>
<script src='${main}' defer></script>
<script id="state" type="application/json">${JSON.stringify(state)}</script>
</head>
${serialize(document.body)}
</html>`;
Even though I recognize the performance hit I was trying surplus behind ES5 Getter/Setters and ES6 Proxies and I noticed certain dependencies would just get skipped. It looked like the compiled templates would be slightly different whether the parenthesis were there or not.
I'm probably doing something terribly bad to performance by trying this. I just come from a Knockout background (used in production for about 7 years, training new devs etc). It's been clear to me for quite some time that while fine grained change dependency tracking is the superior approach(not knockout specifically) the api/syntax and the need to keep track of nested mapping prevents it from ever gaining a ton of traction. MobX the other large library with this sort of approach is still typically used with React and trapped by the fact updates are still per component, making it always limited by React and hence slower than it.
It's my belief that while having explicit computational wrappers is a must here, in the whole scheme of larger projects it's much cleaner than tracing through layers of shouldComponentUpdate and other very procedural lifecycle functions. However having data look like anything but POJO's is unacceptable.
I was very happy to have discovered this project 6 months back as none of my approaches I've worked on the last couple years beat out the fastest virtual dom ones across the board but I knew it should be possible. While it's likely the overhead of both the Surplus syntax and the proxying is less performant than just putting the logic in the proxying method itself, I was curious to try it for comparison sake.
In any case this project has been an inspiration and I learn something every time I look at the source code.
I have this object
const attributes = { "slash-color": "light", "text-color": "dark"}
return <div class={ $style.host } {...attributes}>...</div>
However, Surplus throws the error in the browser "attributes is not a function". Is this because it has to be S.data? In the example I see:
var props = { type: "text" },
input3 = <input {...props} />;
But this doesn't seem to work as expected.
Hi @adamhaile, yes, me again... ๐
Anyway, now that you have support for html entities it would be nice to remove all useless spaces and blanks (like new lines) from text nodes.
An example:
[...]
<label className="form-checkbox">
<input type="checkbox" fn={ data(user.enabled) } />
<i className="form-icon" />
Active
</label>
[...]
Generates something like:
[...]
__div2_form2_div1_div3_label1 = createElement('label', "form-checkbox", __div2_form2_div1_div3);
__div2_form2_div1_div3_label1_input1 = createElement('input', null, __div2_form2_div1_div3_lab);
__div2_form2_div1_div3_label1_i2 = createElement('i', "form-icon", __div2_form2_div1_div3_label1);
createTextNode('\
Active\
', __div2_form2_div1_div3_label1);
[...]
And "minifyed" gives something like:
[...]A=r("label","form-checkbox",y),B=r("input",null,A),D=r("i","form-icon",A),s(" Active ",A)[...]
Is it clear what I mean? At least the spaces at the beginning and end of the string should be remove as they serve nothing in the html and in the source they are only for readability. Ideally all sequences of multiple spaces could be reduced to a single space if not at the beginning nor end.
There is a exception though, <pre>
tags... Or is there some other reason not to remove spaces?
If you would like I can have a go to implement this...
Thanks,
Grieb.
Looks like an awesome library, would it be possible to provide some "hello-world" examples in CodePen or JSFiddle so you could experiment a little? Also a minified version on cdnjs that works directly in the browser?
Use npm prepare script to generate needed objects before publishing. For example:
I would be happy to help with this one if you like. Maybe within the next 1-2 weeks.
HI Adam again. Last I've mentioned to you, that I'd have to write a small utility to convert svg to jsx, which would then be converted by surplus. So I went and looked, and lo, the React community had already written such a thing. And happily, I went and wrote a little function to translate svg to jsx, then jsx to surplus. However... it doesn't render right! I suspect the reason is because of unrecognized SVG attributes. Let me illustrate.
Let's say I start with this SVG:
<svg viewBox="0 0 100 100">
<line x1="0" y1="50" x2="100" y2="50" stroke="black" stroke-width="100"></line>
</svg>
It's a pretty idiotic way to make a black square.
Now I run it through JSX:
<svg viewBox="0 0 100 100">
<line x1={0} y1={50} x2={100} y2={50} stroke="black" strokeWidth={100} />
</svg>
All good. But look again! The stroke-width
is now strokeWidth
! Turns out (scroll to the bottom) (another link) that SVG attributes are "supported" by JSX. Which seems to mean that the canonical attribute version is camelCased (and thus, basically all svg attributes involving a dash are inflicted). Here is the place where surplus isn't supporting it, and if you run the surplus .compile ()
(0.5.0-beta11) through this, it becomes:
(function () {
var __, __line1;
__ = Surplus.createSvgElement('svg', null, null);
Surplus.setAttribute(__, "viewBox", "0 0 100 100");
__line1 = Surplus.createSvgElement('line', null, __);
Surplus.setAttribute(__line1, "x1", 0);
Surplus.setAttribute(__line1, "y1", 50);
Surplus.setAttribute(__line1, "x2", 100);
Surplus.setAttribute(__line1, "y2", 50);
Surplus.setAttribute(__line1, "stroke", "black");
Surplus.setAttribute(__line1, "strokeWidth", 100);
return __;
})()
Which renders to
<svg viewBox="0 0 100 100">
<line x1="0" y1="50" x2="100" y2="50" stroke="black" strokeWidth="100"></line>
</svg>
Just a meager line (strokeWidth is not an svg attribute and has no effect).
I'll see if I have time for a PR.
(By the way, turns out the JSX spec seems to say namespaced attributes go with colons... except that the rest of the community and React say they go camelCased.)
Hi again @adamhaile!
If I have an element like:
<input type="checkbox" fn={ data(user.enabled) } onChange={ () => user.setEnabled(vm.setEnableState) } />
Is there any guaranties that fn
updates the value before onChange
is fired?
Thanks, Grieb.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.