├── .gitignore ├── .travis.yml ├── babel-plugin.js ├── lib └── tag.js ├── package.json ├── README.md ├── tests ├── entries │ ├── styles.jsx │ ├── attrs.jsx │ ├── children.jsx │ └── tags.jsx └── test.js ├── LICENSE.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' -------------------------------------------------------------------------------- /babel-plugin.js: -------------------------------------------------------------------------------- 1 | var htmlTags = require('html-tags'); 2 | var svgTags = require('svg-tags'); 3 | 4 | module.exports = require('babel-plugin-jsx/gen')({ 5 | captureScope: true, 6 | builtins: htmlTags.concat(svgTags) 7 | }); -------------------------------------------------------------------------------- /lib/tag.js: -------------------------------------------------------------------------------- 1 | function Tag(name, props) { 2 | this.name = name; 3 | this.props = props; 4 | this.children = []; 5 | } 6 | 7 | Tag.prototype.toString = function() { 8 | var props = this.props ? ' ' + this.props : ''; 9 | 10 | return '<' + this.name + props + '>' + 11 | this.children.join('') + 12 | ''; 13 | }; 14 | 15 | module.exports = Tag; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsx-to-html", 3 | "version": "1.1.0", 4 | "description": "Render JSX-IR to HTML or XML string", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node_modules/.bin/mocha tests/test.js" 8 | }, 9 | "author": "Arthur Stolyar ", 10 | "license": "MIT", 11 | "dependencies": { 12 | "babel-plugin-jsx": "^1.1.0", 13 | "empty-tags": "^1.0.0", 14 | "escape-html": "^1.0.2", 15 | "html-tags": "^1.1.1", 16 | "jsx-runtime": "^1.1.0", 17 | "svg-tags": "^1.0.0" 18 | }, 19 | "repository": "jsx-ir/jsx-to-html", 20 | "devDependencies": { 21 | "babel-core": "^5.6.20", 22 | "mocha": "^2.2.5" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jsx-ir/jsx-to-html.svg?branch=master)](https://travis-ci.org/jsx-ir/jsx-to-html) 2 | 3 | ## HTML Renderer for JSX-IR 4 | 5 | ### Installation 6 | 7 | ```npm install jsx-to-html``` 8 | 9 | ### Usage 10 | 11 | #### Transpiling 12 | 13 | ```js 14 | babel.transform(code, { 15 | plugins: ['jsx-to-html/babel-plugin'], 16 | blacklist: ['react'] 17 | }); 18 | ``` 19 | or any other way described [here](http://babeljs.io/docs/advanced/plugins/#usage), just pass `'jsx-to-html/babel-plugin'`` as a plugin name. 20 | 21 | ### Runtime 22 | 23 | ```javascript 24 | import { render } from 'jsx-to-html'; 25 | 26 | var content = render(
Hello World
); 27 | 28 | container.innerHTML = content; 29 | ``` 30 | 31 | ## License 32 | 33 | [MIT](LICENSE.md) -------------------------------------------------------------------------------- /tests/entries/styles.jsx: -------------------------------------------------------------------------------- 1 | var runtime = require('../../index'); 2 | var emptyTags = require('empty-tags'); 3 | var assert = require('assert'); 4 | 5 | export var object = () => { 6 | let elem = runtime.render( 7 |
12 | ); 13 | 14 | assert.equal(elem, '
'); 15 | }; 16 | 17 | export var object_vendors = () => { 18 | let elem = runtime.render( 19 |
27 | ); 28 | 29 | let str = '
' 31 | 32 | assert.equal(elem, str); 33 | }; 34 | 35 | export var string = () => { 36 | let elem = runtime.render( 37 |
40 | ); 41 | 42 | assert.equal(elem, '
'); 43 | }; -------------------------------------------------------------------------------- /tests/entries/attrs.jsx: -------------------------------------------------------------------------------- 1 | var runtime = require('../../index'); 2 | var emptyTags = require('empty-tags'); 3 | var assert = require('assert'); 4 | 5 | export var simple = () => { 6 | let elem = runtime.render( 7 |
8 | ); 9 | 10 | let str = '
'; 11 | 12 | assert.equal(elem, str); 13 | }; 14 | 15 | export var js_values = () => { 16 | let elem = runtime.render( 17 |
24 | ); 25 | 26 | let str = '
'; 28 | 29 | assert.equal(elem, str); 30 | }; 31 | 32 | export var prop_vs_attr= () => { 33 | let elem = runtime.render( 34 |
35 | ); 36 | 37 | let str = '
'; 38 | 39 | assert.equal(elem, str); 40 | }; -------------------------------------------------------------------------------- /tests/entries/children.jsx: -------------------------------------------------------------------------------- 1 | var runtime = require('../../index'); 2 | var emptyTags = require('empty-tags'); 3 | var assert = require('assert'); 4 | 5 | export var simple_text = () => { 6 | let elem = runtime.render( 7 |
8 | text 9 |
10 | ); 11 | 12 | assert.equal(elem, '
text
'); 13 | }; 14 | 15 | export var escape_text = () => { 16 | let elem = runtime.render( 17 |
{ '\'"&' }
18 | ); 19 | 20 | assert.equal(elem, '
<span>'"&</span>
'); 21 | }; 22 | 23 | export var array_child = () => { 24 | let elem = runtime.render( 25 |
26 | { [, 'Hello', ] } 27 |
28 | ); 29 | 30 | assert.equal(elem, '
Hello
'); 31 | } 32 | 33 | export var jsvalues_text = () => { 34 | let elem = runtime.render( 35 |
36 | { true } 37 | { 1 } 38 | { 'str' } 39 | { {} } 40 | { [1, 2, 3] } 41 |
42 | ); 43 | 44 | var str = '
true1str[object Object]123
'; 45 | 46 | assert.equal(elem, str); 47 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Arthur Stolyar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var babel = require('babel-core'); 6 | var plugin = require('../babel-plugin'); 7 | var Module = require('module'); 8 | 9 | var testsFolder = path.join(__dirname, 'entries'); 10 | var dir = fs.readdirSync(testsFolder); 11 | 12 | var modulePaths = module.paths; 13 | 14 | describe('dom tests', function() { 15 | dir.forEach(function(fileName) { 16 | var filePath = path.join(testsFolder, fileName); 17 | var file = fs.readFileSync(filePath, 'utf-8'); 18 | 19 | testFile(file, filePath, fileName); 20 | 21 | function testFile(file, filePath, fileName) { 22 | var result = babel.transform(file, { 23 | plugins: [plugin], 24 | blacklist: ['react'] 25 | }); 26 | 27 | var mod = requireString(result.code, filePath, fileName); 28 | 29 | describe(fileName, function() { 30 | Object.keys(mod).forEach(function(key) { 31 | it(key, mod[key]); 32 | }); 33 | }); 34 | } 35 | }); 36 | }); 37 | 38 | function requireString(src, filePath, fileName) { 39 | var module = new Module(fileName); 40 | 41 | module.filename = filePath; 42 | module.paths = Module._nodeModulePaths(path.dirname(filePath)); 43 | module._compile(src, filePath); 44 | 45 | return module.exports; 46 | } 47 | -------------------------------------------------------------------------------- /tests/entries/tags.jsx: -------------------------------------------------------------------------------- 1 | var runtime = require('../../index'); 2 | var emptyTags = require('empty-tags'); 3 | var assert = require('assert'); 4 | 5 | export var simple = () => { 6 | let elem = runtime.render(
); 7 | 8 | assert.equal( 9 | elem, '
' 10 | ); 11 | }; 12 | 13 | export var svg_tags = () => { 14 | let elem = runtime.render( 15 |
16 | 17 |
18 | ); 19 | 20 | let str = '
'; 21 | 22 | assert.equal(elem, str); 23 | }; 24 | 25 | export var foreign_tags = () => { 26 | let elem = runtime.render( 27 |
28 | 29 | 30 | 31 |
32 | ); 33 | 34 | let str = '
'; 35 | 36 | assert.equal(elem, str); 37 | }; 38 | 39 | export var custom_tags = () => { 40 | let elem = runtime.render( 41 |
42 | 43 |
44 | ); 45 | 46 | let str = '
'; 47 | 48 | assert.equal(elem, str); 49 | }; 50 | 51 | export var scope_tags = () => { 52 | var Scoped = function() { 53 | return 54 | }; 55 | 56 | let elem = runtime.render( 57 |
58 | 59 |
60 | ); 61 | 62 | let str = '
'; 63 | 64 | assert.equal(elem, str); 65 | }; 66 | 67 | export var empty_tags = () => { 68 | emptyTags.forEach(function(tag, i) { 69 | var Empty = function() { 70 | return { 71 | tag: tag, 72 | children: [
], 73 | props: null 74 | } 75 | }; 76 | 77 | assert.throws(function() { 78 | runtime.render(); 79 | }, function(e) { 80 | if (e.message === 'Tag <' + tag + ' /> cannot have children') { 81 | return true; 82 | } 83 | }, 'Tag <' + tag + '> cannot have children, but it has'); 84 | }); 85 | } 86 | 87 | export var escale_tag_name = () => { 88 | assert.throws(function() { 89 | runtime.render({ 90 | tag: '
', 91 | children: null, 92 | props: null 93 | }); 94 | }, function(e) { 95 | if (e.message.indexOf('Incorrect tag name:') === 0) { 96 | return true; 97 | } 98 | }); 99 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var jsx = require('jsx-runtime'); 2 | var escape = require('escape-html'); 3 | var Tag = require('./lib/tag'); 4 | var hasOwn = Object.prototype.hasOwnProperty; 5 | 6 | var emptyTags = require('empty-tags').reduce(function(map, tag) { 7 | map[tag] = true; 8 | return map; 9 | }, Object.create(null)); 10 | 11 | var renderer = jsx.register('HTML', { 12 | tags: { 13 | '*': { 14 | enter: function(tag, props) { 15 | if (escape(tag) !== tag) { 16 | throw new Error('Incorrect tag name: ' + tag); 17 | } 18 | 19 | return new Tag(tag, props); 20 | }, 21 | leave: function(parent, tag) { 22 | return parent; 23 | }, 24 | child: function(child, parent) { 25 | if (child == null) return parent; 26 | 27 | if (child instanceof Tag) { 28 | // do nothing 29 | } else { 30 | child = escape(child + ''); 31 | } 32 | 33 | parent.children.push(child); 34 | 35 | return parent; 36 | }, 37 | props: function(props) { 38 | return Object.keys(props) 39 | .map(function(key) { 40 | return mapProps(key, key && props[key]); 41 | }).join(' '); 42 | }, 43 | children: function(children, parent, tag) { 44 | if (typeof emptyTags[tag.toLowerCase()] !== 'undefined') { 45 | throw new Error('Tag <' + tag + ' /> cannot have children'); 46 | } 47 | 48 | return children; 49 | } 50 | } 51 | }, 52 | after: function(tag) { 53 | return tag.toString(); 54 | } 55 | }); 56 | 57 | module.exports = renderer; 58 | 59 | function mapProps(key, val) { 60 | if (!key || val == null) return ''; 61 | if (val instanceof Tag) return ''; 62 | 63 | if (key === 'className') key = 'class'; 64 | else if (key === 'cssFor') key = 'for'; 65 | else key = key.toLowerCase(); 66 | 67 | if (key === 'style') { 68 | val = handleStyle(val); 69 | } 70 | 71 | if (typeof val === 'string') { 72 | // do nothing 73 | } else { 74 | val = JSON.stringify(val); 75 | } 76 | 77 | return escape(key) + '="' + escape(val) + '"'; 78 | } 79 | 80 | function handleStyle(style) { 81 | if (typeof style === 'string') return style; 82 | 83 | var string = ''; 84 | 85 | for (var key in style) { 86 | if (!hasOwn.call(style, key)) continue; 87 | 88 | var val = style[key]; 89 | 90 | key = key.replace(/[A-Z]/g, function(m) { 91 | return '-' + m.toLowerCase(); 92 | }); 93 | 94 | if (key.search(/moz-|webkit-|o-|ms-/) === 0) { 95 | key = '-' + key; 96 | } 97 | 98 | string += (string ? ' ' : '') + key + ': ' + val + ';'; 99 | } 100 | 101 | return string; 102 | } --------------------------------------------------------------------------------