diff --git a/README.md b/README.md index 79d0741..f165f5c 100644 --- a/README.md +++ b/README.md @@ -15,31 +15,75 @@ npm i reshape-custom-elements --save ## Usage +### Input HTML + +```html + + Text + + + + + + + Reshape is licensed under the MIT license + + + + + This will get wrapped in a div instead of a span + + +``` + +### Reshape processing + ```js const reshape = require('reshape') const customElements = require('reshape-custom-elements') -const html = ` - Text - ` - -reshape({plugins: [customElements({defaultTag: 'span'})]}) +reshape({ + plugins: [ + customElements({ + replacementTag: 'span', + additionalTags: ['label'], + replacementTagMap: { + 'my-footer': 'footer' + } + }) + ] +}) .process(html) - .then((res) => console.log(res.output())) + .then(res => console.log(res.output())) ``` +### Output HTML + ```html Text + + Label + +
+ This will get wrapped in a div instead of a span +
+ +
``` ## Options -| Name | Description | Default | -| ---- | ----------- | ------- | -| **defaultTag** | Tag used to replace the custom element tag name | `div` | -| **skipTags** | Array of tags to be processed despite being a normal html tag | `[]` +| Name | Description | Default | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| **replacementTag** | Tag used to replace the custom element tag name | `div` | +| **additionalTags** | Array of tags to be processed despite being a normal HTML tag | `[]` | +| **blacklist** | Array of tags that should not be processed | `[]` | +| **replacementTagMap** | Object containing custom tag ↔ replacement tag mappings | `{}` | +| **replacementTagOverrideAttribute** | Attribute name that can be used to locally override the used replacement tag. Overrides `replacementTag` and `replacementTagMap`. | `data-replacement` | ## License diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..debc624 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,119 @@ +export interface ReshapeCustomElementsOptions { + replacementTag: string + additionalTags: HtmlTags[] +} + +export default function reshapeCustomElements( + options: ReshapeCustomElementsOptions +): Function + +type HtmlTags = + | 'a' + | 'abbr' + | 'address' + | 'area' + | 'article' + | 'aside' + | 'audio' + | 'b' + | 'base' + | 'bdi' + | 'bdo' + | 'blockquote' + | 'body' + | 'br' + | 'button' + | 'canvas' + | 'caption' + | 'cite' + | 'code' + | 'col' + | 'colgroup' + | 'datalist' + | 'dd' + | 'del' + | 'details' + | 'dfn' + | 'dialog' + | 'div' + | 'dl' + | 'dt' + | 'em' + | 'embed' + | 'fieldset' + | 'figcaption' + | 'figure' + | 'footer' + | 'form' + | 'h1' + | 'h2' + | 'h3' + | 'h4' + | 'h5' + | 'h6' + | 'head' + | 'header' + | 'hr' + | 'html' + | 'i' + | 'iframe' + | 'img' + | 'input' + | 'ins' + | 'kbd' + | 'keygen' + | 'label' + | 'legend' + | 'li' + | 'link' + | 'main' + | 'map' + | 'mark' + | 'menu' + | 'menuitem' + | 'meta' + | 'meter' + | 'nav' + | 'noscript' + | 'object' + | 'ol' + | 'optgroup' + | 'option' + | 'output' + | 'p' + | 'param' + | 'pre' + | 'progress' + | 'q' + | 'rp' + | 'rt' + | 'ruby' + | 's' + | 'samp' + | 'script' + | 'section' + | 'select' + | 'small' + | 'source' + | 'span' + | 'strong' + | 'style' + | 'sub' + | 'summary' + | 'sup' + | 'table' + | 'tbody' + | 'td' + | 'textarea' + | 'tfoot' + | 'th' + | 'thead' + | 'time' + | 'title' + | 'tr' + | 'track' + | 'u' + | 'ul' + | 'var' + | 'video' + | 'wbr' diff --git a/lib/index.js b/lib/index.js index 1e63233..5ecc324 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,18 +1,22 @@ const { modifyNodes } = require('reshape-plugin-util') module.exports = function reshapeCustomElements(options = {}) { - const defaultTag = options.defaultTag || 'div' - const skipTags = options.skipTags || [] + const defaultReplacementTag = + options.replacementTag || options.defaultTag || 'div' + const additionalTags = options.additionalTags || options.skipTags || [] + const replacementTagAttr = + options.replacementTagOverrideAttribute || 'data-replacement' + const replacementTagMap = options.replacementTagMap || {} + const blacklist = options.blacklist || [] return function(tree) { return modifyNodes( tree, - node => { - return ( - node.type === 'tag' && - (htmlTags.indexOf(node.name) < 0 || skipTags.indexOf(node.name) > -1) - ) - }, + node => + node.type === 'tag' && + blacklist.indexOf(node.name) < 0 && + (htmlTags.indexOf(node.name) < 0 || + additionalTags.indexOf(node.name) > -1), node => { // look for a class attribute if (!node.attrs) { @@ -24,7 +28,7 @@ module.exports = function reshapeCustomElements(options = {}) { // if there's already the same class, return if (node.attrs.class.find(n => n.content === node.name)) { - node.name = defaultTag + node.name = defaultReplacementTag return node } @@ -44,8 +48,22 @@ module.exports = function reshapeCustomElements(options = {}) { location: node.location }) - // set the name to the default and return - node.name = defaultTag + // find out the replacement tag + let replacementTag = defaultReplacementTag + if ( + node.attrs[replacementTagAttr] && + node.attrs[replacementTagAttr].length > 0 + ) { + // if there is a replacement override attribute, use it + replacementTag = node.attrs[replacementTagAttr][0].content + delete node.attrs[replacementTagAttr] + } else if (replacementTagMap[node.name]) { + // if there's a replacement tag mapping, use it + replacementTag = replacementTagMap[node.name] + } + + // set the new tag name and return + node.name = replacementTag return node } ) diff --git a/package-lock.json b/package-lock.json index e62b016..e69b448 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1126,23 +1126,25 @@ } }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } } } @@ -1171,12 +1173,6 @@ "left-pad": "^1.1.3" } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -2763,9 +2759,9 @@ } }, "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==", "dev": true }, "hosted-git-info": { @@ -3234,23 +3230,23 @@ } }, "istanbul-reports": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.4.tgz", - "integrity": "sha512-QCHGyZEK0bfi9GR215QSm+NJwFKEShbtc7tfbUdLAEzn3kKhLDDZqvljn8rPZM9v8CEOhzL1nlYoO4r1ryl67w==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", "dev": true, "requires": { "handlebars": "^4.1.2" } }, "joi": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-12.0.0.tgz", - "integrity": "sha512-z0FNlV4NGgjQN1fdtHYXf5kmgludM65fG/JlXzU6+rwkt9U5UWuXVYnXa2FpK0u6+qBuCmrm5byPNuiiddAHvQ==", + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", "dev": true, "requires": { - "hoek": "4.x.x", + "hoek": "6.x.x", "isemail": "3.x.x", - "topo": "2.x.x" + "topo": "3.x.x" } }, "js-string-escape": { @@ -3772,9 +3768,9 @@ } }, "neo-async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, "nested-error-stacks": { @@ -3816,16 +3812,10 @@ "path-key": "^2.0.0" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "nyc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.0.tgz", - "integrity": "sha512-iy9fEV8Emevz3z/AanIZsoGa8F4U2p0JKevZ/F0sk+/B2r9E6Qn+EPs0bpxEhnAt6UPlTL8mQZIaSJy8sK0ZFw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", "dev": true, "requires": { "archy": "^1.0.0", @@ -4323,9 +4313,9 @@ "dev": true }, "prettier": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.0.tgz", - "integrity": "sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.17.1.tgz", + "integrity": "sha512-TzGRNvuUSmPgwivDqkZ9tM/qTGW9hqDKWOE9YHiyQdixlKbv7kvEqsmDPrcHJTKwthU774TQwZXVtaQ/mMsvjg==", "dev": true }, "pretty-ms": { @@ -4732,13 +4722,13 @@ "dev": true }, "reshape": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/reshape/-/reshape-1.0.0.tgz", - "integrity": "sha1-tznnhLmon/3h6sL5KIJGOdmZ4p8=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/reshape/-/reshape-1.0.1.tgz", + "integrity": "sha512-QizKr2EpJlACyfGb9Ld3FlQVx8ZYPWygn4NyusZb4D0smhdQ3qeV6kK1xrAHBMTMDbRAM8PeBg8xRWfrwpK4vw==", "dev": true, "requires": { "code-frame": "^5.0.0", - "joi": "^12.0.0", + "joi": "^14.3.1", "lodash.merge": "^4.6.1", "reshape-code-gen": "^2.0.0", "reshape-parser": "^1.0.0", @@ -5423,12 +5413,12 @@ } }, "topo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", - "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", "dev": true, "requires": { - "hoek": "4.x.x" + "hoek": "6.x.x" } }, "tough-cookie": { @@ -5489,9 +5479,9 @@ "dev": true }, "uglify-js": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.11.tgz", - "integrity": "sha512-izPJg8RsSyqxbdnqX36ExpbH3K7tDBsAU/VfNv89VkMFy3z39zFjunQGsSHOlGlyIfGLGprGeosgQno3bo2/Kg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.12.tgz", + "integrity": "sha512-KeQesOpPiZNgVwJj8Ge3P4JYbQHUdZzpx6Fahy6eKAYRSV4zhVmLXoC+JtOeYxcHCHTve8RG1ZGdTvpeOUM26Q==", "dev": true, "optional": true, "requires": { @@ -5810,48 +5800,25 @@ "dev": true }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } } } @@ -5898,12 +5865,12 @@ "dev": true }, "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", "dev": true, "requires": { - "cliui": "^4.0.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", "os-locale": "^3.1.0", @@ -5913,7 +5880,7 @@ "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "yargs-parser": "^13.1.0" }, "dependencies": { "camelcase": { diff --git a/package.json b/package.json index fb5c2db..45be683 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "description": "transform custom element names into class names", "version": "0.2.0", "author": "Jeff Escalante", + "types": "index.d.ts", "ava": { "verbose": "true" }, @@ -13,10 +14,10 @@ "devDependencies": { "ava": "^1.4.1", "coveralls": "^3.0.3", - "nyc": "^14.1.0", - "prettier": "^1.17.0", + "nyc": "^14.1.1", + "prettier": "^1.17.1", "pretty-quick": "^1.10.0", - "reshape": "^1.0.0" + "reshape": "^1.0.1" }, "homepage": "https://github.com/reshape/custom-elements", "keywords": [ diff --git a/test/test.js b/test/test.js index d5eb849..fc0a48e 100644 --- a/test/test.js +++ b/test/test.js @@ -2,50 +2,91 @@ const reshape = require('reshape') const test = require('ava') const customElements = require('..') -test('basic', (t) => { +test('basic', t => { const html = 'Test' const expected = '
Test
' return compare(t, html, expected) }) -test('add to existing class', (t) => { +test('add to existing class', t => { const html = 'Test' const expected = '
Test
' return compare(t, html, expected) }) -test('class already exists', (t) => { +test('class already exists', t => { const html = 'Test' const expected = '
Test
' return compare(t, html, expected) }) -test('html tag match', (t) => { +test('html tag match', t => { const html = '
Test
' - const expected = '
Test
' + const expected = html return compare(t, html, expected) }) -test('undefined options', (t) => { +test('undefined options', t => { const html = '
Test
' - const expected = '
Test
' + const expected = html return compare(t, html, expected, undefined) }) -test('defaultTag', (t) => { +test('replacementTag', t => { const html = 'Test' const expected = 'Test' - return compare(t, html, expected, { defaultTag: 'span' }) + return compare(t, html, expected, { replacementTag: 'span' }) }) -test('skip tags option', (t) => { +test('additionalTags option', t => { const html = '
Test
' const expected = '
Test
' - return compare(t, html, expected, { skipTags: ['header'] }) + return compare(t, html, expected, { additionalTags: ['header'] }) }) -function compare (t, html, expected, options) { +test('backwards compatibility', t => { + const html = '
Test
' + const expected = 'Test' + return compare(t, html, expected, { + defaultTag: 'span', + skipTags: ['header'] + }) +}) + +test('custom replacement tag', t => { + const html = 'Test' + const expected = 'Test' + return compare(t, html, expected) +}) + +test('custom replacement tag attribute', t => { + const html = 'Test' + const expected = 'Test' + return compare(t, html, expected, { replacementTagOverrideAttribute: 'tag' }) +}) + +test('custom replacement tag map', t => { + const html = 'Test' + const expected = 'Test' + return compare(t, html, expected, { replacementTagMap: { custom: 'span' } }) +}) + +test('custom replacement tag map overriding', t => { + const html = 'Test' + const expected = 'Test' + return compare(t, html, expected, { replacementTagMap: { custom: 'div' } }) +}) + +test("don't replace blacklisted tags", t => { + const html = 'Keep my tag!' + const expected = html + return compare(t, html, expected, { blacklist: ['blacklisted'] }) +}) + +function compare(t, html, expected, options) { return reshape({ plugins: [customElements(options)] }) .process(html) - .then((res) => { t.is(res.output(), expected) }) + .then(res => { + t.is(res.output(), expected) + }) }