├── .prettierignore ├── .npmrc ├── .gitignore ├── index.js ├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── tsconfig.json ├── index.test-d.ts ├── license ├── test.js ├── lib └── index.js ├── package.json └── readme.md /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.d.ts 3 | *.log 4 | coverage/ 5 | node_modules/ 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./lib/index.js').ChildrenOrValue} ChildrenOrValue 3 | * @typedef {import('./lib/index.js').Props} Props 4 | */ 5 | 6 | export {u} from './lib/index.js' 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | name: bb 2 | on: 3 | issues: 4 | types: [opened, reopened, edited, closed, labeled, unlabeled] 5 | pull_request_target: 6 | types: [opened, reopened, edited, closed, labeled, unlabeled] 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: unifiedjs/beep-boop-beta@main 12 | with: 13 | repo-token: ${{secrets.GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "exactOptionalPropertyTypes": true, 8 | "lib": ["es2022"], 9 | "module": "node16", 10 | "strict": true, 11 | "target": "es2022" 12 | }, 13 | "exclude": ["coverage/", "node_modules/"], 14 | "include": ["**/*.js"] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: ${{matrix.node}} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v3 17 | strategy: 18 | matrix: 19 | node: 20 | - lts/gallium 21 | - node 22 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import {expectAssignable, expectType} from 'tsd' 2 | import type {HTML, List, ListItem, Text} from 'mdast' 3 | import {u} from 'unist-builder' 4 | 5 | expectType<{type: 'example'}>(u('example')) 6 | expectType<{type: 'example'} & {property: true}>(u('example', {property: true})) 7 | 8 | const node1 = u('text', 'text') 9 | 10 | expectType<{type: 'text'; value: string}>(node1) 11 | expectAssignable(node1) 12 | 13 | const node2 = u('list', [ 14 | u('listItem', [u('html', {checked: true}, 'text')]) 15 | ]) 16 | 17 | expectType<{ 18 | type: 'list' 19 | children: Array<{ 20 | type: 'listItem' 21 | children: Array<{type: 'html'; value: string} & {checked: boolean}> 22 | }> 23 | }>(node2) 24 | expectAssignable(node2) 25 | expectAssignable(node2.children[0]) 26 | expectAssignable(node2.children[0].children[0]) 27 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Eugene Sharygin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import test from 'node:test' 3 | import {u} from 'unist-builder' 4 | 5 | test('u', async function (t) { 6 | await t.test('should expose the public api', async function () { 7 | assert.deepEqual(Object.keys(await import('unist-builder')).sort(), ['u']) 8 | }) 9 | 10 | await t.test('should work', async function () { 11 | assert.deepEqual( 12 | u('root', [ 13 | u('subtree', {id: 1}), 14 | u('subtree', {id: 2}, [ 15 | u('node', [u('leaf', 'leaf-1'), u('leaf', 'leaf-2'), u('leaf', '')]), 16 | u('leaf', {id: 3}, 'leaf-3') 17 | ]) 18 | ]), 19 | { 20 | type: 'root', 21 | children: [ 22 | {type: 'subtree', id: 1}, 23 | { 24 | type: 'subtree', 25 | id: 2, 26 | children: [ 27 | { 28 | type: 'node', 29 | children: [ 30 | {type: 'leaf', value: 'leaf-1'}, 31 | {type: 'leaf', value: 'leaf-2'}, 32 | {type: 'leaf', value: ''} 33 | ] 34 | }, 35 | {type: 'leaf', id: 3, value: 'leaf-3'} 36 | ] 37 | } 38 | ] 39 | } 40 | ) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('unist').Node} Node 3 | */ 4 | 5 | /** 6 | * @typedef {Array | string} ChildrenOrValue 7 | * List to use as `children` or value to use as `value`. 8 | * 9 | * @typedef {Record} Props 10 | * Other fields to add to the node. 11 | */ 12 | 13 | /** 14 | * Build a node. 15 | * 16 | * @template {string} T 17 | * @template {Props} P 18 | * @template {Array} C 19 | * 20 | * @overload 21 | * @param {T} type 22 | * @returns {{type: T}} 23 | * 24 | * @overload 25 | * @param {T} type 26 | * @param {P} props 27 | * @returns {{type: T} & P} 28 | * 29 | * @overload 30 | * @param {T} type 31 | * @param {string} value 32 | * @returns {{type: T, value: string}} 33 | * 34 | * @overload 35 | * @param {T} type 36 | * @param {P} props 37 | * @param {string} value 38 | * @returns {{type: T, value: string} & P} 39 | * 40 | * @overload 41 | * @param {T} type 42 | * @param {C} children 43 | * @returns {{type: T, children: C}} 44 | * 45 | * @overload 46 | * @param {T} type 47 | * @param {P} props 48 | * @param {C} children 49 | * @returns {{type: T, children: C} & P} 50 | * 51 | * @param {string} type 52 | * Node type. 53 | * @param {ChildrenOrValue | Props | null | undefined} [props] 54 | * Fields assigned to node (default: `undefined`). 55 | * @param {ChildrenOrValue | null | undefined} [value] 56 | * Children of node or value of `node` (cast to string). 57 | * @returns {Node} 58 | * Built node. 59 | */ 60 | export function u(type, props, value) { 61 | /** @type {Node} */ 62 | const node = {type: String(type)} 63 | 64 | if ( 65 | (value === undefined || value === null) && 66 | (typeof props === 'string' || Array.isArray(props)) 67 | ) { 68 | value = props 69 | } else { 70 | Object.assign(node, props) 71 | } 72 | 73 | if (Array.isArray(value)) { 74 | // @ts-expect-error: create a parent. 75 | node.children = value 76 | } else if (value !== undefined && value !== null) { 77 | // @ts-expect-error: create a literal. 78 | node.value = String(value) 79 | } 80 | 81 | return node 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unist-builder", 3 | "version": "4.0.0", 4 | "description": "unist utility to create a new trees with a nice syntax", 5 | "license": "MIT", 6 | "keywords": [ 7 | "unist", 8 | "unist-util", 9 | "util", 10 | "utility", 11 | "tree", 12 | "ast", 13 | "build", 14 | "builder", 15 | "create", 16 | "dsl", 17 | "hyperscript", 18 | "sugar", 19 | "syntax" 20 | ], 21 | "repository": "syntax-tree/unist-builder", 22 | "bugs": "https://github.com/syntax-tree/unist-builder/issues", 23 | "funding": { 24 | "type": "opencollective", 25 | "url": "https://opencollective.com/unified" 26 | }, 27 | "author": "Eugene Sharygin ", 28 | "contributors": [ 29 | "Eugene Sharygin ", 30 | "Titus Wormer (https://wooorm.com)", 31 | "Christian Murphy " 32 | ], 33 | "sideEffects": false, 34 | "type": "module", 35 | "exports": "./index.js", 36 | "files": [ 37 | "lib/", 38 | "index.d.ts", 39 | "index.js" 40 | ], 41 | "dependencies": { 42 | "@types/unist": "^3.0.0" 43 | }, 44 | "devDependencies": { 45 | "@types/mdast": "^4.0.0", 46 | "@types/node": "^20.0.0", 47 | "c8": "^8.0.0", 48 | "prettier": "^2.0.0", 49 | "remark-cli": "^11.0.0", 50 | "remark-preset-wooorm": "^9.0.0", 51 | "tsd": "^0.28.0", 52 | "type-coverage": "^2.0.0", 53 | "typescript": "^5.0.0", 54 | "xo": "^0.54.0" 55 | }, 56 | "scripts": { 57 | "prepack": "npm run build && npm run format", 58 | "build": "tsc --build --clean && tsc --build && tsd && type-coverage", 59 | "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", 60 | "test-api": "node --conditions development test.js", 61 | "test-coverage": "c8 --100 --reporter lcov npm run test-api", 62 | "test": "npm run build && npm run format && npm run test-coverage" 63 | }, 64 | "prettier": { 65 | "bracketSpacing": false, 66 | "semi": false, 67 | "singleQuote": true, 68 | "tabWidth": 2, 69 | "trailingComma": "none", 70 | "useTabs": false 71 | }, 72 | "remarkConfig": { 73 | "plugins": [ 74 | "remark-preset-wooorm" 75 | ] 76 | }, 77 | "typeCoverage": { 78 | "atLeast": 100, 79 | "detail": true, 80 | "ignoreCatch": true, 81 | "strict": true 82 | }, 83 | "xo": { 84 | "prettier": true 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # unist-builder 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | [![Sponsors][sponsors-badge]][collective] 8 | [![Backers][backers-badge]][collective] 9 | [![Chat][chat-badge]][chat] 10 | 11 | [unist][] utility to create trees with ease. 12 | 13 | ## Contents 14 | 15 | * [What is this?](#what-is-this) 16 | * [When should I use this?](#when-should-i-use-this) 17 | * [Install](#install) 18 | * [Use](#use) 19 | * [API](#api) 20 | * [`u(type[, props][, children|value])`](#utype-props-childrenvalue) 21 | * [`ChildrenOrValue`](#childrenorvalue) 22 | * [`Props`](#props) 23 | * [Types](#types) 24 | * [Compatibility](#compatibility) 25 | * [Related](#related) 26 | * [Contribute](#contribute) 27 | * [License](#license) 28 | 29 | ## What is this? 30 | 31 | This package is a hyperscript interface (like `createElement` from React and 32 | `h` from Vue and such) to help with creating unist trees. 33 | 34 | ## When should I use this? 35 | 36 | You can use this utility in your project when you generate syntax trees with 37 | code. 38 | It helps because it replaces most of the repetition otherwise needed in a syntax 39 | tree with function calls. 40 | 41 | You can instead use [`hastscript`][hastscript] or [`xastscript`][xastscript] 42 | when creating hast (HTML) or xast (XML) nodes. 43 | 44 | ## Install 45 | 46 | This package is [ESM only][esm]. 47 | In Node.js (version 16+), install with [npm][]: 48 | 49 | ```sh 50 | npm install unist-builder 51 | ``` 52 | 53 | In Deno with [`esm.sh`][esmsh]: 54 | 55 | ```js 56 | import {u} from 'https://esm.sh/unist-builder@4' 57 | ``` 58 | 59 | In browsers with [`esm.sh`][esmsh]: 60 | 61 | ```html 62 | 65 | ``` 66 | 67 | ## Use 68 | 69 | ```js 70 | import {u} from 'unist-builder' 71 | 72 | const tree = u('root', [ 73 | u('subtree', {id: 1}), 74 | u('subtree', {id: 2}, [ 75 | u('node', [u('leaf', 'leaf 1'), u('leaf', 'leaf 2')]), 76 | u('leaf', {id: 3}, 'leaf 3'), 77 | u('void', {id: 4}) 78 | ]) 79 | ]) 80 | 81 | console.dir(tree, {depth: undefined}) 82 | ``` 83 | 84 | …yields: 85 | 86 | ```js 87 | { 88 | type: 'root', 89 | children: [ 90 | {type: 'subtree', id: 1}, 91 | { 92 | type: 'subtree', 93 | id: 2, 94 | children: [ 95 | { 96 | type: 'node', 97 | children: [ 98 | {type: 'leaf', value: 'leaf 1'}, 99 | {type: 'leaf', value: 'leaf 2'} 100 | ] 101 | }, 102 | {type: 'leaf', id: 3, value: 'leaf 3'}, 103 | {type: 'void', id: 4} 104 | ] 105 | } 106 | ] 107 | } 108 | ``` 109 | 110 | ## API 111 | 112 | This package exports the identifier [`u`][u]. 113 | There is no default export. 114 | 115 | ### `u(type[, props][, children|value])` 116 | 117 | Build a node. 118 | 119 | ###### Signatures 120 | 121 | * `u(type[, props], children)` — create a parent ([`Parent`][parent]) 122 | * `u(type[, props], value)` — create a literal ([`Literal`][literal]) 123 | * `u(type[, props])` — create a void node (neither parent not literal) 124 | 125 | ###### Parameters 126 | 127 | * `type` (`string`) 128 | — node type 129 | * `props` (`Record`) 130 | — fields assigned to node 131 | * `children` ([`Array`][node]) 132 | — children of node 133 | * `value` (`*`) 134 | — value of `node` (cast to string) 135 | 136 | ###### Returns 137 | 138 | Built node ([`Node`][node]). 139 | 140 | ### `ChildrenOrValue` 141 | 142 | List to use as `children` or value to use as `value` (TypeScript type). 143 | 144 | ###### Type 145 | 146 | ```ts 147 | type ChildrenOrValue = Array | string 148 | ``` 149 | 150 | ### `Props` 151 | 152 | Other fields to add to the node (TypeScript type). 153 | 154 | ###### Type 155 | 156 | ```ts 157 | export type Props = Record 158 | ``` 159 | 160 | ## Types 161 | 162 | This package is fully typed with [TypeScript][]. 163 | It exports the additional types [`ChildrenOrValue`][childrenorvalue] and 164 | [`Props`][props]. 165 | 166 | ## Compatibility 167 | 168 | Projects maintained by the unified collective are compatible with maintained 169 | versions of Node.js. 170 | 171 | When we cut a new major release, we drop support for unmaintained versions of 172 | Node. 173 | This means we try to keep the current release line, `unist-builder@^4`, 174 | compatible with Node.js 16. 175 | 176 | ## Related 177 | 178 | * [`hastscript`](https://github.com/syntax-tree/hastscript) 179 | — create [hast][] trees 180 | * [`xastscript`](https://github.com/syntax-tree/xastscript) 181 | — create [xast][] trees 182 | 183 | ## Contribute 184 | 185 | See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for 186 | ways to get started. 187 | See [`support.md`][support] for ways to get help. 188 | 189 | This project has a [code of conduct][coc]. 190 | By interacting with this repository, organization, or community you agree to 191 | abide by its terms. 192 | 193 | ## License 194 | 195 | [MIT][license] © Eugene Sharygin 196 | 197 | 198 | 199 | [build-badge]: https://github.com/syntax-tree/unist-builder/workflows/main/badge.svg 200 | 201 | [build]: https://github.com/syntax-tree/unist-builder/actions 202 | 203 | [coverage-badge]: https://img.shields.io/codecov/c/github/syntax-tree/unist-builder.svg 204 | 205 | [coverage]: https://codecov.io/github/syntax-tree/unist-builder 206 | 207 | [downloads-badge]: https://img.shields.io/npm/dm/unist-builder.svg 208 | 209 | [downloads]: https://www.npmjs.com/package/unist-builder 210 | 211 | [size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=unist-builder 212 | 213 | [size]: https://bundlejs.com/?q=unist-builder 214 | 215 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 216 | 217 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 218 | 219 | [collective]: https://opencollective.com/unified 220 | 221 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 222 | 223 | [chat]: https://github.com/syntax-tree/unist/discussions 224 | 225 | [npm]: https://docs.npmjs.com/cli/install 226 | 227 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 228 | 229 | [esmsh]: https://esm.sh 230 | 231 | [typescript]: https://www.typescriptlang.org 232 | 233 | [license]: license 234 | 235 | [health]: https://github.com/syntax-tree/.github 236 | 237 | [contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md 238 | 239 | [support]: https://github.com/syntax-tree/.github/blob/main/support.md 240 | 241 | [coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md 242 | 243 | [unist]: https://github.com/syntax-tree/unist 244 | 245 | [node]: https://github.com/syntax-tree/unist#node 246 | 247 | [parent]: https://github.com/syntax-tree/unist#parent 248 | 249 | [literal]: https://github.com/syntax-tree/unist#literal 250 | 251 | [hast]: https://github.com/syntax-tree/hast 252 | 253 | [xast]: https://github.com/syntax-tree/xast 254 | 255 | [hastscript]: https://github.com/syntax-tree/hastscript 256 | 257 | [xastscript]: https://github.com/syntax-tree/xastscript 258 | 259 | [u]: #utype-props-childrenvalue 260 | 261 | [props]: #props 262 | 263 | [childrenorvalue]: #childrenorvalue 264 | --------------------------------------------------------------------------------