Giter Club home page Giter Club logo

hyperscript-helpers's Introduction

hyperscript-helpers

Terse syntax for hyperscript.

Less than 50 lines of code, taking your hyperscripting to the next level.

What is it

hyperscript-helpers elm-html inspired helpers for writing hyperscript or virtual-hyperscript.

They work with React.createElement, but there is also a feature-rich hyperscript library for React: react-hyperscript.

// instead of writing
h('div')

// write
div()

// instead of writing
h('section#main', mainContents)

// write
section('#main', mainContents)

hyperscript-helpers vs templates (including JSX)

With hyperscript-helpers:

  • It's nice to use functional utilities like lodash, because it's just functions
  • You get errors if you misspell a tag name, because they are function names
  • You have a consistent syntax at all times, because markup is just functions
  • Also, it's just functions

This is super helpful, especially when using hyperscript-helpers with Cycle.js!

See the supported TAG_NAMES here: src/index.js.

Example

Suppose we have a list of menu items of:

{ title: String, id: Number }

and a function that returns attributes given an id:

function attrs(id) {
  return { draggable: "true", "data-id": id };
}

How would we render these in plain hyperscript, JSX or with the helpers?

// plain hyperscript
h('ul#bestest-menu', items.map( item =>
  h('li#item-'+item.id, attrs(item.id), item.title))
);

// JSX
<ul id="bestest-menu">
  {items.map( item =>
    <li id={"item-"+item.id} {...attrs(item.id)}>{item.title}</li>
  )}
</ul>

// hyperscript-helpers
ul('#bestest-menu', items.map( item =>
  li('#item-'+item.id, attrs(item.id), item.title))
);

How to use

npm install hyperscript-helpers

The hyperscript-helpers are hyperscript-agnostic, which means there are no dependencies. Instead, you need to pass the implementation when you import the helpers.

Using ES6 πŸ’–

const h = require('hyperscript'); // or 'virtual-hyperscript'
const { div, span, h1 } =
  require('hyperscript-helpers')(h); // ← Notice the (h)

With React

// βœ… Preferred
const h = require('react-hyperscript');
const React = require('react');
const { div, span, h1 } =
  require('hyperscript-helpers')(h); // ← Notice the (h)


// Also works, but beware of the createElement API
const React = require('react');
const { div, span, h1 } =
  require('hyperscript-helpers')(React.createElement); // ← Notice the (React.createElement)

Using ES5

var h = require('hyperscript'); // or 'virtual-hyperscript'
var hh = require('hyperscript-helpers')(h);  // ← Notice the (h)
// to use the short syntax, you need to introduce them to the current scope
var div  = hh.div,
    span = hh.span,
    h1   = hh.h1;

Once that's done, you can go and use the terse syntax:

$ node
β–Έ const hh = require('hyperscript-helpers')(require('hyperscript'));
β—‚ undefined

β–Έ const { div, span, h1 } = hh;
β—‚ undefined

β–Έ span('😍').outerHTML
β—‚ '<span>😍</span>'

β–Έ h1({ 'data-id': 'headline-6.1.2' }, 'Structural Weaknesses').outerHTML
β—‚ '<h1 data-id="headline-6.1.2">Structural Weaknesses</h1>'

β–Έ div('#with-proper-id.wrapper', [ h1('Heading'), span('Spanner') ]).outerHTML
β—‚ '<div class="wrapper" id="with-proper-id"><h1>Heading</h1><span>Spanner</span></div>'

It's also natively supported to spell the helper function names with an uppercase first letter, for example to avoid conflicts with existing variables or reserved JavaScript keywords:

β–Έ const { Span, Var } = hh;
β—‚ undefined

β–Έ Span('😍').outerHTML
β—‚ '<span>😍</span>'

β–Έ Var('x').outerHTML
β—‚ '<var>x</var>'

Creating custom HTML tag names can be done with the createTag function:

β–Έ const someFn = hh.createTag('otherTag');
β—‚ undefined

β–Έ someFn('bla').outerHTML
β—‚ '<otherTag>bla</otherTag>'

API

Because hyperscript-helpers are hyperscript-agnostic there is no "exact" API. But, just to give you a direction of what should be possible:

tagName(selector)
tagName(attrs)
tagName(children)
tagName(attrs, children)
tagName(selector, children)
tagName(selector, attrs, children)

Where

  • tagName is a helper function named like the HTML element that it creates; hyperscript-helpers natively supports spelling the tag name with the first letter lowercase or uppercase.
  • selector is string, starting with "." or "#".
  • attrs is an object of attributes.
  • children is a hyperscript node, an array of hyperscript nodes, a string or an array of strings.

hyperscript-helpers is a collection of wrapper functions, so the syntax of your exact hyperscript library (like virtual-hyperscript) still applies.

For example, for multiple classes:

