snabbdom can be used with jsx/tsx via jsxFactory: jsx

pull/451/head
Noj Vek 5 years ago
parent 5fd1cec46c
commit 7ff8d13b4e

7
.gitignore vendored

@ -45,6 +45,9 @@ node_modules
/es/is.d.ts
/es/is.js
/es/is.js.map
/es/jsx.d.ts
/es/jsx.js
/es/jsx.js.map
/es/snabbdom.bundle.d.ts
/es/snabbdom.bundle.js
/es/snabbdom.bundle.js.map
@ -101,6 +104,10 @@ node_modules
/is.d.ts
/is.js
/is.js.map
/jsx.d.ts
/jsx-global.d.ts
/jsx.js
/jsx.js.map
/snabbdom.bundle.d.ts
/snabbdom.bundle.js
/snabbdom.bundle.js.map

@ -30,12 +30,15 @@ module.exports = function(config) {
hostname: ci ? ip : 'localhost',
preprocessors: {
'src/**/*.ts': ['karma-typescript'],
'test/**/*.js': ['karma-typescript'],
'test/**/*.{js,ts,tsx}': ['karma-typescript'],
},
browserStack: {
name: 'Snabbdom',
retryLimit: 3,
},
client: {
captureConsole: true,
},
browserNoActivityTimeout: 1000000,
customLaunchers: browserstack,
karmaTypescriptConfig: {

53
package-lock.json generated

@ -98,6 +98,12 @@
"through2": "^2.0.3"
}
},
"@types/assert": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/assert/-/assert-1.4.3.tgz",
"integrity": "sha512-491hfOvNr0+BGOHT2m36xJ+LK68IuOshvxV0VIrKOnzBDL11WlDa3PwO+drTYkwCdfzJRN9REcDPZVVcrx1ucw==",
"dev": true
},
"@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@ -110,6 +116,12 @@
"integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
"dev": true
},
"@types/mocha": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
"dev": true
},
"@types/normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@ -3283,8 +3295,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -3305,14 +3316,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -3327,20 +3336,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -3457,8 +3463,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -3470,7 +3475,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -3485,7 +3489,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -3493,14 +3496,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -3519,7 +3520,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -3600,8 +3600,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -3613,7 +3612,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -3699,8 +3697,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -3736,7 +3733,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -3756,7 +3752,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -3800,14 +3795,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},

