diff --git a/.gitignore b/.gitignore index f394799..c77b50d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ .DS_Store *.log -.nyc_output/ coverage/ node_modules/ test/jsx-*.js -hastscript.js -hastscript.min.js yarn.lock diff --git a/.prettierignore b/.prettierignore index c50edf1..cebe81f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,2 @@ coverage/ -hastscript.js -hastscript.min.js -*.json *.md diff --git a/factory.js b/factory.js deleted file mode 100644 index cc39c38..0000000 --- a/factory.js +++ /dev/null @@ -1,193 +0,0 @@ -'use strict' - -var find = require('property-information/find') -var normalize = require('property-information/normalize') -var parseSelector = require('hast-util-parse-selector') -var spaceSeparated = require('space-separated-tokens') -var commaSeparated = require('comma-separated-tokens') - -module.exports = factory - -var own = {}.hasOwnProperty - -function factory(schema, defaultTagName, caseSensitive) { - var adjust = caseSensitive && createAdjustMap(caseSensitive) - - return h - - // Hyperscript compatible DSL for creating virtual hast trees. - function h(selector, properties) { - 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. - 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) - ) { - // Nope, it’s something for `children`. - index-- - } else { - for (property in properties) { - addProperty(schema, node.properties, property, properties[property]) - } - } - } - - // Handle children. - while (++index < arguments.length) { - addChild(node.children, arguments[index]) - } - - if (name === 'template') { - node.content = {type: 'root', children: node.children} - node.children = [] - } - - return node - } -} - -function isNode(name, value) { - var type = value.type - - if (name === 'input' || !type || typeof type !== 'string') { - return false - } - - if (typeof value.children === 'object' && 'length' in value.children) { - return true - } - - type = type.toLowerCase() - - if (name === 'button') { - return ( - type !== 'menu' && - type !== 'submit' && - type !== 'reset' && - type !== 'button' - ) - } - - return 'value' in value -} - -function addProperty(schema, properties, key, value) { - var info = find(schema, key) - var result = value - var index = -1 - var finalResult - - // Ignore nullish and NaN values. - if (result == null || result !== result) { - return - } - - // Handle list values. - if (typeof result === 'string') { - if (info.spaceSeparated) { - result = spaceSeparated.parse(result) - } else if (info.commaSeparated) { - result = commaSeparated.parse(result) - } else if (info.commaOrSpaceSeparated) { - result = spaceSeparated.parse(commaSeparated.parse(result).join(' ')) - } - } - - // Accept `object` on style. - if (info.property === 'style' && typeof result !== 'string') { - result = style(result) - } - - // Class names (which can be added both on the `selector` and here). - if (info.property === 'className' && properties.className) { - result = properties.className.concat(result) - } - - if (typeof result === 'object' && 'length' in result) { - finalResult = [] - while (++index < result.length) { - finalResult[index] = parsePrimitive(info, info.property, result[index]) - } - } else { - finalResult = parsePrimitive(info, info.property, result) - } - - properties[info.property] = finalResult -} - -function addChild(nodes, value) { - var index = -1 - - if (typeof value === 'string' || typeof value === 'number') { - nodes.push({type: 'text', value: String(value)}) - } else if (typeof value === 'object' && 'length' in value) { - while (++index < value.length) { - addChild(nodes, value[index]) - } - } else if (typeof value === 'object' && 'type' in value) { - if (value.type === 'root') { - addChild(nodes, value.children) - } else { - nodes.push(value) - } - } else { - throw new Error('Expected node, nodes, or string, got `' + value + '`') - } -} - -// Parse a single primitives. -function parsePrimitive(info, name, value) { - var result = value - - if ((info.number || info.positiveNumber) && !isNaN(result) && result !== '') { - result = Number(result) - } - - // Accept `boolean` and `string`. - if ( - (info.boolean || info.overloadedBoolean) && - typeof result === 'string' && - (result === '' || normalize(value) === normalize(name)) - ) { - result = true - } - - return result -} - -function style(value) { - var result = [] - var key - - for (key in value) { - result.push([key, value[key]].join(': ')) - } - - return result.join('; ') -} - -function createAdjustMap(values) { - var result = {} - var index = -1 - - while (++index < values.length) { - result[values[index].toLowerCase()] = values[index] - } - - return result -} diff --git a/html.js b/html.js deleted file mode 100644 index a57c6d0..0000000 --- a/html.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict' - -var schema = require('property-information/html') -var factory = require('./factory') - -var html = factory(schema, 'div') -html.displayName = 'html' - -module.exports = html diff --git a/index.js b/index.js index 50f2197..79c9d0c 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,211 @@ -'use strict' +import {html, svg, find, normalize} from 'property-information' +import {parseSelector} from 'hast-util-parse-selector' +import {parse as spaces} from 'space-separated-tokens' +import {parse as commas} from 'comma-separated-tokens' +import {svgCaseSensitiveTagNames} from './svg-case-sensitive-tag-names.js' -module.exports = require('./html') +var own = {}.hasOwnProperty + +export const h = factory(html, 'div') +h.displayName = 'html' + +export const s = factory(svg, 'g', svgCaseSensitiveTagNames) +s.displayName = 'svg' + +function factory(schema, defaultTagName, caseSensitive) { + var adjust = caseSensitive && createAdjustMap(caseSensitive) + + return h + + // Hyperscript compatible DSL for creating virtual hast trees. + function h(selector, properties) { + var node = + selector === undefined || selector === null + ? {type: 'root', children: []} + : parseSelector(selector, defaultTagName) + var name = + selector === undefined || selector === null + ? null + : node.tagName.toLowerCase() + var index = 1 + var property + + // Normalize the name. + if (name !== undefined && name !== null) { + node.tagName = adjust && own.call(adjust, name) ? adjust[name] : name + } + + // Handle props. + if (properties) { + if ( + name === undefined || + name === null || + typeof properties === 'string' || + 'length' in properties || + isNode(name, properties) + ) { + // Nope, it’s something for `children`. + index-- + } else { + for (property in properties) { + if (own.call(properties, property)) { + addProperty(schema, node.properties, property, properties[property]) + } + } + } + } + + // Handle children. + while (++index < arguments.length) { + addChild(node.children, arguments[index]) + } + + if (name === 'template') { + node.content = {type: 'root', children: node.children} + node.children = [] + } + + return node + } +} + +function isNode(name, value) { + var type = value.type + + if (name === 'input' || !type || typeof type !== 'string') { + return false + } + + if (typeof value.children === 'object' && 'length' in value.children) { + return true + } + + type = type.toLowerCase() + + if (name === 'button') { + return ( + type !== 'menu' && + type !== 'submit' && + type !== 'reset' && + type !== 'button' + ) + } + + return 'value' in value +} + +function addProperty(schema, properties, key, value) { + var info = find(schema, key) + var result = value + var index = -1 + var finalResult + + // Ignore nullish and NaN values. + if ( + result === undefined || + result === null || + (typeof result === 'number' && Number.isNaN(result)) + ) { + return + } + + // Handle list values. + if (typeof result === 'string') { + if (info.spaceSeparated) { + result = spaces(result) + } else if (info.commaSeparated) { + result = commas(result) + } else if (info.commaOrSpaceSeparated) { + result = spaces(commas(result).join(' ')) + } + } + + // Accept `object` on style. + if (info.property === 'style' && typeof result !== 'string') { + result = style(result) + } + + // Class names (which can be added both on the `selector` and here). + if (info.property === 'className' && properties.className) { + result = properties.className.concat(result) + } + + if (typeof result === 'object' && 'length' in result) { + finalResult = [] + while (++index < result.length) { + finalResult[index] = parsePrimitive(info, info.property, result[index]) + } + } else { + finalResult = parsePrimitive(info, info.property, result) + } + + properties[info.property] = finalResult +} + +function addChild(nodes, value) { + var index = -1 + + if (typeof value === 'string' || typeof value === 'number') { + nodes.push({type: 'text', value: String(value)}) + } else if (typeof value === 'object' && 'length' in value) { + while (++index < value.length) { + addChild(nodes, value[index]) + } + } else if (typeof value === 'object' && 'type' in value) { + if (value.type === 'root') { + addChild(nodes, value.children) + } else { + nodes.push(value) + } + } else { + throw new Error('Expected node, nodes, or string, got `' + value + '`') + } +} + +// Parse a single primitives. +function parsePrimitive(info, name, value) { + var result = value + + if ( + (info.number || info.positiveNumber) && + !Number.isNaN(Number(result)) && + result !== '' + ) { + result = Number(result) + } + + // Accept `boolean` and `string`. + if ( + (info.boolean || info.overloadedBoolean) && + typeof result === 'string' && + (result === '' || normalize(value) === normalize(name)) + ) { + result = true + } + + return result +} + +function style(value) { + var result = [] + var key + + for (key in value) { + if (own.call(value, key)) { + result.push([key, value[key]].join(': ')) + } + } + + return result.join('; ') +} + +function createAdjustMap(values) { + var result = {} + var index = -1 + + while (++index < values.length) { + result[values[index].toLowerCase()] = values[index] + } + + return result +} diff --git a/package.json b/package.json index 3ece52f..e33c4d3 100644 --- a/package.json +++ b/package.json @@ -27,56 +27,46 @@ "contributors": [ "Titus Wormer (https://wooorm.com)" ], + "sideEffects": false, + "type": "module", + "main": "index.js", "types": "index.d.ts", "files": [ "index.d.ts", - "svg.d.ts", "index.js", - "factory.js", - "html.js", - "svg.js", - "svg-case-sensitive-tag-names.json" + "svg-case-sensitive-tag-names.d.ts", + "svg-case-sensitive-tag-names.js" ], "dependencies": { "@types/hast": "^2.0.0", - "comma-separated-tokens": "^1.0.0", - "hast-util-parse-selector": "^2.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0" + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.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", + "acorn": "^6.0.0", + "acorn-jsx": "^5.0.0", + "astring": "^1.0.0", + "c8": "^7.0.0", + "estree-util-build-jsx": "^2.0.0", "prettier": "^2.0.0", "remark-cli": "^9.0.0", "remark-preset-wooorm": "^8.0.0", - "svg-tag-names": "^2.0.0", + "svg-tag-names": "^3.0.0", "tape": "^5.0.0", - "tinyify": "^3.0.0", - "unist-builder": "^2.0.0", - "xo": "^0.38.0" + "unist-builder": "^3.0.0", + "xo": "^0.39.0" }, "scripts": { "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/index.js", - "test-types": "dtslint .", - "test": "npm run generate && npm run format && npm run build && npm run test-coverage && npm run test-types" - }, - "nyc": { - "check-coverage": true, - "lines": 100, - "functions": 100, - "branches": 100 + "test-api": "node test/index.js", + "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test/index.js", + "test": "npm run generate && npm run format && npm run test-coverage" }, "prettier": { "tabWidth": 2, @@ -88,22 +78,12 @@ }, "xo": { "prettier": true, - "esnext": false, "rules": { - "eqeqeq": [ - "error", - "always", - { - "null": "ignore" - } - ], - "guard-for-in": "off", - "no-eq-null": "off", - "no-self-compare": "off", - "unicorn/prefer-number-properties": "off" + "no-var": "off", + "prefer-arrow-callback": "off" }, - "ignores": [ - "hastscript.js" + "ignore": [ + "**/*.d.ts" ] }, "remarkConfig": { diff --git a/readme.md b/readme.md index 044036d..99062bf 100644 --- a/readme.md +++ b/readme.md @@ -18,6 +18,9 @@ Use [`unist-builder`][u] to create any [**unist**][unist] tree. ## Install +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. + [npm][]: ```sh @@ -27,8 +30,7 @@ npm install hastscript ## Use ```js -var h = require('hastscript') -var s = require('hastscript/svg') +import {h, s} from 'hastscript' // Children as an array: console.log( @@ -138,6 +140,9 @@ Yields: ## API +This package exports the following identifiers: `h` and `s`. +There is no default export. + ### `h(selector?[, properties][, …children])` ### `s(selector?[, properties][, …children])` @@ -193,7 +198,7 @@ that SVG can be used too: ```jsx /** @jsx h */ /** @jsxFrag null */ -var h = require('hastscript') +import {h} from 'hastscript' console.log(
@@ -219,7 +224,7 @@ console.log( ```jsx /** @jsx s */ /** @jsxFrag null */ -var s = require('hastscript/svg') +import {s} from 'hastscript' console.log( @@ -249,7 +254,7 @@ Babel also lets you configure this in a script: ```jsx /** @jsx s */ /** @jsxFrag null */ -var s = require('hastscript/svg') +import {s} from 'hastscript' console.log() ``` diff --git a/script/build.js b/script/build.js index 928a2ef..a7c2867 100644 --- a/script/build.js +++ b/script/build.js @@ -1,12 +1,11 @@ -'use strict' +import fs from 'fs' +import {svgTagNames} from 'svg-tag-names' -var fs = require('fs') -var tagNames = require('svg-tag-names') +var casing = svgTagNames.filter((d) => d !== d.toLowerCase()) -var casing = tagNames.filter(function (d) { - return d !== d.toLowerCase() -}) - -var doc = JSON.stringify(casing, null, 2) + '\n' - -fs.writeFileSync('./svg-case-sensitive-tag-names.json', doc) +fs.writeFileSync( + 'svg-case-sensitive-tag-names.js', + 'export const svgCaseSensitiveTagNames = ' + + JSON.stringify(casing, null, 2) + + '\n' +) diff --git a/script/generate-jsx.js b/script/generate-jsx.js index a21d6f4..7a379ee 100644 --- a/script/generate-jsx.js +++ b/script/generate-jsx.js @@ -1,18 +1,24 @@ -'use strict' - -var fs = require('fs') -var path = require('path') -var buble = require('buble') -var babel = require('@babel/core') +import fs from 'fs' +import path from 'path' +import babel from '@babel/core' +import {Parser} from 'acorn' +import acornJsx from 'acorn-jsx' +import {generate} from 'astring' +import {buildJsx} from 'estree-util-build-jsx' 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 + path.join('test', 'jsx-build-jsx.js'), + generate( + buildJsx( + Parser.extend(acornJsx()).parse( + doc.replace(/'name'/, "'jsx (build-jsx)'"), + {sourceType: 'module'} + ), + {pragma: 'h', pragmaFrag: 'null'} + ) + ) ) fs.writeFileSync( diff --git a/svg-case-sensitive-tag-names.js b/svg-case-sensitive-tag-names.js new file mode 100644 index 0000000..575ceef --- /dev/null +++ b/svg-case-sensitive-tag-names.js @@ -0,0 +1,41 @@ +export const svgCaseSensitiveTagNames = [ + 'altGlyph', + 'altGlyphDef', + 'altGlyphItem', + 'animateColor', + 'animateMotion', + 'animateTransform', + 'clipPath', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', + 'foreignObject', + 'glyphRef', + 'linearGradient', + 'radialGradient', + 'solidColor', + 'textArea', + 'textPath' +] diff --git a/svg-case-sensitive-tag-names.json b/svg-case-sensitive-tag-names.json deleted file mode 100644 index 19f0e61..0000000 --- a/svg-case-sensitive-tag-names.json +++ /dev/null @@ -1,41 +0,0 @@ -[ - "altGlyph", - "altGlyphDef", - "altGlyphItem", - "animateColor", - "animateMotion", - "animateTransform", - "clipPath", - "feBlend", - "feColorMatrix", - "feComponentTransfer", - "feComposite", - "feConvolveMatrix", - "feDiffuseLighting", - "feDisplacementMap", - "feDistantLight", - "feDropShadow", - "feFlood", - "feFuncA", - "feFuncB", - "feFuncG", - "feFuncR", - "feGaussianBlur", - "feImage", - "feMerge", - "feMergeNode", - "feMorphology", - "feOffset", - "fePointLight", - "feSpecularLighting", - "feSpotLight", - "feTile", - "feTurbulence", - "foreignObject", - "glyphRef", - "linearGradient", - "radialGradient", - "solidColor", - "textArea", - "textPath" -] diff --git a/svg.d.ts b/svg.d.ts deleted file mode 100644 index b39440f..0000000 --- a/svg.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -// TypeScript Version: 3.5 - -import hastscript = require('hastscript') - -export = hastscript diff --git a/svg.js b/svg.js deleted file mode 100644 index b933def..0000000 --- a/svg.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -var schema = require('property-information/svg') -var caseSensitive = require('./svg-case-sensitive-tag-names.json') -var factory = require('./factory') - -var svg = factory(schema, 'g', caseSensitive) -svg.displayName = 'svg' - -module.exports = svg diff --git a/test/core.js b/test/core.js index 045c21f..2c395fd 100644 --- a/test/core.js +++ b/test/core.js @@ -1,8 +1,5 @@ -'use strict' - -var test = require('tape') -var s = require('../svg') -var h = require('../html') +import test from 'tape' +import {h, s} from '../index.js' test('hastscript', function (t) { t.equal(typeof h, 'function', 'should expose a function') @@ -476,7 +473,7 @@ test('hastscript', function (t) { ) t.deepEqual( - h('', {foo: NaN}), + h('', {foo: Number.NaN}), { type: 'element', tagName: 'div', diff --git a/test/index.js b/test/index.js index d39cad7..6fcc935 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,5 @@ -'use strict' - /* eslint-disable import/no-unassigned-import */ -require('./core') -require('./jsx-babel') -require('./jsx-buble') +import './core.js' +import './jsx-babel.js' +import './jsx-build-jsx.js' /* eslint-enable import/no-unassigned-import */ diff --git a/test/jsx.jsx b/test/jsx.jsx index 2dc7856..f49af52 100644 --- a/test/jsx.jsx +++ b/test/jsx.jsx @@ -1,8 +1,6 @@ -'use strict' - -var test = require('tape') -var u = require('unist-builder') -var h = require('..') +import test from 'tape' +import {u} from 'unist-builder' +import {h} from '../index.js' test('name', function (t) { t.deepEqual(, h('a'), 'should support a self-closing element')