diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..9c378bd
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+indent_style = tab
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[{package.json,.*rc,*.yml}]
+indent_style = space
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..dd44972
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+*.md
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..3a94731
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,58 @@
+{
+ "parser": "babel-eslint",
+ "extends": "eslint:recommended",
+ "env": {
+ "browser": true,
+ "mocha": true,
+ "es6": true
+ },
+ "parserOptions": {
+ "ecmaFeatures": {
+ "modules": true,
+ "jsx": true
+ }
+ },
+ "globals": {
+ "require": true
+ },
+ "rules": {
+ "no-empty": 0,
+ "no-console": 0,
+ "no-unused-vars": [0, { "varsIgnorePattern": "^h$" }],
+ "no-cond-assign": 1,
+ "semi": 2,
+ "camelcase": 0,
+ "comma-style": 2,
+ "comma-dangle": [2, "never"],
+ "indent": [2, "tab", {"SwitchCase": 1}],
+ "no-mixed-spaces-and-tabs": [2, "smart-tabs"],
+ "no-trailing-spaces": [2, { "skipBlankLines": true }],
+ "max-nested-callbacks": [2, 3],
+ "no-eval": 2,
+ "no-implied-eval": 2,
+ "no-new-func": 2,
+ "guard-for-in": 2,
+ "eqeqeq": 1,
+ "no-else-return": 2,
+ "no-redeclare": 2,
+ "no-dupe-keys": 2,
+ "radix": 2,
+ "strict": [2, "never"],
+ "no-shadow": 0,
+ "no-delete-var": 2,
+ "no-undef-init": 2,
+ "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,
+ "no-const-assign": 2,
+ "prefer-spread": 2,
+ "no-useless-concat": 2,
+ "no-var": 2,
+ "object-shorthand": 2,
+ "prefer-arrow-callback": 2
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..72e9b45
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/dist
+/node_modules
+/npm-debug.log
+.DS_Store
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..4c2095a
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1 @@
+.eslintrc
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..8524235
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 4
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..38d8969
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Jason Miller
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dd09fd0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,47 @@
+# vhtml
+
+[![NPM](https://img.shields.io/npm/v/vhtml.svg?style=flat)](https://www.npmjs.org/package/vhtml)
+[![travis-ci](https://travis-ci.org/developit/vhtml.svg?branch=master)](https://travis-ci.org/developit/vhtml)
+
+### **Render JSX/Hyperscript to HTML strings, without VDOM**
+
+> Need to use HTML strings (angular?) but want to use JSX? vhtml's got your back.
+
+
+---
+
+
+## Installation
+
+Via npm:
+
+`npm install --save vhtml`
+
+
+---
+
+
+## Usage
+
+```js
+// import the library:
+import h from 'vhtml';
+
+// tell babel to transpile JSX to h() calls:
+/** @jsx h */
+
+// now render JSX to an HTML string!
+let items = ['one', 'two', 'three'];
+
+document.body.innerHTML = (
+
+
Hi!
+
Here is a list of {items.length} items:
+
+ { items.map( item => (
+ - { item }
+ )) }
+
+
+);
+```
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f716f83
--- /dev/null
+++ b/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "vhtml",
+ "amdName": "vhtml",
+ "version": "1.0.0",
+ "description": "Hyperscript reviver that constructs a sanitized HTML string.",
+ "main": "dist/vhtml.js",
+ "minified:main": "dist/vhtml.min.js",
+ "jsnext:main": "src/vhtml.js",
+ "scripts": {
+ "build": "npm-run-all transpile minify size",
+ "transpile": "rollup -c rollup.config.js",
+ "minify": "uglifyjs $npm_package_main -cm -o $npm_package_minified_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_minified_main}.map",
+ "size": "echo \"gzip size: $(gzip-size $npm_package_minified_main | pretty-bytes)\"",
+ "test": "eslint {src,test} && mocha --compilers js:babel-register test/**/*.js",
+ "prepublish": "npm-run-all build test",
+ "release": "npm run -s build && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
+ },
+ "babel": {
+ "presets": [
+ "es2015-minimal",
+ "stage-0",
+ "react"
+ ],
+ "plugins": [
+ "transform-object-rest-spread",
+ [
+ "transform-react-jsx",
+ {
+ "pragma": "h"
+ }
+ ]
+ ]
+ },
+ "keywords": [
+ "hyperscript",
+ "html",
+ "renderer",
+ "strings"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/developit/vhtml.git"
+ },
+ "author": "Jason Miller ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/developit/vhtml/issues"
+ },
+ "homepage": "https://github.com/developit/vhtml",
+ "devDependencies": {
+ "babel-core": "^6.6.4",
+ "babel-eslint": "^5.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-stage-0": "^6.5.0",
+ "babel-register": "^6.7.2",
+ "chai": "^3.5.0",
+ "eslint": "~2.2.0",
+ "gzip-size-cli": "^1.0.0",
+ "mkdirp": "^0.5.1",
+ "mocha": "^2.4.5",
+ "npm-run-all": "^1.5.1",
+ "pretty-bytes-cli": "^1.0.0",
+ "rollup": "^0.25.4",
+ "rollup-plugin-babel": "^2.4.0",
+ "uglify-js": "^2.6.2"
+ }
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..0de39c9
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,28 @@
+import path from 'path';
+import fs from 'fs';
+import babel from 'rollup-plugin-babel';
+
+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,
+ format: 'umd',
+ external,
+ plugins: [
+ babel({
+ babelrc: false,
+ comments: false,
+ exclude: 'node_modules/**',
+ presets: [
+ 'es2015-minimal-rollup'
+ ].concat(pkg.babel.presets.slice(1)),
+ plugins: require('babel-preset-es2015-minimal-rollup').plugins.concat([
+ ['transform-react-jsx', { pragma:'h' }]
+ ])
+ })
+ ]
+};
diff --git a/src/vhtml.js b/src/vhtml.js
new file mode 100644
index 0000000..a63a134
--- /dev/null
+++ b/src/vhtml.js
@@ -0,0 +1,36 @@
+// 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 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])}"`;
+ }
+ }
+ s += `>${[].concat(...children).map(child).join('')}${name}>`;
+ sanitized[s] = true;
+ return s;
+}
+
+
+
+// for fun:
+/*
+export default const h = (tag, attrs, ...kids) => (
+ `<${tag}${h.attrs(attrs)}>${[].concat(...kids).join('')}${tag}>`
+);
+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'};
+*/
diff --git a/test/vhtml.js b/test/vhtml.js
new file mode 100644
index 0000000..51550df
--- /dev/null
+++ b/test/vhtml.js
@@ -0,0 +1,42 @@
+import h from '../src/vhtml';
+import { expect } from 'chai';
+/** @jsx h */
+/*global describe,it*/
+
+describe('vhtml', () => {
+ it('should stringify html', () => {
+ let items = ['one', 'two', 'three'];
+ expect(
+
+
Hi!
+
Here is a list of {items.length} items:
+
+ { items.map( item => (
+ - { item }
+ )) }
+
+
+ ).to.equal(
+ `Hi!
Here is a list of 3 items:
`
+ );
+ });
+
+ it('should sanitize children', () => {
+ expect(
+
+ { `blocked` }
+ allowed
+
+ ).to.equal(
+ `<strong>blocked</strong>allowed
`
+ );
+ });
+
+ it('should sanitize attributes', () => {
+ expect(
+ "'`} />
+ ).to.equal(
+ `
`
+ );
+ });
+});