@ -10,6 +10,8 @@
"test": "test"
},
"devDependencies": {
"@types/assert": "^1.4.3",
"@types/mocha": "^5.2.7",
"@typescript-eslint/eslint-plugin": "^2.7.0",
"benchmark": "^2.1.4",
"browserify": "^14.4.0",

@ -1,6 +1,6 @@
import {vnode, VNode, VNodeData} from './vnode';
export type VNodes = Array<VNode>;
export type VNodeChildElement = VNode | string | number | undefined | null;
export type VNodeChildElement = VNode | string | number | boolean | undefined | null;
export type ArrayOrElement<T> = T | T[];
export type VNodeChildren = ArrayOrElement<VNodeChildElement>
import * as is from './is';
@ -18,17 +18,17 @@ function addNS(data: any, children: VNodes | undefined, sel: string | undefined)
}
export function h(sel: string): VNode;
export function h(sel: string, data: VNodeData): VNode;
export function h(sel: string, data: VNodeData | null): VNode;
export function h(sel: string, children: VNodeChildren): VNode;
export function h(sel: string, data: VNodeData, children: VNodeChildren): VNode;
export function h(sel: string, data: VNodeData | null, children: VNodeChildren): VNode;
export function h(sel: any, b?: any, c?: any): VNode {
var data: VNodeData = {}, children: any, text: any, i: number;
if (c !== undefined) {
data = b;
if (b !== null) data = b;
if (is.array(c)) { children = c; }
else if (is.primitive(c)) { text = c; }
else if (c && c.sel) { children = [c]; }
} else if (b !== undefined) {
} else if (b !== undefined && b !== null) {
if (is.array(b)) { children = b; }
else if (is.primitive(b)) { text = b; }
else if (b && b.sel) { children = [b]; }

@ -0,0 +1,14 @@
import {VNode, VNodeData} from './vnode';
declare global {
/**
* opt-in jsx intrinsic global interfaces
* see: https://www.typescriptlang.org/docs/handbook/jsx.html#type-checking
*/
namespace JSX {
type Element = VNode;
interface IntrinsicElements {
[elemName: string]: VNodeData;
}
}
}

@ -0,0 +1,39 @@
import {vnode, VNode, VNodeData} from './vnode';
import {h, VNodeChildren} from './h';
export type FunctionComponent = (props: {[prop: string]: any} | null, children?: VNode[]) => VNode;
function flattenAndFilter(children: VNodeChildren[], flattened: VNode[]): VNode[] {
for (const child of children) {
// filter out falsey children, except 0 since zero can be a valid value e.g inside a chart
if (child !== undefined && child !== null && child !== false && child !== '') {
if (Array.isArray(child)) {
flattenAndFilter(child, flattened);
} else if (typeof child === 'string' || typeof child === 'number' || typeof child === 'boolean') {
flattened.push(vnode(undefined, undefined, undefined, String(child), undefined));
} else{
flattened.push(child);
}
}
}
return flattened;
}
/**
* jsx/tsx compatible factory function
* see: https://www.typescriptlang.org/docs/handbook/jsx.html#factory-functions
*/
export function jsx(tag: string | FunctionComponent, data: VNodeData | null, ...children: VNodeChildren[]): VNode {
const flatChildren = flattenAndFilter(children, []);
if (typeof tag === 'function') {
// tag is a function component
return tag(data, flatChildren);
} else {
if (flatChildren.length == 1 && !flatChildren[0].sel && flatChildren[0].text) {
// only child is a simple text node, pass as text for a simpler vtree
return h(tag, data, flatChildren[0].text);
} else {
return h(tag, data, flatChildren);
}
}
}

@ -67,6 +67,12 @@ describe('snabbdom', function() {
var vnode = h('a', {}, 'I am a string');
assert.equal(vnode.text, 'I am a string');
});
it('can create vnode with null props', function() {
var vnode = h('a', null);
assert.deepStrictEqual(vnode.data, {});
vnode = h('a', null, ['I am a string']);
assert.equal(vnode.children[0].text, 'I am a string');
});
it('can create vnode for comment', function() {
var vnode = h('!', 'test');
assert.equal(vnode.sel, '!');

@ -0,0 +1,118 @@
import * as assert from 'assert';
import {jsx} from '../src/jsx';
describe('snabbdom', function() {
describe('jsx', function() {
it('can be used as a jsxFactory method', function() {
const vnode = <div title="Hello World">Hello World</div>;
assert.equal(JSON.stringify(vnode), JSON.stringify({
sel: 'div',
data: {title: 'Hello World'},
text: 'Hello World',
}));
});
it('creates text property for text only child', function() {
const vnode = <div>foo bar</div>;
assert.equal(JSON.stringify(vnode), JSON.stringify({
sel: 'div',
data: {},
text: 'foo bar',
}));
});
it('creates an array of children for multiple children', function() {
const vnode = <div>{'foo'}{'bar'}</div>;
assert.equal(JSON.stringify(vnode), JSON.stringify({
sel: 'div',
data: {},
children: [
{text: 'foo'},
{text: 'bar'},
]
}));
});
it('flattens children', function() {
const vnode = (
<section>
<h1>A Heading</h1>
some description
{['part1', 'part2'].map(part => <span>{part}</span>)}
</section>
);
assert.equal(JSON.stringify(vnode), JSON.stringify({
sel: 'section',
data: {},
children: [
{sel: 'h1', data: {}, text: 'A Heading'},
{text: 'some description'},
{sel: 'span', data: {}, text: 'part1'},
{sel: 'span', data: {}, text: 'part2'},
],
}));
});
it('removes falsey children', function() {
const showLogin = false;
const showCaptcha = false;
const loginAttempts = 0;
const userName = ``;
const profilePic = undefined;
const isLoggedIn = true;
const vnode = (
<div>
Login Form
{showLogin && <login-form />}
{showCaptcha ? <captcha-form /> : null}
{userName}
{profilePic}
Login Attempts: {loginAttempts}
Logged In: {isLoggedIn}
</div>
);
assert.equal(JSON.stringify(vnode), JSON.stringify({
sel: 'div',
data: {},
children: [
{text: 'Login Form'},
{text: 'Login Attempts: '},
{text: '0'},
{text: 'Logged In: '},
{text: 'true'},
],
}));
});
it('works with a function component', function() {
const Part = ({part}: {part: string}) => <span>{part}</span>
const vnode = (
<div>
<a attrs={{href: 'https://github.com/snabbdom/snabbdom'}}>Snabbdom</a>
and tsx
{['work', 'like', 'a', 'charm!'].map(part => <Part part={part}></Part>)}
{'💃🕺🎉'}
</div>
);
assert.equal(JSON.stringify(vnode), JSON.stringify({
sel: 'div',
data: {},
children: [
{sel: 'a', data: {attrs: {href: 'https://github.com/snabbdom/snabbdom'}}, text: 'Snabbdom'},
{text: 'and tsx'},
{sel: 'span', data: {}, text: 'work'},
{sel: 'span', data: {}, text: 'like'},
{sel: 'span', data: {}, text: 'a'},
{sel: 'span', data: {}, text: 'charm!'},
{text: '💃🕺🎉'},
],
}));
})
});
});

@ -7,6 +7,8 @@
"declaration": true,
"removeComments": false,
"noUnusedLocals": true,
"jsx": "react",
"jsxFactory": "jsx",
"lib": [
"dom",
"es5",
@ -27,6 +29,8 @@
"src/htmldomapi.ts",
"src/hooks.ts",
"src/is.ts",
"src/jsx-global.d.ts",
"src/jsx.ts",
"src/snabbdom.bundle.ts",
"src/snabbdom.ts",
"src/thunk.ts",

Loading…
Cancel
Save