diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index dd44972..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -*.md diff --git a/.eslintrc b/.eslintrc index 3a94731..06a5fd3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -32,7 +32,7 @@ "no-implied-eval": 2, "no-new-func": 2, "guard-for-in": 2, - "eqeqeq": 1, + "eqeqeq": 0, "no-else-return": 2, "no-redeclare": 2, "no-dupe-keys": 2, @@ -44,7 +44,6 @@ "no-shadow-restricted-names": 2, "handle-callback-err": 0, "no-lonely-if": 2, - "keyword-spacing": 2, "constructor-super": 2, "no-this-before-super": 2, "no-dupe-class-members": 2, diff --git a/.gitignore b/.gitignore index 72e9b45..9df4e9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /dist /node_modules /npm-debug.log +package-lock.json .DS_Store diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 4c2095a..0000000 --- a/.npmignore +++ /dev/null @@ -1 +0,0 @@ -.eslintrc diff --git a/.travis.yml b/.travis.yml index 8524235..965fe2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: node_js -node_js: - - 4 +node_js: node +cache: npm diff --git a/README.md b/README.md index dd09fd0..1c41422 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,10 @@ ### **Render JSX/Hyperscript to HTML strings, without VDOM** > Need to use HTML strings (angular?) but want to use JSX? vhtml's got your back. +> +> Building components? do yourself a favor and use [Preact](https://github.com/developit/preact) +[**JSFiddle Demo**](https://jsfiddle.net/developit/9q0vyskg/) --- @@ -45,3 +48,56 @@ document.body.innerHTML = ( ); ``` + + +### New: "Sortof" Components! + +`vhtml` intentionally does not transform JSX to a Virtual DOM, instead serializing it directly to HTML. +However, it's still possible to make use of basic Pure Functional Components as a sort of "template partial". + +When `vhtml` is given a Function as the JSX tag name, it will invoke that function and pass it `{ children, ...props }`. +This is the same signature as a Pure Functional Component in react/preact, except `children` is an Array of already-serialized HTML strings. + +This actually means it's possible to build compositional template modifiers with these simple Components, or even higher-order components. + +Here's a more complex version of the previous example that uses a component to encapsulate iteration items: + +```js +let items = ['one', 'two']; + +const Item = ({ item, index, children }) => ( +
  • +

    {item}

    + {children} +
  • +); + +console.log( +
    +

    Hi!

    + +
    +); +``` + +The above outputs the following HTML: + +```html +
    +

    Hi!

    + +
    +``` diff --git a/package.json b/package.json index f716f83..6e3d2ff 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vhtml", "amdName": "vhtml", - "version": "1.0.0", + "version": "2.2.0", "description": "Hyperscript reviver that constructs a sanitized HTML string.", "main": "dist/vhtml.js", "minified:main": "dist/vhtml.min.js", @@ -17,9 +17,8 @@ }, "babel": { "presets": [ - "es2015-minimal", - "stage-0", - "react" + "es2015", + "stage-0" ], "plugins": [ "transform-object-rest-spread", @@ -31,6 +30,10 @@ ] ] }, + "files": [ + "src", + "dist" + ], "keywords": [ "hyperscript", "html", @@ -49,23 +52,22 @@ "homepage": "https://github.com/developit/vhtml", "devDependencies": { "babel-core": "^6.6.4", - "babel-eslint": "^5.0.0", + "babel-eslint": "^7.0.0", "babel-plugin-transform-object-rest-spread": "^6.6.4", "babel-plugin-transform-react-jsx": "^6.6.5", - "babel-preset-es2015-minimal": "^1.1.0", - "babel-preset-es2015-minimal-rollup": "^1.1.0", - "babel-preset-react": "^6.5.0", + "babel-preset-es2015": "^6.9.0", "babel-preset-stage-0": "^6.5.0", "babel-register": "^6.7.2", "chai": "^3.5.0", - "eslint": "~2.2.0", + "eslint": "^3.1.0", "gzip-size-cli": "^1.0.0", "mkdirp": "^0.5.1", - "mocha": "^2.4.5", - "npm-run-all": "^1.5.1", + "mocha": "^3.1.2", + "npm-run-all": "^2.3.0", "pretty-bytes-cli": "^1.0.0", - "rollup": "^0.25.4", + "rollup": "^0.36.3", "rollup-plugin-babel": "^2.4.0", + "rollup-plugin-es3": "^1.0.3", "uglify-js": "^2.6.2" } } diff --git a/rollup.config.js b/rollup.config.js index 0de39c9..3a7799d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,28 +1,26 @@ import path from 'path'; import fs from 'fs'; import babel from 'rollup-plugin-babel'; +import es3 from 'rollup-plugin-es3'; let pkg = JSON.parse(fs.readFileSync('./package.json')); -let external = Object.keys(pkg.peerDependencies || {}).concat(Object.keys(pkg.dependencies || {})); export default { entry: pkg['jsnext:main'], dest: pkg.main, sourceMap: path.resolve(pkg.main), moduleName: pkg.amdName, + exports: 'default', format: 'umd', - external, plugins: [ babel({ babelrc: false, comments: false, - exclude: 'node_modules/**', presets: [ - 'es2015-minimal-rollup' + ['es2015', { loose:true, modules:false }] ].concat(pkg.babel.presets.slice(1)), - plugins: require('babel-preset-es2015-minimal-rollup').plugins.concat([ - ['transform-react-jsx', { pragma:'h' }] - ]) - }) + plugins: pkg.babel.plugins + }), + es3() ] }; diff --git a/src/empty-tags.js b/src/empty-tags.js new file mode 100644 index 0000000..6f91e68 --- /dev/null +++ b/src/empty-tags.js @@ -0,0 +1,18 @@ +export default [ + 'area', + 'base', + 'br', + 'col', + 'command', + 'embed', + 'hr', + 'img', + 'input', + 'keygen', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr' +]; \ No newline at end of file diff --git a/src/vhtml.js b/src/vhtml.js index a63a134..0e03f69 100644 --- a/src/vhtml.js +++ b/src/vhtml.js @@ -1,36 +1,60 @@ +import emptyTags from './empty-tags'; + // escape an attribute let esc = str => String(str).replace(/[&<>"']/g, s=>`&${map[s]};`); let map = {'&':'amp','<':'lt','>':'gt','"':'quot',"'":'apos'}; - -// sanitize text children and filter out falsey values -let child = s => truthy(s) ? (sanitized[s]===true ? s : esc(s)) : ''; - -// check that a value is not false, undefined or null -let truthy = v => v!==false && v!=null; +let setInnerHTMLAttr = 'dangerouslySetInnerHTML'; +let DOMAttributeNames = { + className: 'class', + htmlFor: 'for' +}; let sanitized = {}; /** Hyperscript reviver that constructs a sanitized HTML string. */ -export default function h(name, attrs, ...children) { - let s = `<${name}`; - if (attrs) for (let i in attrs) { - if (attrs.hasOwnProperty(i) && truthy(attrs[i])) { - s += ` ${esc(i)}="${esc(attrs[i])}"`; +export default function h(name, attrs) { + let stack=[], s = ''; + attrs = attrs || {}; + for (let i=arguments.length; i-- > 2; ) { + stack.push(arguments[i]); + } + + // Sortof component support! + if (typeof name==='function') { + attrs.children = stack.reverse(); + return name(attrs); + // return name(attrs, stack.reverse()); + } + + if (name) { + s += '<' + name; + if (attrs) for (let i in attrs) { + if (attrs[i]!==false && attrs[i]!=null && i !== setInnerHTMLAttr) { + s += ` ${DOMAttributeNames[i] ? DOMAttributeNames[i] : esc(i)}="${esc(attrs[i])}"`; + } } + s += '>'; } - s += `>${[].concat(...children).map(child).join('')}`; - sanitized[s] = true; - return s; -} + if (emptyTags.indexOf(name) === -1) { + if (attrs[setInnerHTMLAttr]) { + s += attrs[setInnerHTMLAttr].__html; + } + else while (stack.length) { + let child = stack.pop(); + if (child) { + if (child.pop) { + for (let i=child.length; i--; ) stack.push(child[i]); + } + else { + s += sanitized[child]===true ? child : esc(child); + } + } + } + s += name ? `` : ''; + } -// for fun: -/* -export default const h = (tag, attrs, ...kids) => ( - `<${tag}${h.attrs(attrs)}>${[].concat(...kids).join('')}` -); -h.attrs = a => Object.keys(a || {}).reduce( (s,i) => `${s} ${h.esc(i)}="${h.esc(a[i]+'')}"`, ''); -h.esc = str => str.replace(/[&<>"']/g, s=>`&${h.map[s]};`); -h.map = {'&':'amp','<':'lt','>':'gt','"':'quot',"'":'apos'}; -*/ + sanitized[s] = true; + return s; +} diff --git a/test/vhtml.js b/test/vhtml.js index 51550df..f88ddf2 100644 --- a/test/vhtml.js +++ b/test/vhtml.js @@ -39,4 +39,155 @@ describe('vhtml', () => { `
    ` ); }); + + it('should not sanitize the "dangerouslySetInnerHTML" attribute, and directly set its `__html` property as innerHTML', () => { + expect( +
    Injected HTML" }} /> + ).to.equal( + `
    Injected HTML
    ` + ); + }); + + it('should flatten children', () => { + expect( +
    + {[['a','b']]} + d + {['e',['f'],[['g']]]} +
    + ).to.equal( + `
    abdefg
    ` + ); + }); + + it('should support sortof components', () => { + let items = ['one', 'two']; + + const Item = ({ item, index, children }) => ( +
  • +

    {item}

    + {children} +
  • + ); + + expect( +
    +

    Hi!

    + +
    + ).to.equal( + `

    Hi!

    ` + ); + }); + + it('should support sortof components without args', () => { + let items = ['one', 'two']; + + const Item = () => ( +
  • +

    +
  • + ); + + expect( +
    +

    Hi!

    + +
    + ).to.equal( + `

    Hi!

    ` + ); + }); + + it('should support sortof components without args but with children', () => { + let items = ['one', 'two']; + + const Item = ({ children }) => ( +
  • +

    + {children} +
  • + ); + + expect( +
    +

    Hi!

    + +
    + ).to.equal( + `

    Hi!

    ` + ); + }); + + it('should support empty (void) tags', () => { + expect( +
    + + +
    + + + +
    + + + + + + + + + + {/* Not void elements */} +
    + +

    +

    + ).to.equal( + `


    ` + ); + }); + + it('should handle special prop names', () => { + expect( +
    + ).to.equal( + '
    ' + ); + }); + + it('should support string fragments', () => { + expect( + h(null, null, "foo", "bar", "baz") + ).to.equal( + 'foobarbaz' + ); + }); + + it('should support element fragments', () => { + expect( + h(null, null,

    foo

    , bar,
    baz
    ) + ).to.equal( + '

    foo

    bar
    baz
    ' + ); + }); + }); pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy