Giter Club home page Giter Club logo

snabbdom-pragma's Introduction

Snabbdom-pragma

Well tested NotReact.createElement pragma although for Snabbdom !


Snabbdom-pragma is the favorite way to use the Facebook JSX syntax with the virtual DOM library Snabbdom. Based on many principles, Snabbdom-pragma, aim to handle the same API as React.createElement to take all benefits from the most used transpilers proven by the wider React community.

Snabbdom-pragma draws its strength thanks to the Snabbdom, Facebook JSX, and React.createElement specs with some grounded tests.

Table of Contents

Table of Contents

Getting started

  • 1. To use Snabbdom-pragma you need to download it thanks to your favorite JavaScript Package Manager.

    yarn add snabbdom-pragma
    npm install --save snabbdom-pragma
  • 2. The pragma option tells to your transpiler to use Snabbdom.createElement function instead of the default React.createElement.

    buble.transform(input, {
      jsx: 'Snabbdom.createElement'
    })
  • 3. You will need to import the Snabbdom.createElement function into your code.

    import Snabbdom from 'snabbdom-pragma'
  • 4. Your JSX source code will now be transpiled to use Snabbdom.

    const vnode = <div>Hello World</div>
    
    patch(document.body, vnode)

Usage

Snabbdom-pragma is compiler/transpiler independent ! At least your transpiler allow custom JSX pragma. (If you know a well used transpiler not in this list, feel free to open an issue)

Bublé

Snabbdom-pragma works fine and is fully tested for Bublé.

buble.transform(input, {
  jsx: 'Snabbdom.createElement'
})

Typescript

Snabbdom-pragma works fine and is fully tested and typed for Typescript.

typescript.transpileModule(input, {
  compilerOptions: {
    jsx: 'react',
    jsxFactory: 'Snabbdom.createElement'
  }
})

Babel

Snabbdom-pragma works fine and is fully tested for Babel with the transform-react-jsx plugin enabled.

babel.transform(input, {
  plugins: ['transform-react-jsx', {
    pragma: 'Snabbdom.createElement'
  }]
})

Traceur

Snabbdom-pragma works fine and is fully tested for Traceur.

traceur.compile(input, {
  jsx: 'Snabbdom.createElement'
})

JSX Features

Thanks to your transpiler, JSX tags will be transpiled into NotReact.createElement function following the React.creatElement specifications.

Elements

As Snabbdom.createElement is like a straightforward mapping to Snabbdom/h, HTML elements will work out of the box.

/* written */
const vnode = <div>Hello World</div>

/* Once Transpiled */
const vnode = Snabbdom.createElement('div', null, 'Hello World')

/* Similar to */
const vnode = h('div', {}, 'Hello World')

Attributes

By default, attributes will be entrust to the props module. (see Modules Features)

/* written */
const vnode = <input type="text"/>

/* Once Transpiled */
const vnode = Snabbdom.createElement('input', { type: 'text' })

/* Similar to */
const vnode = h('input', { props: { type: 'text' } }, [])

SVG

SVG tags work without any configuration, but attributes will only work with the attrs module.

/* written */
const vnode = <circle cx="43.5" cy="23" r="5"/>

/* Once Transpiled */
const vnode = Snabbdom.createElement('circle', { cx: 43.5, cy: 23, r: 5 })

/* Similar to */
const vnode = h('circle', { attrs: { cx: 43.5, cy: 23, r: 5 } }, [])

Snabbdom Features

In Snabbdom, functionalities is delegated to separate modules. Like hook (lifecycle), on (events), style, props, etc... Snabbdom-pragma give you two ways to use these modules.

Modules object

You can deal with modules properties with an object.

/* written */
const vnode = <div style={{ color: 'red', fontWeight: 'bold' }}></div>

/* Once Transpiled */
const vnode = Snabbdom.createElement('div', { style: { color: 'red', fontWeight: 'bold' } })

/* Similar to */
const vnode = h('div', { style: { color: 'red', fontWeight: 'bold' } }, [])

Modules attribute

Or by using the MODULE-PROPERTY attribute.

/* written */
const vnode = <button on-click={ callback }/>

/* Once Transpiled */
const vnode = Snabbdom.createElement('button', { 'on-click': callback })

/* Similar to */
const vnode = h('button', { on: { click: callback } }, [])

Both

/* written */
const vnode = <div style-color="red" style={{ background: 'blue' }}></div>

/* Once Transpiled */
const vnode = Snabbdom.createElement('div', { 'style-color': 'red', style: { background: 'blue' } })

/* Similar to */
const vnode = h('div', { style: { color: 'red', background: 'blue' } }, [])

Custom Modules

Custom modules are supported through the createElementWithModules method. You will need to register this method as pragma instead of the createElement.

    pragma: 'Snabbdom.createElementWithModules("ALIAS_1": "MODULE_1", "ALIAS_2": "MODULE_2", ...)'

Then use

/* written */
const vnode = <div style-color="red"></div>

/* Once Transpiled */
const vnode = Snabbdom.createElementWithModules({ 'style': '' })('div', { style: { 'color': 'red' } })

/* Similar to */
const vnode = h('div', { style: { color: 'red' } }, [])

'NotReact' Features

In React you can create components and use them inside other components, using the React.createClass function.

Components

Snabbdom-pragma use simple functions as component of type (attributes, children) => vnode.

/* written */
const Component = ({ name }, children) =>
  <div>
    Hello { name }

    <div>
      { children }
    </div>
  </div>

const vnode = <Component name="world">
    <p>It works !</p>
  </Component>

/* Once Transpiled */
const Component = ({ name }, children) =>
  Snabbdom.createElement('div', null, 'Hello ', name,
    Snabbdom.createElement('div', null, children)
  )

const vnode = Snabbdom.createElement(Component, { name: 'world' },
  Snabbdom.createElement('p', null, 'It works !')
)

/* Similar to */
const Component = ({ name }, children) =>
  h('div', {}, ['Hello ', name,
    h('div', {}, children)
  ])

const vnode = Component({ name: 'world' }, [
  h('p', {}, 'It works !')
])

As in React, components function need to start with a capital letter, while regular HTML tags start with lower case letters. This is the common way to tell to your transpiler to give the function to the Snabbdom.createElement instead of a string.

Misc

snabbdom-pragma's People

Contributors

blikblum avatar jvanbruegge avatar swizz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

snabbdom-pragma's Issues

Typescript rewrite

We already done some steps into Typescript uses with v2.1.0 and v2.2.0.

As Snabbdom is write in Typescript, and some Snabbdom user are part of the Typescript community.

It will be time for us to make the switch.

Hi. Does snabbdom-pragma work with esbuild?

Sorry for posting without trying to check it myself.
I've been away from dev for a while but I would like to try cycles.js using the esbuild bundler
Just wondering if there's a straight forward answer before diving into all the tooling machinery.
Thanks

Consider to return a vnode object instead of calling h()

According to snabbdom/snabbdom#277, it could be a valuable addition to return a vnode object directly with our own formatting instead of fighting to match the h implementation.

/* written */
const vnode = <input type="text"/>

/* Once Transpiled */
const vnode = Snabbdom.createElement('input', { type: 'text' })

/* Once Executed */
const vnode = {
  sel: 'input',
  data: {
    props: {
      type: 'text'
    }
  },
  children: [],
  text: undefined,
  elm: undefined,
  key: undefined
}

/* Similar to */
const vnode = h('input', { props: { type: 'text' } }, [])

Add Typescript transpiler

Typescript is well used in the snabbdom community.

And the compiler have --jsx and --jsxFactory options. So, it could be awesome to add it into officially supported transpilers !

Read this

not rendering properly data and aria attributes

data-* and aria-*
get rendered as [object Object] on OS X
snabbdom-pragma 1.8.0
they should be passed to h() function as attrs: { "data-id": "value" }, instead they are passed as attrs: { "data": { "id": "value"}}

Unable to run test scripts

Trying to set a snabbdom-pragma development env:

Forked develop branch

Installed dependencies (yarn)

Tried to run test (npm test): no script found

Tried to run test:prebuild (npm run test:prebuild): 1 exception

× Couldn't find any matching tests

The same error occurs with npm run test:postbuild

Running under windows 10, node v6.9

Having trouble with all attributes besides Class and ID

I am having trouble getting various attributes to get rendered in the HTML. For example:

// JSX
<span ariaLabel={ label }>...</span>

// and

<label for={ inputId }>...</label>

Compiles down to:

// Compiled Ouput
Snabbdom.createElement("span", { ariaLabel: "Close" }, span)

// and

Snabbdom.createElement("label", { for: "input-id" }, label)

Which renders as:

// Rendered HTML
<span>...</span>

// and

<label>...</label>

As you can see that output doesn't produce the aria-label and for attributes I defined (I've noticed the same for all other aria attributes as well). It looks like those attributes need to be nested under the attrs object in the createElement() execution, which I can only produce by not using JSX syntax, and instead doing something like:

