diff --git a/.github/workflows/bb.yml b/.github/workflows/bb.yml index 291ab09..0198fc3 100644 --- a/.github/workflows/bb.yml +++ b/.github/workflows/bb.yml @@ -2,7 +2,7 @@ name: bb on: issues: types: [opened, reopened, edited, closed, labeled, unlabeled] - pull_request: + pull_request_target: types: [opened, reopened, edited, closed, labeled, unlabeled] jobs: main: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe284ad..69924a4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,5 +17,5 @@ jobs: strategy: matrix: node: - - lts/erbium + - lts/fermium - node diff --git a/.npmrc b/.npmrc index 43c97e7..9951b11 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock=false +ignore-scripts=true diff --git a/index.js b/index.js index 90a4fc9..f33ac57 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,8 @@ /** - * @typedef {import('./lib/index.js').XChild}} Child Acceptable child value - * @typedef {import('./lib/index.js').XAttributes}} Attributes Acceptable attributes value. + * @typedef {import('./lib/index.js').XChild}} Child + * Acceptable child value + * @typedef {import('./lib/index.js').XAttributes}} Attributes + * Acceptable attributes value. */ export {x} from './lib/index.js' diff --git a/lib/index.js b/lib/index.js index 6af9859..71cdc57 100644 --- a/lib/index.js +++ b/lib/index.js @@ -8,12 +8,12 @@ * @typedef {{[attribute: string]: XValue}} XAttributes Attributes to support JS primitive types * * @typedef {string|number|null|undefined} XPrimitiveChild - * @typedef {Array.} XArrayChild + * @typedef {Array} XArrayChild * @typedef {Node|XPrimitiveChild|XArrayChild} XChild - * @typedef {import('./jsx-classic').Element} x.JSX.Element - * @typedef {import('./jsx-classic').IntrinsicAttributes} x.JSX.IntrinsicAttributes - * @typedef {import('./jsx-classic').IntrinsicElements} x.JSX.IntrinsicElements - * @typedef {import('./jsx-classic').ElementChildrenAttribute} x.JSX.ElementChildrenAttribute + * @typedef {import('./jsx-classic.js').Element} x.JSX.Element + * @typedef {import('./jsx-classic.js').IntrinsicAttributes} x.JSX.IntrinsicAttributes + * @typedef {import('./jsx-classic.js').IntrinsicElements} x.JSX.IntrinsicElements + * @typedef {import('./jsx-classic.js').ElementChildrenAttribute} x.JSX.ElementChildrenAttribute */ /** @@ -27,9 +27,9 @@ export const x = /** * @type {{ * (): Root - * (name: null|undefined, ...children: XChild[]): Root - * (name: string, attributes: XAttributes, ...children: XChild[]): Element - * (name: string, ...children: XChild[]): Element + * (name: null|undefined, ...children: Array): Root + * (name: string, attributes: XAttributes, ...children: Array): Element + * (name: string, ...children: Array): Element * }} */ ( @@ -38,24 +38,25 @@ export const x = * * @param {string|null} [name] * @param {XAttributes|XChild} [attributes] - * @param {XChild[]} children + * @param {Array} children * @returns {XResult} */ function (name, attributes, ...children) { - var index = -1 + let index = -1 /** @type {XResult} */ - var node - /** @type {string} */ - var key + let node if (name === undefined || name === null) { node = {type: 'root', children: []} - // @ts-ignore Root builder doesn’t accept attributes. + // @ts-expect-error Root builder doesn’t accept attributes. children.unshift(attributes) } else if (typeof name === 'string') { node = {type: 'element', name, attributes: {}, children: []} if (isAttributes(attributes)) { + /** @type {string} */ + let key + for (key in attributes) { // Ignore nullish and NaN values. if ( @@ -64,7 +65,7 @@ export const x = (typeof attributes[key] !== 'number' || !Number.isNaN(attributes[key])) ) { - // @ts-ignore Pretty sure we just set it. + // @ts-expect-error Pretty sure we just set it. node.attributes[key] = String(attributes[key]) } } @@ -85,11 +86,11 @@ export const x = ) /** - * @param {Array.} nodes + * @param {Array} nodes * @param {XChild} value */ function addChild(nodes, value) { - var index = -1 + let index = -1 if (value === undefined || value === null) { // Empty. diff --git a/lib/jsx-automatic.d.ts b/lib/jsx-automatic.d.ts index 3c45020..ac742b3 100644 --- a/lib/jsx-automatic.d.ts +++ b/lib/jsx-automatic.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable-next-line @typescript-eslint/consistent-type-imports -- fix in major */ import {XAttributes, XChild, XResult} from './index.js' export namespace JSX { @@ -11,6 +12,8 @@ export namespace JSX { */ type IntrinsicAttributes = never + /* eslint-disable @typescript-eslint/consistent-type-definitions -- interfaces are required here */ + /** * This defines the prop types for known elements. * @@ -39,4 +42,6 @@ export namespace JSX { */ children?: never } + + /* eslint-enable @typescript-eslint/consistent-type-definitions */ } diff --git a/lib/jsx-classic.d.ts b/lib/jsx-classic.d.ts index 1e1e0a0..53de7ad 100644 --- a/lib/jsx-classic.d.ts +++ b/lib/jsx-classic.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable-next-line @typescript-eslint/consistent-type-imports -- fix in major */ import {XAttributes, XChild, XResult} from './index.js' /** @@ -16,6 +17,8 @@ export type Element = XResult */ export type IntrinsicAttributes = never +/* eslint-disable @typescript-eslint/consistent-type-definitions -- interfaces are required here */ + /** * This defines the prop types for known elements. * @@ -44,3 +47,5 @@ export interface ElementChildrenAttribute { */ [children]?: never } + +/* eslint-enable @typescript-eslint/consistent-type-definitions */ diff --git a/lib/runtime.js b/lib/runtime.js index b608d4e..51a71a8 100644 --- a/lib/runtime.js +++ b/lib/runtime.js @@ -34,7 +34,7 @@ export const jsx = * @returns {XResult} */ function (name, props) { - var {children, ...properties} = props + const {children, ...properties} = props return name === null ? x(name, children) : x(name, properties, children) } ) diff --git a/package.json b/package.json index 8ad5e34..3678641 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xastscript", - "version": "3.0.0", + "version": "3.0.1", "description": "xast utility to create trees", "license": "MIT", "keywords": [ @@ -32,7 +32,9 @@ "files": [ "lib/", "index.d.ts", - "index.js" + "index.js", + "jsx-runtime.d.ts", + "jsx-runtime.js" ], "exports": { ".": "./index.js", @@ -48,20 +50,19 @@ "@babel/plugin-transform-react-jsx": "^7.0.0", "@types/babel__core": "^7.0.0", "@types/tape": "^4.0.0", - "astring": "^1.0.0", - "buble": "^0.20.0", "c8": "^7.0.0", "estree-util-build-jsx": "^2.0.0", + "estree-util-to-js": "^1.0.0", "prettier": "^2.0.0", - "remark-cli": "^9.0.0", - "remark-preset-wooorm": "^8.0.0", + "remark-cli": "^11.0.0", + "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "tape": "^5.0.0", - "tsd": "^0.14.0", + "tsd": "^0.23.0", "type-coverage": "^2.0.0", "typescript": "^4.0.0", "unist-builder": "^3.0.0", - "xo": "^0.39.0" + "xo": "^0.52.0" }, "scripts": { "prepack": "npm run build && npm run format", @@ -81,11 +82,7 @@ "trailingComma": "none" }, "xo": { - "prettier": true, - "rules": { - "no-var": "off", - "prefer-arrow-callback": "off" - } + "prettier": true }, "remarkConfig": { "plugins": [ diff --git a/readme.md b/readme.md index 0930dfd..c0d62db 100644 --- a/readme.md +++ b/readme.md @@ -8,20 +8,62 @@ [![Backers][backers-badge]][collective] [![Chat][chat-badge]][chat] -**[xast][]** utility to create XML *[trees][tree]* (like [`hastscript`][h] for -**[hast][]** and [`unist-builder`][u] for **[unist][]**). +[xast][] utility to create trees with ease. -## Install +## Contents + +* [What is this?](#what-is-this) +* [When should I use this?](#when-should-i-use-this) +* [Install](#install) +* [Use](#use) +* [API](#api) + * [`x(name?[, attributes][, …children])`](#xname-attributes-children) +* [JSX](#jsx) +* [Types](#types) +* [Compatibility](#compatibility) +* [Security](#security) +* [Related](#related) +* [Contribute](#contribute) +* [License](#license) + +## What is this? + +This package is a hyperscript interface (like `createElement` from React and +such) to help with creating xast trees. + +## When should I use this? -This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c): -Node 12+ is needed to use it and it must be `import`ed instead of `require`d. +You can use this utility in your project when you generate xast syntax trees +with code. +It helps because it replaces most of the repetition otherwise needed in a syntax +tree with function calls. -[npm][]: +You can instead use [`unist-builder`][u] when creating any unist nodes and +[`hastscript`][h] when creating hast (HTML) nodes. + +## Install + +This package is [ESM only][esm]. +In Node.js (version 12.20+, 14.14+, 16.0+, 18.0+), install with [npm][]: ```sh npm install xastscript ``` +In Deno with [`esm.sh`][esmsh]: + +```js +import {x} from 'https://esm.sh/xastscript@3' +``` + +In browsers with [`esm.sh`][esmsh]: + +```html + +``` + ## Use ```js @@ -145,9 +187,16 @@ Yields: ## API +This package exports the identifier `x`. +There is no default export. + +The export map supports the automatic JSX runtime. +You can pass `xastscript` to your build tool (TypeScript, Babel, SWC) as with +an `importSource` option or similar. + ### `x(name?[, attributes][, …children])` -Create XML *[trees][tree]* in **[xast][]**. +Create [xast][] trees. ##### Signatures @@ -166,7 +215,8 @@ When nullish, a [`Root`][root] is built instead. ###### `attributes` -Map of attributes (`Object.<*>`, optional). +Map of attributes (`Record`, +optional). Nullish (`null` or `undefined`) or `NaN` values are ignored, other values are turned to strings. @@ -176,10 +226,10 @@ Cannot be omitted when building an [`Element`][element] if the first child is a ###### `children` -(Lists of) children (`string`, `number`, `Node`, `Array.`, optional). +(Lists of) children (`string`, `number`, `Node`, `Array`, 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. +If a [`Root`][root] node is encountered, its children are used instead. ##### Returns @@ -187,12 +237,15 @@ If a [`Root`][root] node is given, its children are used instead. ## JSX -`xastscript` can be used as a pragma for JSX. +`xastscript` can be used with JSX. +Either use the automatic runtime set to `xastscript` or import `x` yourself and +define it as the pragma (plus set the fragment to `null`). + The example above (omitting the second) can then be written like so: ```jsx +/** @jsxImportSource x */ import {u} from 'unist-builder' -import {x} from 'xastscript' console.log( @@ -214,17 +267,14 @@ console.log( ) ``` -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). +You can use [`estree-util-build-jsx`][estree-util-build-jsx] to compile JSX +away. -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: +For [Babel][], use [`@babel/plugin-transform-react-jsx`][babel-jsx] and either +pass `pragma: 'x'` and `pragmaFrag: 'null'`, or pass `importSource: +'xastscript'`. +Alternatively, Babel also lets you configure this with a comment: +Babel also lets you configure this from code: ```jsx /** @jsx x @jsxFrag null */ @@ -236,16 +286,20 @@ console.log() For [TypeScript][], this can be done by setting `"jsx": "react"`, `"jsxFactory": "x"`, and `"jsxFragmentFactory": "null"` in the compiler options. For more details on configuring JSX for TypeScript, see the -[TypeScript JSX handbook page][]. +[TypeScript JSX handbook page][typescript-jsx]. +TypeScript also lets you configure this from code as shown with Babel above. -TypeScript also lets you configure this in a script: +## Types -```tsx -/** @jsx x @jsxFrag null */ -import {x} from 'xastscript' +This package is fully typed with [TypeScript][]. +It exports the additional types `Child` and `Attributes`. -console.log() -``` +## Compatibility + +Projects maintained by the unified collective are compatible with all maintained +versions of Node.js. +As of now, that is Node.js 12.20+, 14.14+, 16.0+, and 18.0+. +Our projects sometimes work with older versions, but this is not guaranteed. ## Security @@ -254,20 +308,20 @@ XML can be a dangerous language: don’t trust user-provided data. ## Related * [`unist-builder`][u] - — Create any unist tree + — create any unist tree * [`hastscript`][h] - — Create a **[hast][]** (HTML or SVG) unist tree + — create a hast tree * [`xast-util-to-xml`](https://github.com/syntax-tree/xast-util-to-xml) - — Serialize nodes to XML + — serialize xast as XML * [`xast-util-from-xml`](https://github.com/syntax-tree/xast-util-from-xml) - — Parse from XML + — parse xast from XML * [`hast-util-to-xast`](https://github.com/syntax-tree/hast-util-to-xast) - — Transform hast (html, svg) to xast (xml) + — transform hast to xast ## Contribute -See [`contributing.md` in `syntax-tree/.github`][contributing] for ways to get -started. +See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for +ways to get started. See [`support.md`][support] for ways to get help. This project has a [code of conduct][coc]. @@ -308,24 +362,26 @@ abide by its terms. [npm]: https://docs.npmjs.com/cli/install +[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c + +[esmsh]: https://esm.sh + +[typescript]: https://www.typescriptlang.org + [license]: license [author]: https://wooorm.com -[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md +[health]: https://github.com/syntax-tree/.github -[support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md +[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md -[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md +[support]: https://github.com/syntax-tree/.github/blob/main/support.md -[unist]: https://github.com/syntax-tree/unist - -[hast]: https://github.com/syntax-tree/hast +[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md [xast]: https://github.com/syntax-tree/xast -[tree]: https://github.com/syntax-tree/unist#tree - [node]: https://github.com/syntax-tree/unist#node [root]: https://github.com/syntax-tree/xast#root @@ -338,12 +394,10 @@ abide by its terms. [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 -[typescript]: https://www.typescriptlang.org +[typescript-jsx]: https://www.typescriptlang.org/docs/handbook/jsx.html -[typescript jsx handbook page]: https://www.typescriptlang.org/docs/handbook/jsx.html +[estree-util-build-jsx]: https://github.com/syntax-tree/estree-util-build-jsx diff --git a/script/generate-jsx.js b/script/generate-jsx.js index 04921dd..8484b87 100644 --- a/script/generate-jsx.js +++ b/script/generate-jsx.js @@ -1,46 +1,52 @@ -import fs from 'fs' -import path from 'path' +import fs from 'node:fs' +import path from 'node:path' import babel from '@babel/core' import {Parser} from 'acorn' import acornJsx from 'acorn-jsx' -import {generate} from 'astring' +import {toJs} from 'estree-util-to-js' import {buildJsx} from 'estree-util-build-jsx' -var doc = String(fs.readFileSync(path.join('test', 'jsx.jsx'))) +const doc = String(fs.readFileSync(path.join('test', 'jsx.jsx'))) fs.writeFileSync( path.join('test', 'jsx-build-jsx-classic.js'), - generate( + toJs( + // @ts-expect-error it’s a program. buildJsx( - // @ts-ignore Acorn nodes are assignable to ESTree nodes. + // @ts-expect-error Acorn nodes are assignable to ESTree nodes. Parser.extend(acornJsx()).parse( doc.replace(/'name'/, "'jsx (estree-util-build-jsx, classic)'"), + // Note: different npms resolve this differently, so it may break or work, hence the ignore. // @ts-ignore Hush, `2021` is fine. {sourceType: 'module', ecmaVersion: 2021} ), {pragma: 'x', pragmaFrag: 'null'} ) - ) + // @ts-expect-error Some bug in `to-js` + ).value ) fs.writeFileSync( path.join('test', 'jsx-build-jsx-automatic.js'), - generate( + toJs( + // @ts-expect-error it’s a program. buildJsx( - // @ts-ignore Acorn nodes are assignable to ESTree nodes. + // @ts-expect-error Acorn nodes are assignable to ESTree nodes. Parser.extend(acornJsx()).parse( doc.replace(/'name'/, "'jsx (estree-util-build-jsx, automatic)'"), + // Note: different npms resolve this differently, so it may break or work, hence the ignore. // @ts-ignore Hush, `2021` is fine. {sourceType: 'module', ecmaVersion: 2021} ), {runtime: 'automatic', importSource: '.'} ) - ).replace(/\/jsx-runtime(?=["'])/g, './lib/runtime.js') + // @ts-expect-error Some bug in `to-js` + ).value.replace(/\/jsx-runtime(?=["'])/g, './lib/runtime.js') ) fs.writeFileSync( path.join('test', 'jsx-babel-classic.js'), - // @ts-ignore Result always given. + // @ts-expect-error Result always given. babel.transform(doc.replace(/'name'/, "'jsx (babel, classic)'"), { plugins: [ ['@babel/plugin-transform-react-jsx', {pragma: 'x', pragmaFrag: 'null'}] @@ -50,7 +56,7 @@ fs.writeFileSync( fs.writeFileSync( path.join('test', 'jsx-babel-automatic.js'), - // @ts-ignore Result always given. + // @ts-expect-error Result always given. babel .transformSync(doc.replace(/'name'/, "'jsx (babel, automatic)'"), { plugins: [ diff --git a/test-d/automatic.tsx b/test-d/automatic.tsx index 7203678..dc3f4e8 100644 --- a/test-d/automatic.tsx +++ b/test-d/automatic.tsx @@ -2,7 +2,7 @@ /* @jsxImportSource .. */ import {expectType, expectError} from 'tsd' -import {Root, Element} from 'xast' +import type {Root, Element} from 'xast' import {x} from '../index.js' import {Fragment, jsx, jsxs} from '../jsx-runtime.js' diff --git a/test-d/classic.tsx b/test-d/classic.tsx index c880686..1470076 100644 --- a/test-d/classic.tsx +++ b/test-d/classic.tsx @@ -1,7 +1,7 @@ /* @jsx x */ /* @jsxFrag null */ import {expectType, expectError} from 'tsd' -import {Root, Element} from 'xast' +import type {Root, Element} from 'xast' import {x} from '../index.js' type Result = Element | Root diff --git a/test-d/index.tsx b/test-d/index.tsx index a62cace..1c62379 100644 --- a/test-d/index.tsx +++ b/test-d/index.tsx @@ -1,5 +1,5 @@ import {expectType, expectError} from 'tsd' -import {Root, Element} from 'xast' +import type {Root, Element} from 'xast' import {x} from '../index.js' expectType(x()) diff --git a/test/core.js b/test/core.js index 7e220c4..4c1225c 100644 --- a/test/core.js +++ b/test/core.js @@ -1,7 +1,7 @@ import test from 'tape' import {x} from '../index.js' -test('xastscript', function (t) { +test('xastscript', (t) => { t.equal(typeof x, 'function', 'should expose a function') t.deepEqual( @@ -11,8 +11,8 @@ test('xastscript', function (t) { ) t.throws( - function () { - // @ts-ignore runtime. + () => { + // @ts-expect-error runtime. x(1) }, /Expected element name, got `1`/, @@ -69,7 +69,7 @@ test('xastscript', function (t) { ) t.deepEqual( - // @ts-ignore Deeply nested children are not typed. + // @ts-expect-error Deeply nested children are not typed. x('y', {}, [[[x('a')]], [[[[x('b')]], x('c')]]]), { type: 'element', @@ -119,8 +119,8 @@ test('xastscript', function (t) { ) t.throws( - function () { - // @ts-ignore runtime. + () => { + // @ts-expect-error runtime. x('y', {}, {}) }, /Expected node, nodes, string, got `\[object Object]`/, diff --git a/test/jsx.jsx b/test/jsx.jsx index 7774e5b..3b3397f 100644 --- a/test/jsx.jsx +++ b/test/jsx.jsx @@ -2,12 +2,12 @@ import test from 'tape' import {u} from 'unist-builder' import {x} from '../index.js' -test('name', function (t) { +test('name', (t) => { t.deepEqual(, x('a'), 'should support a self-closing element') t.deepEqual(b, x('a', 'b'), 'should support a value as a child') - var A = 'a' + const A = 'a' t.deepEqual(, x(A), 'should support an uppercase tag name') @@ -39,7 +39,7 @@ test('name', function (t) { 'should support a fragment with an expression' ) - var com = {acme: {a: 'A', b: 'B'}} + const com = {acme: {a: 'A', b: 'B'}} t.deepEqual( , @@ -71,7 +71,7 @@ test('name', function (t) { 'should support expression value attributes' ) - var props = {a: 1, b: 2} + const props = {a: 1, b: 2} t.deepEqual( , @@ -96,7 +96,7 @@ test('name', function (t) { 'should support a fragment in an element (#1)' ) - var dl = [ + const dl = [ ['Firefox', 'A red panda.'], ['Chrome', 'A chemical element.'] ] diff --git a/tsconfig.json b/tsconfig.json index 60106cb..ec45143 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "emitDeclarationOnly": true, "allowSyntheticDefaultImports": true, "skipLibCheck": true, - "strictNullChecks": true + "strictNullChecks": true, + "strict": true } }