// ... with Matt-Esch/virtual-dom/.../virtual-hyperscript
button({className: "btn btn-default"}); // ← separated by space!
button(".btn.btn-default");             // ← separated by dot!

Other hyperscript libraries may have other syntax conventions.

Potential issues

Selector shortcut

The selector shortcut (div('.my-class')) may cause unexpected results in some cases. Our suggestion is:

Whenever you use tagName(<children>) syntax and <children> may be a string, starting with . (period) or # (number sign), wrap the argument in [].

// βœ… GOOD
filenames.map(filename => span([filename])); // <span>README.md</span><span>.gitignore</span>

// ❌ BAD
filenames.map(span); // <span>README.md</span><span class="gitignore"></span>

As most hyperscript is written by hand, we decided keep this convenient shortcut despite the issue.

Logic in class names

If you need to apply logic rules for class generation, we recommend using libraries like classnames for making proper {className: ...} argument.

Not recommended:

span(error ? ".error" : null);         // ← may be a trap, because:
span(error ? ".error" : null, {}, []); // ← this one is wrong

Tools

html-to-hyperscript.paqmind.com – webservice to convert HTML to hyperscript

Contributing

To get set up, simply clone the repository, navigate to the directory on your terminal and do the following:

# install dependencies
npm install

# build the project
npm start

# run tests
npm test

# commit your changes with commitizen
npm run commit
# or "git cz", if you have commitizen in your PATH

The source code can be found under the src directory, and the built file is under dist.

Tests are written with Mocha, using the awesome JSVerify library.


hyperscript-helpers is brought to you by @ohanhi.

License: MIT

hyperscript-helpers's People

Contributors

arlair avatar ashtonsix avatar bumblehead avatar ivan-kleshnin avatar kynikos avatar melleb avatar ohanhi avatar phadej avatar queckezz avatar staltz avatar stoeffel avatar tylors 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  avatar  avatar  avatar  avatar  avatar  avatar

hyperscript-helpers's Issues

Solved: Function type error

Strange error.

import h from 'inferno-hyperscript';
import hh from 'hyperscript-helpers';
//export { default as h } from 'inferno-hyperscript';
export { h };

export const html = hh(h); 
export default html;

This code was working if I use in package.json this declarations:

  "dependencies": {
    ...
    "hyperscript": "^2.0.2",
    "hyperscript-helpers": "^3.0.3",
    "inferno-hyperscript": "^3.10.1"
    ...
  },

but if I use this:

  "dependencies": {
    ...
    "hyperscript": "^2.0.2",
    "hyperscript-helpers": "^3.0.3",
    "inferno-hyperscript": "^5.0.1"
    ...
  },

give an error:

The argument of type 'typeof' inferno-hyperscript '' is not assignable to the parameter of type 'Function'.
In the type 'typeof' hell-hyperscript '' the 'apply' property is missing.

seams that in this declaration:
export default function hh(h: Function): HyperScriptHelpers;
Function is no more a good type for h param

Someone have an idea on how to solve this strange problem?
regards

P.S. (my tsconfig.json)

