Skip to content

Commit

Permalink
Add support for using h, s as a JSX pragmas
Browse files Browse the repository at this point in the history
Closes GH-15.

Reviewed-by: Christian Murphy <christian.murphy.42@gmail.com>
Reviewed-by: Remco Haszing <remcohaszing@gmail.com>
  • Loading branch information
wooorm authored Apr 26, 2021
1 parent ecb5b46 commit dd94377
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
.nyc_output/
coverage/
node_modules/
test/jsx-*.js
hastscript.js
hastscript.min.js
yarn.lock
18 changes: 14 additions & 4 deletions factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,23 @@ function factory(schema, defaultTagName, caseSensitive) {

// Hyperscript compatible DSL for creating virtual hast trees.
function h(selector, properties) {
var node = parseSelector(selector, defaultTagName)
var name = node.tagName.toLowerCase()
var node =
selector == null
? {type: 'root', children: []}
: parseSelector(selector, defaultTagName)
var name = selector == null ? null : node.tagName.toLowerCase()
var index = 1
var property

// Normalize the name.
node.tagName = adjust && own.call(adjust, name) ? adjust[name] : name
if (name != null) {
node.tagName = adjust && own.call(adjust, name) ? adjust[name] : name
}

// Handle props.
if (properties) {
if (
name == null ||
typeof properties === 'string' ||
'length' in properties ||
isNode(name, properties)
Expand Down Expand Up @@ -134,7 +140,11 @@ function addChild(nodes, value) {
addChild(nodes, value[index])
}
} else if (typeof value === 'object' && 'type' in value) {
nodes.push(value)
if (value.type === 'root') {
addChild(nodes, value.children)
} else {
nodes.push(value)
}
} else {
throw new Error('Expected node, nodes, or string, got `' + value + '`')
}
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@
"space-separated-tokens": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-syntax-jsx": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"browserify": "^17.0.0",
"buble": "^0.20.0",
"dtslint": "^4.0.0",
"nyc": "^15.0.0",
"prettier": "^2.0.0",
Expand All @@ -54,16 +58,17 @@
"svg-tag-names": "^2.0.0",
"tape": "^5.0.0",
"tinyify": "^3.0.0",
"unist-builder": "^2.0.0",
"xo": "^0.35.0"
},
"scripts": {
"generate": "node build",
"generate": "node script/generate-jsx && node script/build",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"build-bundle": "browserify . -s hastscript > hastscript.js",
"build-mangle": "browserify . -s hastscript -p tinyify > hastscript.min.js",
"build": "npm run build-bundle && npm run build-mangle",
"test-api": "node test",
"test-coverage": "nyc --reporter lcov tape test.js",
"test-coverage": "nyc --reporter lcov tape test/index.js",
"test-types": "dtslint .",
"test": "npm run generate && npm run format && npm run build && npm run test-coverage && npm run test-types"
},
Expand Down
114 changes: 106 additions & 8 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,34 +138,124 @@ Yields:

## API

### `h(selector?[, properties][, ...children])`
### `h(selector?[, properties][, children])`

### `s(selector?[, properties][, ...children])`
### `s(selector?[, properties][, children])`

DSL to create virtual [**hast**][hast] [*trees*][tree] for HTML or SVG.
Create virtual [**hast**][hast] [*trees*][tree] for HTML or SVG.

##### Signatures

* `h(): root`
* `h(null[, …children]): root`
* `h(name[, properties][, …children]): element`

(and the same for `s`).

##### Parameters

###### `selector`

Simple CSS selector (`string`, optional).
Can contain a tag name (`foo`), IDs (`#bar`), and classes (`.baz`).
If there is no tag name in the selector, `h` defaults to a `div` element,
and `s` to a `g` element.
If the selector is a string but there is no tag name in it, `h` defaults to
build a `div` element, and `s` to a `g` element.
`selector` is parsed by [`hast-util-parse-selector`][parse-selector].
When string, builds an [`Element`][element].
When nullish, builds a [`Root`][root] instead.

###### `properties`

Map of properties (`Object.<*>`, optional).
Keys should match either the HTML attribute name, or the DOM property name, but
are case-insensitive.
Cannot be given when building a [`Root`][root].

###### `children`

(Lists of) child nodes (`string`, `Node`, `Array.<string|Node>`, optional).
When strings are encountered, they are mapped to [`text`][text] nodes.
(Lists of) children (`string`, `number`, `Node`, `Array.<children>`, optional).
When strings or numbers are encountered, they are mapped to [`Text`][text]
nodes.
If [`Root`][root] nodes are given, their children are used instead.

##### Returns

[`Element`][element].
[`Element`][element] or [`Root`][root].

## JSX

`hastscript` can be used as a pragma for JSX.
The example above can then be written like so, using inline Babel pragmas, so
that SVG can be used too:

`example-html.jsx`:

```jsx
/** @jsx h */
/** @jsxFrag null */
var h = require('hastscript')

console.log(
<div class="foo" id="some-id">
<span>some text</span>
<input type="text" value="foo" />
<a class="alpha bravo charlie" download>
deltaecho
</a>
</div>
)

console.log(
<form method="POST">
<input type="text" name="foo" />
<input type="text" name="bar" />
<input type="submit" name="send" />
</form>
)
```

`example-svg.jsx`:

```jsx
/** @jsx s */
/** @jsxFrag null */
var s = require('hastscript/svg')

console.log(
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 500 500">
<title>SVG `&lt;circle&gt;` element</title>
<circle cx={120} cy={120} r={100} />
</svg>
)
```

Because JSX does not allow dots (`.`) or number signs (`#`) in tag names, you
have to pass class names and IDs in as attributes.

Note that you must still import `hastscript` yourself and configure your
JavaScript compiler to use the identifier you assign it to as a pragma (and
pass `null` for fragments).

For [bublé][], this can be done by setting `jsx: 'h'` and `jsxFragment: 'null'`
(note that `jsxFragment` is currently only available on the API, not the CLI).
Bublé is less ideal because it allows a single pragma.

For [Babel][], use [`@babel/plugin-transform-react-jsx`][babel-jsx] (in classic
mode), and pass `pragma: 'h'` and `pragmaFrag: 'null'`.
This is less ideal because it allows a single pragma.

Babel also lets you configure this in a script:

```jsx
/** @jsx s */
/** @jsxFrag null */
var s = require('hastscript/svg')

console.log(<rect />)
```

This is useful because it allows using *both* `hastscript/html` and
`hastscript/svg`, although in different files.

## Security

Expand Down Expand Up @@ -317,10 +407,18 @@ abide by its terms.

[element]: https://github.com/syntax-tree/hast#element

[root]: https://github.com/syntax-tree/xast#root

[text]: https://github.com/syntax-tree/hast#text

[u]: https://github.com/syntax-tree/unist-builder

[bublé]: https://github.com/Rich-Harris/buble

[babel]: https://github.com/babel/babel

[babel-jsx]: https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-react-jsx

[parse-selector]: https://github.com/syntax-tree/hast-util-parse-selector

[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
Expand Down
File renamed without changes.
25 changes: 25 additions & 0 deletions script/generate-jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

var fs = require('fs')
var path = require('path')
var buble = require('buble')
var babel = require('@babel/core')

var doc = String(fs.readFileSync(path.join('test', 'jsx.jsx')))

fs.writeFileSync(
path.join('test', 'jsx-buble.js'),
buble.transform(doc.replace(/'name'/, "'jsx (buble)'"), {
jsx: 'h',
jsxFragment: 'null'
}).code
)

fs.writeFileSync(
path.join('test', 'jsx-babel.js'),
babel.transform(doc.replace(/'name'/, "'jsx (babel)'"), {
plugins: [
['@babel/plugin-transform-react-jsx', {pragma: 'h', pragmaFrag: 'null'}]
]
}).code
)
Loading

0 comments on commit dd94377

Please sign in to comment.