span({ attrs: { "aria-label": "Close" } }, [...])

// and

label({ attrs: { for: "Close" } }, [...])

Which compiles down to:

// Compiled Output
Snabbdom.createElement("span", { attrs: { "aria-label": "Close" } }, span)

// and

Snabbdom.createElement("label", { attrs: { for: "input-id" } }, label)

Which renders as:

// Rendered HTML
<span aria-label="Close">...</span>

// and

<label for="input-id">...</label>

After digging through all that, I tried the following:

// JSX
function baseInput(labelString) {
    const attrs = { for: 'input' };
    return (
        <div>
            <label attrs={attrs}>{labelString}</label>
            <input id="input" />
        </div>
    );
}

This produces the markup I need, but for some reason that doesn't feel right.

Is there something I am doing wrong here, or is that last example the designed approach to getting the markup that I need rendered?

Destructuring "type parallelism" error in createElement

return sel(data || {}, children)

If line 69 collects all ...children in an array (e.g. JSX.Element[]), shouldn't ...children be re-splatted into the sel fn on line 71?

As line 71 is now written, sel(, children) gets an extra level of array wrapping with each pass into a Component (at one pass deeper in, JSX.Element[] arrays become JSX.Element[][], with only the zeroth element populated). I believe this is not the intent.

Interestingly the story for sanitizeChildren called on line 73 is different and instructive: Pure text is only ever found through the zeroth element of the rhs OR operand on line 60, but this is different from what a general Component sel fn (line 71) expects I believe (that won't expect an [][]).

Last, line 77 is "saved" (meaning will have no apparent problems), I guess, by line 62's reduceDeep, right? So it also does not suffer the bug of line 71, I guess?

There is a deeper test that I don't know how to write that proves these "type parallelisms" are preserved. I believe that test is full typing!

Span with value zero can break application

Simple counter application:

const patch = init([
    classModule,
    propsModule,
    styleModule,
    eventListenersModule,
])

let currentVnode: VNode = toVNode(document.getElementById('container') as any)

const view = (count: number) => {
   return <div>
       <button on-click={() => render(count+1)}>Increment</button>
       <span>{count}</span>
       <button on-click={() => render(count-1)}>Decrement</button>
   </div>
}

const render = (count: number) => {
    currentVnode = patch(currentVnode, view(count))
}

render(0)

If I increment above the value 0 and then decrement to -1 then it will give me the error: Uncaught TypeError: Node.removeChild: Argument 1 is not an object. It also gives an error if I decrement below 0 and then try to increment to 1. The fix is to change <span>{count}</span to <span>{String(count)}</span>. This behaviour differs from the hyperscript. The following hyperscript has no problems: h("span", null, count). Also the vnode generated shows that the span with a zero has children: {"sel":"span","data":{},"children":[{"text":0}],"text":0} whereas a span with a 1 has: {"sel":"span","data":{},"text":1}

Destructuration is broken for custom components

Since 9e3efb3 destructuration is broken.

In particular, the jsx spec seem to specify to send null when there are no props.

You were handling that case before this refactoring 9e3efb3#diff-1fdf421c05c1140f6d71444ea2b27638L29

And after 9e3efb3#diff-1fdf421c05c1140f6d71444ea2b27638R48 you just rely on the default argument behavior of es6 ... which is equivalent to === void 0 or === undefined.

Which breaks for null of course. So when there are no props, you send null instead of {} downstream, and when destructuring it breaks down.

You should manually check for null as you previously did, it will never be undefined here as the jsx spec specifies ^^.

Certain SVG attributes aren't parsed

Attributes such as stroke-width and stroke-dasharray only work when supplying the module e.g. attrs-stroke-dasharray.

I'm guessing this is because snabbdom-pragma sees stroke as the module name.

Is this fixable?

Is `h('div#id.class1.class2', ...)` needed ?

ATM, the sel property in Snabbdom-pragma is only related to the html element.

Snabbdom-JSX use something like the following code, to handle something they call Id and static classes (sel attribute)

<div selector="#id.class1.class2" />

Today, you can use the following code to achieve this

<div id="id" class={{ 'class1': true, 'class2': true }} />

So, Is this feature requested ?

Performance improvements

I'm curious to know more about how to measure performance in op/sec, to have a comparison between :

  • snabbdom-pragma 2.1.0
  • snabbdom-pragma 1.10.0
  • snabbdom/h 0.6.6

Compilation failures with TypeScript 3.2

TypeScript 3.2 contains more stringent type checking for TSX (see microsoft/TypeScript#27627). This change checks the return type of components against JSX.Element. Unfortunately this means it’s no longer possible to write all kinds of component functions that snabbdom-pragma’s createElement accepts (i.e. anything that returns the CircularChildren type).

This means that, with the following code,

function Comp1(args : any) {
	return <div></div>;
}

function Comp2(args : any) {
	return '';
}

function Comp3(args : any) {
	return ['test', <div></div>];
}

function Comp4(args : any) {
	return [<div></div>];
}

const node = <div className="test"><Comp1 arg="val"/><Comp2 arg="val"/><Comp3 arg="val"/><Comp4 arg="val"/></div>;

only Comp1 works because its return type is actually compatible with Snabbdom’s JSX.Element interface (which, essentially, is just VNode). The other components throw errors like these:

JSX element type '(string | Element)[]' is not a constructor function for JSX elements.

I’ve tried setting type Element = SnabbdomPragma.CircularChildren but this only fixes Comp2. Comp3 says:

error TS2605: JSX element type '(string | number | VNode | Children[])[]' is not a constructor function for JSX elements.
Type '(string | number | VNode | Children[])[]' is not assignable to type 'VNode[]'.
Type 'string | number | VNode | Children[]' is not assignable to type 'VNode'.
Type 'string' is not assignable to type 'VNode'.

while Comp4 says:

error TS2605: JSX element type 'CircularChildren[]' is not a constructor function for JSX elements.
Type 'CircularChildren[]' is not assignable to type 'VNode[]'.
Type 'CircularChildren' is not assignable to type 'VNode'.
Type 'string' is not assignable to type 'VNode'.

The only thing that works for the moment is not declaring JSX.Element (or setting it to any). Maybe @weswigham, whose patch the TypeScript 3.2 change was, has more insights.

Either way: could you remove the JSX.Element interface from snabbdom-pragma.d.ts as a workaround? I know this means less type checking of snabbdom-pragma stateless function components’ return types but it would get rid of the false negatives.

child arrays are not being flattened

This pragma has the same problem as here: snabbdom-jsx/snabbdom-jsx#10

If I have this code:

const arr = [<div>Hello</div>, <div>Hello2</div>];
return <div>
        { arr }
    </div>;

will incorrectly create:

{
    children: [
        [{ sel: 'div' ... }, { sel: 'div' ... }]
    ],
    ...
}

you see that there is one array too much

VNodes in children become undefined after createElement()

I have this virtual node (called children), previously created using createElement()

[{
	"sel": "button",
	"data": {
		"props": {
			"type": "button"
		},
		"on": {}
	},
	"children": [{
		"text": "Remove"
	}]
}]

I want to pass this into a new createElement()-call like below:

Snabbdom.createElement('div', undefined, children);

But the result then becomes:

{
	"sel": "span",
	"data": {},
	"children": [null]
}

If I however do Snabbdom.createElement('div', undefined, h('div', undefined, children));, I get the correct output (but with an additional unwanted element inbetween):

{
	"sel": "span",
	"data": {},
	"children": [{
		"sel": "div",
		"children": [{
			"sel": "button",
			"data": {
				"props": {
					"type": "button"
				},
				"on": {}
			},
			"children": [{
				"text": "Remove"
			}]
		}]
	}]
}

Is there a better work-around to re-use vnodes? Would it be possible to allow vnodes to be passed into children, like what h()-calls currently allow?

'No default export' error with TypeScript

When importing snabbdom-pragma like so into my project

import Snabbdom from 'snabbdom-pragma';

TypeScript throws the following error

TS1192: Module '"<snip>/snabbdom-pragma/snabbdom-pragma"' has no default export.

The only way to get it to compile without errors is as a qualified import, which is a bit warty.

import * as Snabbdom from 'snabbdom-pragma';

My tsconfig.json:

{
  "compilerOptions": {
    "allowJs": true,
    "jsx": "React",
    "jsxFactory": "Snabbdom.createElement",
    "module": "es6",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "outDir": "www/js",
    "sourceMap": true,
    "strict": true,
    "target": "es5"
  },
  "exclude": [ "node_modules" ]
}

Setting a data attribute with more than one dash does not work

Actual example:

let calcKey = 'x'
return <a data-calc-key={calcKey}>

Will set el.dataset.calc to [Object object] instead of setting el.dataset.calcKey to 'x'

The reason is that the following params will be passed:

{
  data: {
    calc: {
       key: 'x'
    }
  }
}

This struct is created by deepifyKeys

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.