{
	"version": "2.7.2",
	"compilerOptions": {
		"module": "esnext",
		"target": "es2016",
		"lib": ["es6", "es7", "dom"],
		"types": [ "inferno" ],
		"noImplicitAny": true,
           ...
}

The problem was the import:
import h from 'inferno-hyperscript';
must be change in:
import { h } from 'inferno-hyperscript';
sorry for my mistake.

Hyperscript helper with no parameter bug?

I created a bug over TylorS/cycle-snabbdom#27
but I think the issue might be in Hyperscript Helpers.

When I defined a helper with no parameters, eg a div(), or a br(), it is calling h as:

h(tagname, undefined)

Snabbdom seems to expect

h(tagname)

which also seems logical to me.

I'm not sure what virtual-dom expects.

Do you think this is a bug?

Using ES2015 Proxy

I was experimenting with ES2015 Proxy and it seemed like a good fit for this library depending on the target audience or simply in the future when there is more browser support.
Basically it just relieves the need to keep a list of TAG_NAMES or to build an object yourself.
In particular this works really nicely because with snabbdom (the h() I generally use) svg 'just works' there is no separate svg() function like there is for virtual-dom.

Feel free to close this as this isn't really an issue, but I thought you may be interested.

const isValidString =
  param =>
    typeof param === 'string' && param.length > 0;

const startsWith =
  (string, start) =>
    string[0] === start;

const isSelector =
  param =>
    isValidString(param) && 
    startsWith(param, '.') || 
    startsWith(param, '#');

const hh = 
  h => 
    new Proxy(h, {
      get(target, property) {
        return (first, ...rest) => {
          if (isSelector(first)) {
            return h(property + first, ...rest);
          } else {
            return h(property, first, ...rest);
          }
        }
      }
    });

// usage
import h from 'snabbdom/h'
const {div, main, svg, g, circle} = hh(h)
...

SVG support?

Would be cool. I’m just wondering whether to submit a PR here or make a new package of it.

It looks like SVG support in virtual-dom is/used to be a separate module in the same package. I don’t quite get the logic of their file structure though.

Import error

Since 3.0.0 I'm getting _htmlTagNames2.default.forEach is not a function
(checked within non ES2016 environment).

That's because of mismatch between CommonJS and SystemJS(?) module systems

  1. html-tag-names/index.js

module.exports = require('./index.json');

  1. hyperscript-helpers/src/index.js

import tagNames from 'html-tag-names';

  1. Generated code
var _htmlTagNames = require('html-tag-names');

var _htmlTagNames2 = _interopRequireDefault(_htmlTagNames);

...

exports['default'] = function (h) {
  var createTag = node(h);
  var exported = { tagNames: _htmlTagNames2['default'], isSelector: isSelector, createTag: createTag };
  _htmlTagNames2['default'].forEach(function (n) { // <-- `default` is undefined
    exported[n] = createTag(n);
  });
  return exported;
};

TypeScript Definitions

Before anything I want to thank you for this awesome library! πŸ‘ πŸ‘

I've been experimenting with migrating Cycle.js over to TypeScript as well as some of it's drivers and I had the need to write some TypeScript definitions. I was wondering if that is something you would like to have PR'ed for your repo? I have them written here. I'll gladly rename/refactor things if you think it's necessary anywhere.

tagName(children) is not working with React

Hi,

Following code is not working:

const React = require('react');
const ReactDom = require('react-dom');
const { p } = require('hyperscript-helpers')(React.createElement);

ReactDom.render(p(['test']), document.getElementById('root'));

Error:

Warning: Unknown prop `0` on <p> tag. Remove this prop from the element. For details, see https://fb.me/react-unknown-prop in p

My setup:
"hyperscript-helpers": "^3.0.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",

Make the build not be "Failing"

Updating the documentation triggered Travis to rebuild, and apparently because of a dependency update it fails. This does not affect usage, since the built file (in /dist) has not been changed.

Feature idea: easy classNames

One feature I like in hyperscript is the ability to easily write the className using CSS selectors:

h('div.foo'), not h('div', {className: 'foo'}).

In Cycle.js, it's also convenient to have the . dot there, because later the classes are used in selectors, e.g. DOM.select('.foo').events('click'). So it's not great if sometimes you need the dot, and sometimes you don't (in className), that's why I like hyperscript keeping it consistent.

Proposal:
How about allowing a string parameter to the hyperscript helper? If the first parameter is a string, then it's interpreted as the selector, so its classes will be parsed out in hyperscript. If the first parameter is an object, we know its the properties object.

E.g.
div('.foo') instead of div({className: 'foo'}).

Of course one would be able to still provide properties and children:

div('.foo', {'data-id': 'headline-6.1.2'}, [
  img({src: user.avatar})
])

Passing single node as children does not seem to work with preact-hyperscript

The Doc seems to allow passing single nodes as children but
when using with preact-hyperscript,
it seem that a single node can only be passed inside array:

  // this works
  div({style:{border: 'solid thin'}}, [div('hello')])

  // but this does not:
  // div({style:{border: 'solid thin'}}, div('hello'))

See the pen here:
http://codepen.io/dmitriz/pen/qmqYgY?editors=0011

Also referred in queckezz/preact-hyperscript#7
in case it is a problem on their end.

It would be nice to provide this feature from the helpers even if the host library h does not (which is sadly often the case).

please export tag names

please export tag names.

This way my script may reuse tag names you've defined --currently my script must define its own tags, and it uses a copy/pasted definition from your sources here.

style as properties

Why does this work:

div({attributes: {style: `width: 60%;`}})

...but this doesn’t:

div({style: `width: 60%;`})

?

As per the hyperscript README, the following ought to work

h(`div`, {style: `width: 60%`})

svg issues using 'xlink:href'

I'm using svg symbols and am finding it necessary to use the innerHTML attribute.

This does result in svg render but requires innerHTML:

div('.mydiv', {
  innerHTML : [
    '<svg>',
    '  <use xlink:href="./symbols.sprite.svg#ic_perm_data_setting_24px"></use>',
    '</svg>'
  ].join('\n')
})      

This does not render the 'xlink:href' attribute to the use element:

svg('.mysvg', [
  use('.myuse', {
     'xlink:href' : './symbols.sprite.svg#ic_perm_data_setting_24px'
  })
])

These do render attribute 'xlink:href' to the use element, but do not result in svg render:

svg('.mysvg', [
  use('.myuse', {
    attributes : {
      'xlink:href' : './symbols.sprite.svg#ic_perm_data_setting_24px'
    }
  })
])
svg('.mysvg', {
  innerHTML : '<use xlink:href="./symbols.sprite.svg#ic_perm_data_setting_24px"></use>'
})

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.