Skip to content

Add support using x as a JSX pragma #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
.nyc_output/
coverage/
node_modules/
test/jsx-*.js
yarn.lock
14 changes: 11 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ module.exports = x

// Creating xast elements.
function x(name, attributes) {
var node = {type: 'element', name: name, attributes: {}, children: []}
var node =
name == null
? {type: 'root', children: []}
: {type: 'element', name: name, attributes: {}, children: []}
var index = 1
var key

if (typeof name !== 'string' || !name) {
if (name != null && typeof name !== 'string') {
throw new Error('Expected element name, got `' + name + '`')
}

// Handle props.
if (attributes) {
if (
name == null ||
typeof attributes === 'string' ||
typeof attributes === 'number' ||
'length' in attributes
Expand Down Expand Up @@ -51,7 +55,11 @@ function addChild(nodes, value) {
addChild(nodes, value[index])
}
} else if (typeof value === 'object' && value.type) {
nodes.push(value)
if (value.type === 'root') {
addChild(nodes, value.children)
} else {
nodes.push(value)
}
} else {
throw new TypeError('Expected node, nodes, string, got `' + value + '`')
}
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,26 @@
"@types/xast": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/plugin-syntax-jsx": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"buble": "^0.20.0",
"dtslint": "^4.0.0",
"nyc": "^15.0.0",
"prettier": "^2.0.0",
"remark-cli": "^9.0.0",
"remark-preset-wooorm": "^8.0.0",
"tape": "^5.0.0",
"unist-builder": "^2.0.0",
"xo": "^0.34.0"
},
"scripts": {
"generate": "node script/generate-jsx",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test",
"test-coverage": "nyc --reporter lcov tape test.js",
"test-coverage": "nyc --reporter lcov tape test/index.js",
"test-types": "dtslint types",
"test": "npm run format && npm run test-coverage && npm run test-types"
"test": "npm run generate && npm run format && npm run test-coverage && npm run test-types"
},
"nyc": {
"check-coverage": true,
Expand Down
85 changes: 78 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ console.log(
// For other xast nodes, such as comments, instructions, doctypes, or cdata
// can be created with unist-builder:
console.log(
u('root', [
x(null, [
u('instruction', {name: 'xml'}, 'version="1.0" encoding="UTF-8"'),
x('album', [
u('comment', 'Great album!'),
Expand Down Expand Up @@ -142,33 +142,94 @@ Yields:

## API

### `x(name[, attributes][, …children])`
### `x(name?[, attributes][, …children])`

Create XML *[trees][tree]* in **[xast][]**.

##### Signatures

* `x(): root`
* `x(null[, …children]): root`
* `x(name[, attributes][, …children]): element`

##### Parameters

###### `name`

Qualified name (`string`).
Qualified name (`string`, optional).
Case sensitive and can contain a namespace prefix (such as `rdf:RDF`).
When string, an [`Element`][element] is built.
When nullish, a [`Root`][root] is built instead.

###### `attributes`

Map of attributes (`Object.<*>`, optional).
Nullish (`null` or `undefined`) or `NaN` values are ignored, other values are
turned to strings.

Cannot be omitted if `children` is a `Node`.
Cannot be given if building a [`Root`][root].
Cannot be omitted when building an [`Element`][element] if the first child is a
[`Node`][node].

###### `children`

(Lists of) child nodes (`string`, `Node`, `Array.<children>`, 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 a [`Root`][root] node is given, its children are used instead.

##### Returns

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

## JSX

`xastscript` can be used as a pragma for JSX.
The example above (omitting the second) can then be written like so:

```jsx
var u = require('unist-builder')
var x = require('xastscript')

console.log(
<album id={123}>
<name>Born in the U.S.A.</name>
<artist>Bruce Springsteen</artist>
<releasedate>1984-04-06</releasedate>
</album>
)

console.log(
<>
{u('instruction', {name: 'xml'}, 'version="1.0" encoding="UTF-8"')}
<album>
{u('comment', 'Great album!')}
<name>Born in the U.S.A.</name>
<description>{u('cdata', '3 < 5 & 8 > 13')}</description>
</album>
</>
)
```

Note that you must still import `xastscript` 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: 'x'` and `jsxFragment: 'null'`
(note that `jsxFragment` is currently only available on the API, not the CLI).

For [Babel][], use [`@babel/plugin-transform-react-jsx`][babel-jsx] (in classic
mode), and pass `pragma: 'x'` and `pragmaFrag: 'null'`.

Babel also lets you configure this in a script:

```jsx
/** @jsx x */
/** @jsxFrag null */
var x = require('xastscript')

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

## Security

Expand Down Expand Up @@ -249,10 +310,20 @@ abide by its terms.

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

[node]: https://github.com/syntax-tree/unist#node

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

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

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

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

[h]: https://github.com/syntax-tree/hastscript

[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
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: 'x',
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: 'x', pragmaFrag: 'null'}]
]
}).code
)
46 changes: 42 additions & 4 deletions test.js → test/core.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
'use strict'

var test = require('tape')
var x = require('.')
var x = require('..')

test('xastscript', function (t) {
t.equal(typeof x, 'function', 'should expose a function')

t.deepEqual(
x(),
{type: 'root', children: []},
'should create a root when w/o `name`'
)

t.throws(
function () {
x()
x(1)
},
/Expected element name, got `undefined`/,
'should throw without `name`'
/Expected element name, got `1`/,
'should throw w/ incorrect `name`'
)

t.deepEqual(
Expand Down Expand Up @@ -156,5 +162,37 @@ test('xastscript', function (t) {
'should support omitting attributes when given an array for a child'
)

t.deepEqual(
x(null, '1'),
{type: 'root', children: [{type: 'text', value: '1'}]},
'should create a root with a textual child'
)

t.deepEqual(
x(null, 1),
{type: 'root', children: [{type: 'text', value: '1'}]},
'should create a root with a numerical child'
)

t.deepEqual(
x(null, x('a')),
{
type: 'root',
children: [{type: 'element', name: 'a', attributes: {}, children: []}]
},
'should create a root with a node child'
)

t.deepEqual(
x('a', {}, [x(null, x('b'))]),
{
type: 'element',
name: 'a',
attributes: {},
children: [{type: 'element', name: 'b', attributes: {}, children: []}]
},
'should create a node w/ by unraveling roots'
)

t.end()
})
7 changes: 7 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

/* eslint-disable import/no-unassigned-import */
require('./core')
require('./jsx-babel')
require('./jsx-buble')
/* eslint-enable import/no-unassigned-import */
Loading