Giter Site home page Giter Site logo

ijk's Introduction

ijk

Transforms arrays into virtual DOM trees

Build Status codecov

Find h a bit repetitive? Not a huge fan of JSX? Love LISP? Code as data and data as code?

This is a tiny recursive factory function that allows you to write terse, declarative representations of virtual DOM trees. It does not try mimic HTML or JSON syntax but instead a series of nested arrays to represent user interfaces.

const tree = h('x', 'y', 'z')
(
  ['main', [
    ['h1', 'Hello World'],
    ['input', { type: 'range' }],
    ['button', { onclick: console.log }, 'Log Event'],
  ]]
)

The above call to h returns a virtual DOM tree with named attributes that respect the provided schema. Expected output here, would be of the shape { x: 'main', y: {}, z: [...] }. A tree like this can be passed as a node to patch, diff and render algorithms exposed by libraries like Hyperapp, Ultradom or Preact.

Schemas

  • Hyperapp / Ultradom / Preact: h('nodeName','attributes','children')

Signature

A call to h(x,y,z) returns a build function that expects a node of type [0,1,2] where:

  • Index 0 contains a string used as the elements tag name (required)
  • Index 1 contains an object containing element attributes (optional)
  • Index 2 contains an string|array of content or children (optional)

Children are flattened and falsey children are excluded. Numbers passed as children get converted to strings.

Installation

npm i ijk

Usage

Here is a demo with Hyperapp and Preact.

import { h } from 'ijk'

const tree = h('nodeName', 'attributes', 'children')(
  ['main', [
    ['h1', 'Hello World'],
    ['input', { type: 'range' }],
    ['button', { onclick: console.log }, 'Log Event'],
    ['ul', [
      ['li', 1],
      ['li', 2],
      ['li', 3]
    ]],
    false && ['span', 'Hidden']
  ]]
)

Comparison

ijk is essentially h but with optional props and you only have to call h once; not every time you want to represent an element in the DOM. This generally means less repetition and one less import in your view files.

const h =
  h('main', {}, [
    h('h1', {}, 'Hello World'),
    h('input', { type: 'range' }),
    h('button', { onclick: console.log }, 'Log Event'),
    h('ul', {}, [
      h('li', {}, 1),
      h('li', {}, 2),
      h('li', {}, 3),
    ]),
    false && h('span', {}, 'Hidden')
  ])

The main advantages over using JSX is less repetition of tag names and no build step is required.

const jsx =
  <main>
    <h1>Hello World</h1>
    <input type='range' />
    <button onclick={ console.log }>Log Event</button>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
    {false && <span>'Hidden'</span>}
  </main>

Advanced

Here is an example that takes advantage of most features and demonstrates components.

import { h } from 'ijk'

const Item = data => ['li', data]
const Article = ({ title, story, related }) => [
  'article',
  [
    ['h2', title],
    ['hr'],
    ['p', story],
    related.map(Item),
  ]
]

const Main =
  ['main', [
    ['h1', 'Hello World'],
    ['input', { type: 'range' }],
    ['ul', [
      ['li', 1],
      ['li', 2],
      ['li', 3],
    ]],
    ['button', { onclick: console.log }, 'Log Event'],
    false && ['span', 'Hidden'],
    Article({
      title: 'Some News',
      story: 'lorem ipsum dolor sit amet',
      related: [4,5],
    })
  ]]

const tree = h('nodeName', 'attributes', 'children')(Main)

ijk's People

Contributors

crazy4groovy avatar futurist avatar jorgebucaran avatar lukejacksonn 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  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  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ijk's Issues

Tags as functions

Looks cool
Here's an idea:
Instead of strings for tag names use a set of functions so proper typing will provide auto complete and error throwing

Import { dom } from 'ijk'

const tree = h('name', 'props', 'children')(
  ['main', [
    [dom.h1( 'Hello World')]
  
    [dom.button({ onclick: console.log }, 'Log event ']
)

How to share the common code between h and p

The attribute names of the output tree is the only difference between these two functions. I have tried various ways of making them one function that accepts a schema as an argument but I couldn't get it to work 🤔

export const h = node =>
  !node
    ? false
    : typeof node[1] === 'object' && !Array.isArray(node[1])
      ? {
          name: node[0],
          props: node[1],
          children: Array.isArray(node[2])
            ? node[2].map(h).filter(Boolean)
            : node[2] + '',
        }
      : h([node[0], {}, node[1]])

export const p = node =>
  !node
    ? false
    : typeof node[1] === 'object' && !Array.isArray(node[1])
      ? {
          nodeName: node[0],
          attributes: node[1],
          children: Array.isArray(node[2])
            ? node[2].map(p).filter(Boolean)
            : node[2] + '',
        }
      : p([node[0], {}, node[1]])

Ideally I would like to be able to just export h which is configurable like:

h(['name', 'props', 'children']) // hyperapp trees
h(['nodeName', 'attributes', 'children']) // preact trees

Where the array values represent the key names of the vtree nodes.

{
   [schema[0]]: node[0],
   [schema[1]]: node[1],
   [schema[2]]: Array.isArray(node[2]) ? node[2].map(p).filter(Boolean) : node[2] + '',
}

Handle tags like ['hr'] and flatten children

At the moment ['hr'] renders as <hr>undefined</hr> and having nested arrays of children in children causes an error because [['li', 1], ['li',2]] is not a valid node. It would be nice to be able to support both of these cases.

Support emmet-style tags?

Hi @lukejacksonn!

Thank you for this project! I am hoping to experiment with it as a way to write views in a virtual-Dom library independent way, so I hope you continue to support being able to specify the node structure and not just preact and hyperapp.

I have done an experiment with JSX here: https://meiosis.js.org/examples/setup

But I think an ijk/hyperscript version would be even cooler! I will give it a try.

Now, I was thinking, would it make sense to support emmet style tags such as ["button#save.btn.btn-primary", ...]? I suppose you would also have to have a way to specify whether to use class or className, not sure...

What do you think?

Thank you!

Hey, so... I'm making a html2ijk thingy...

I've been using ijk for a little while and right now I think it is the best way to "write" HTML. \o/

But I also love copy & paste. =P

So I'm writing this thing so I can turn HTML into ijk: https://github.com/slacktracer/lmn.

I guess I'm asking... Is that OK with you?

Anyway, thank you very.

By the way, we must start campaigning to get ijk as one of the alternatives for JSX on the hyperapp readme sooner than later. =D

Using with @hyperapp/router

I was unable so far to find a way to use ijk with @hyperapp/router (entirely). Have you ever?

I thought was almost there but I got stuck on (below):

const view = () =>
  h(
    "div",
    null,
    h(
      "ul",
      null,
      h("li", null, h(Link, { to: "/" }, "Home")),
      h("li", null, h(Link, { to: "/about" }, "About"))
    ),
    h("hr"),
    h(Route, { path: "/", render: home }),
    h(Route, { path: "/about", render: about })
  );

I did not find a way to turn this h(Link, { to: "/" }, "Home") to "ijk"...

And this Link({ to: "/" }) does not seem to work at all...

I'm looking into this for a few days now but I got no more clues at the moment. Are they even compatible? Or, of course they are, and am I just being silly? ¯\(ツ)

Missing license

I'd like to use this piece of code for io but it's missing the license information. Would you mind making this MIT licensed?

Handle nodes that return undefined

Sometimes you want to be able to skip a child. To do so you would usually return a falsey value. At the moment falsey values are rendered as an undefined tag which is not ideal.

This:

const view = state => xyzd(
  ['main', [
    true && ['h1', 'Visible'],
    false && ['h1', 'Invisible']
  ]
)

Renders as:

<main>
  <h1>Visible</h1>
  <undefined>undefined</undefined>
</main>

See this behaviour on this CodePen

broken with superfine

ultradom is now superfine, it returns to the 'name, 'props', 'children' signature, and it requires text nodes to be wrapped in a vnode: https://github.com/jorgebucaran/superfine/blob/master/src/index.js#L368-L381

var createVNode = function(name, props, children, element, key, type) {
  return {
    name: name,
    props: props,
    children: children,
    element: element,
    key: key,
    type: type
  }
}

var createTextVNode = function(text, element) {
  return createVNode(text, EMPTY_OBJECT, EMPTY_ARRAY, element, null, TEXT_NODE)
}

Missing release tags?

Looks like 0.16.0. 0.17.0, and 0.18.0 have all been published to npm without any tags created. Might come in handy at some point.

Can I have a text node and another element as children/siblings...?

I mean, I thought I could this:

[
  'main',
  [
    [
      'label',
      [
        'examiners',
        [
          'input',
          {
            checked: state.section === 'examiners',
            name: 'section',
            onclick: () => actions.changeSection('examiners'),
            type: 'radio'
          }
        ]
      ]
    ]
]

But it does not work. Should it be possible? Am I missing something?

Schema listed for Preact no longer valid.

JFYI The shape of vdom nodes has changed in Preact. See the following definition from their src/index.d.ts:

interface VNode<P = {}> {
	type: ComponentType<P> | string;
	props: P & { children: ComponentChildren };
	. . .
}

For the size of this library though, it's easier just to vendor it directly into what ever project you want and add explicit call to Preact.createElement for the recursive base case. A similar fix for would make it work with React.

Optimise clean function perf

Cool lib!

What about

const clense = (a, b) => {
  b && a.push.apply(a, b)
  return a
}

or maybe?

const clense = (a, b) => {
  b && a.push.apply(a, [b])
  return a
}

to save some memory via mutating?

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.