Yet another JSX pragma for Snabbdom
- Straightforward and intuitive syntax
-
<input type="text" />
rather than<input props={{ type: 'text' }}>
-
- Typechecked attributes on intrinsic elements
- Only for HTML elements for now
- Typechecked children
-
className
andid
will be the part of thesel
- Type-safe custom modules via module augmentation
- Support for React 17 style automatic runtime
const vnode = (
<div id="container" className="two classes" onclick={someFn}>
<span $style={{ fontWeight: 'bold' }}>This is bold</span> and this is just normal text
<a href="/foo">I'll take you places!</a>
</div>
);
Note that the following packages are peer dependencies of this library, which need to be installed separately.
Package | Version |
---|---|
csstype |
3 |
snabbdom |
3 |
With npm
$ npm install @herp-inc/snabbdom-jsx
With yarn
$ yarn add @herp-inc/snabbdom-jsx
Note that fragments are still experimental. Make sure you are using Snabbdom v3.2+ and opt it in to enable the feature.
const patch = init(modules, undefined, {
experimental: {
fragments: true,
},
});
With TypeScript
Add the following options to your tsconfig.json
:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@herp-inc/snabbdom-jsx"
}
}
With Babel
Add @babel/plugin-transform-react-jsx
to your devDependencies
.
Add the following options to your Babel configuration:
{
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"importSource": "@herp-inc/snabbdom-jsx",
"runtime": "automatic"
}
]
]
}
By default, an attribute will be passed to the props module.
<input type="text" />
// { props: { type: 'text' } }
However, certain attributes will be treated differently.
className
and id
attributes will be concatenated to the sel
with .
and #
respectively, and won't be passed to any modules. For example, the expression <div id="foo" className="bar baz" />
will yield a virtual node with { sel: 'div#foo.bar.baz' }
An attribute starting with aria-
will be passed to the attributes module.
<button aria-label="Send" />
// { attrs: { 'aria-label': 'Send' } }
An attribute starting with data-
will be passed to the dataset module. Note that the data-
prefix will be removed and dashes will be converted to camel case.
<div data-foo-bar="baz" />
// { dataset: { fooBar: 'baz' } }
The is
attribute can be used when you want to instantiate your customized built-in elements.
<div is="custom-element" />
// { is: 'custom-element' }
An attribute starting with on
will passed to the event listeners module.
<div
onclick={(e) => {
console.log(e);
}}
/>
// { on: { click: f } }
The list
, the role
, and the popoverTarget
attributes will be passed to the attributes module.
Note that the attribute names will be lowercased.
<div role="button" />
// { attrs: { role: 'button' } }
<input list="options" />
// { attrs: { list: 'options' } }
<button popoverTarget="popover" />
// { attrs: { popovertarget: 'popover' } }
The $hook
attribute is treated as hooks.
<div
$hook={{
insert(vnode) {
console.log(vnode);
},
}}
/>
// { hook: { insert: f } }
For the sake of backward compatibility, hook
(without the dollar sign) also behaves the same. However it is deprecated and will be removed in the future.
The $key
attribute is treated as a key.
<div $key="foo" />
// { key: 'foo' }
For the sake of backward compatibility, key
(without the dollar sign) also behaves the same. However it is deprecated and will be removed in the future.
Attributes of <svg>
and its descendant elements are passed to the attributes module.
In Snabbdom, different functionalities are delegated to separate modules. Values can be passed to them via attributes starting with $
.
$attrs
(the attributes module)
<div $attrs={{ class: 'foo' }} />
// { attrs: { class: 'foo' } }
$class
(the class module)
<div $class={{ foo: true }} />
// { class: { foo: true } }
$dataset
(the dataset module)
<div $dataset={{ foo: 'bar' }} />
// { dataset: { foo: 'bar' } }
<div
$on={{
click: (e) => {
console.log(e);
},
}}
/>
// { on: { click: f } }
$props
(the props module)
<div $props={{ className: 'foo' }} />
// { props: { className: 'foo' } }
$style
(the style module)
<div $style={{ opacity: '0', delayed: { opacity: '1' }, remove: { opacity: '0' } }} />
// { style: { opacity: '0', delayed: { opacity: '1' }, remove: { opacity: '0' } } }
For the sake of backward compatibility, the following aliases are also defined. However they are deprecated and will be removed in the future.
Attribute | Alias(es) |
---|---|
$attrs |
attrs |
$class |
class |
$dataset |
data , dataset
|
$on |
on |
$props |
props |
$style |
style |
Just like built-in modules, you can pass an arbitrary value to your custom modules via an attribute starting with $
. For example, the expression <div $custom={{ foo: 'bar' }} />
will yield { custom: { foo: 'bar' } }
.
Unlike built-in modules, we have no assumptions on what kind of values should be passed to custom modules. You have to augment jsx.CustomModules
interface so that it will typecheck.
declare module '@herp-inc/snabbdom-jsx/jsx-runtime' {
namespace jsx {
interface CustomModules {
// Add your custom modules here
custom: {
foo: string;
};
}
}
}
A JSX component can be defined with a function with the signature of <Props>(props: Props) => Snabbdom.VNodeChildElement
.
import type Snabbdom from '@herp-inc/snabbdom-jsx';
type Props = {
children: Snabbdom.Node;
name: string;
};
const Component: Snabbdom.Component<Props> = ({ children, name }) => (
<div>
Hello, {name}!<div>{children}</div>
</div>
);
const vnode = <Component />;
-
boolean
,null
, andundefined
values are not be filtered out of the tree but rendered as comment nodes (for the sake of correct diffing) -
snabbdom-pragma
-styleMODULE-PROPERTY
notation is not supported.
The code base is based on these libraries: