├── .gitignore
├── babel-plugin.js
├── .travis.yml
├── tests
├── _run.js
├── entries
│ ├── text.jsx
│ ├── styles.jsx
│ ├── tags.jsx
│ └── attrs.jsx
├── _get-runtime.js
├── test.js
└── _mockdom.js
├── package.json
├── README.md
├── LICENSE.md
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/babel-plugin.js:
--------------------------------------------------------------------------------
1 | module.exports = require('babel-plugin-jsx/gen')({
2 | captureScope: true
3 | });
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - 'iojs'
5 | - '0.12'
6 | - '0.10'
--------------------------------------------------------------------------------
/tests/_run.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var runtime = require('./_get-runtime.js')();
4 | var _mockdom = require('./_mockdom');
5 |
6 | var run = function(view) {
7 | return _mockdom.run(function() {
8 | runtime.render(view);
9 | });
10 | };
11 |
12 | module.exports = run;
--------------------------------------------------------------------------------
/tests/entries/text.jsx:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var run = require('../_run');
3 |
4 | export var simple = () => {
5 | let elem = run(
6 |
7 | text-test
8 |
9 | );
10 |
11 | assert.deepEqual(
12 | elem, {
13 | tag: 'div',
14 | children: [{
15 | type: '#text',
16 | value: 'text-test'
17 | }]
18 | }
19 | );
20 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsx-to-idom",
3 | "version": "1.1.1",
4 | "description": "JSX-IR renderer for Incremental DOM",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node_modules/.bin/mocha tests/test.js"
8 | },
9 | "author": "Arthur Stolyar ",
10 | "license": "MIT",
11 | "repository": "jsx-ir/jsx-to-idom",
12 | "peerDependencies": {
13 | "incremental-dom": "^0.x"
14 | },
15 | "dependencies": {
16 | "babel-plugin-jsx": "^1.1.0",
17 | "jsx-runtime": "^1.1.0"
18 | },
19 | "devDependencies": {
20 | "babel-core": "^5.6.20",
21 | "mocha": "^2.2.5"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/entries/styles.jsx:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var run = require('../_run');
3 |
4 | export var object = () => {
5 | let elem = run(
6 |
11 | );
12 |
13 | assert.deepEqual(
14 | elem, {
15 | tag: 'div',
16 | props: {
17 | style: {
18 | color: 'black'
19 | }
20 | }
21 | }
22 | );
23 | };
24 |
25 | export var string = () => {
26 | let elem = run(
27 |
30 | );
31 |
32 | assert.deepEqual(
33 | elem, {
34 | tag: 'div',
35 | attrs: {
36 | style: 'color: black'
37 | }
38 | }
39 | );
40 | };
--------------------------------------------------------------------------------
/tests/_get-runtime.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var path = require('path');
3 | var Module = require('module');
4 |
5 | var modulePaths = module.paths;
6 |
7 | function requireString(src, filePath, fileName) {
8 | var module = new Module(fileName);
9 |
10 | module.filename = filePath;
11 | module.paths = Module._nodeModulePaths(path.dirname(filePath));
12 | module._compile(src, filePath);
13 |
14 | return module.exports;
15 | }
16 |
17 | module.exports = function(filePath) {
18 | filePath = path.join(__dirname, filePath || '../index.js');
19 |
20 | var file = fs.readFileSync(filePath, 'utf-8');
21 | var mock = '"use strict"; var _mockdom = require("./tests/_mockdom");\n';
22 |
23 | var fakeName = '../fake-runtime.js';
24 | var module = requireString(mock + '\n' + file, path.join(__dirname, fakeName), fakeName);
25 |
26 | return module;
27 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/jsx-ir/jsx-to-idom)
2 |
3 | ## Incremental DOM Renderer for JSX-IR
4 |
5 | ### Installation
6 |
7 | ```npm install jsx-to-idom```
8 |
9 | _**Note:** do not forget to install "incremental-dom" manually since from NPM 3 peer dependencies won't be installed automatically_
10 |
11 | ### Usage
12 |
13 | #### Transpiling
14 |
15 | ```js
16 | babel.transform(code, {
17 | plugins: ['jsx-to-idom/babel-plugin'],
18 | blacklist: ['react']
19 | });
20 | ```
21 | or any other way described [here](http://babeljs.io/docs/advanced/plugins/#usage), just pass `'jsx-to-idom/babel-plugin'`` as a plugin name.
22 |
23 | ### Runtime
24 |
25 | ```javascript
26 | import { render } from 'jsx-to-idom';
27 | import { patch } from 'incremental-dom'
28 |
29 | patch(container, () => {
30 | render(Hello World
);
31 | });
32 |
33 | ```
34 |
35 | ## License
36 |
37 | [MIT](LICENSE.md)
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var jsx = require('jsx-runtime');
4 | var idom = typeof _mockdom !== 'undefined' ? _mockdom : require('incremental-dom');
5 | var hasOwn = Object.prototype.hasOwnProperty;
6 |
7 | var openStart = idom.elementOpenStart;
8 | var openEnd = idom.elementOpenEnd;
9 | var close = idom.elementClose;
10 | var text = idom.text;
11 | var attr = idom.attr;
12 |
13 | var element = {};
14 |
15 | var renderer = jsx.register('DOM', {
16 | tags: {
17 | '*': {
18 | enter: function(tag, props) {
19 | openStart(tag);
20 |
21 | if (props) {
22 | handleProps(props);
23 | }
24 |
25 | openEnd();
26 |
27 | return element;
28 | },
29 | leave: function(parent, tag) {
30 | close(tag);
31 | return parent;
32 | },
33 | child: function(child, parent) {
34 | if (child === element) {
35 | // do nothing
36 | } else {
37 | text(child + '');
38 | }
39 |
40 | return parent;
41 | }
42 | }
43 | }
44 | });
45 |
46 | module.exports = renderer;
47 |
48 | function handleProps(props) {
49 | for (var key in props) {
50 | if (!hasOwn.call(props, key)) continue;
51 |
52 | var val = props[key];
53 |
54 | if (key === 'className') key = 'class';
55 | if (key === 'cssFor') key = 'for';
56 |
57 | attr(key, val);
58 | }
59 | }
--------------------------------------------------------------------------------
/tests/entries/tags.jsx:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var run = require('../_run');
3 |
4 | export var simple = () => {
5 | var elem = run();
6 |
7 | assert.deepEqual(
8 | elem, {
9 | tag: 'div'
10 | }
11 | );
12 | };
13 |
14 | export var svg_namespace = () => {
15 | let elem = run(
16 |
17 |
18 |
19 | );
20 |
21 | // cannot test namespaces for idom
22 | assert.deepEqual(elem, {
23 | tag: 'div',
24 | children: [{
25 | tag: 'svg'
26 | }]
27 | });
28 | };
29 |
30 | export var html_namespace = () => {
31 | let elem = run(
32 |
33 |
36 |
37 | );
38 |
39 | // cannot test namespaces for idom
40 | assert.deepEqual(elem, {
41 | tag: 'div',
42 | children: [{
43 | tag: 'svg',
44 | children: [{
45 | tag: 'foreignObject',
46 | }]
47 | }]
48 | });
49 | };
50 |
51 | export var custom_tags = () => {
52 | let elem = run(
53 |
54 |
55 |
56 | );
57 |
58 | assert.deepEqual(elem, {
59 | tag: 'div',
60 | children: [{
61 | tag: 'custom-tag'
62 | }]
63 | });
64 | };
65 |
66 | export var scope_tags = () => {
67 | var Scoped = function() {
68 | return
69 | };
70 |
71 | let elem = run(
72 |
73 |
74 |
75 | );
76 |
77 | assert.deepEqual(elem, {
78 | tag: 'div',
79 | children: [{
80 | tag: 'span'
81 | }]
82 | });
83 | };
--------------------------------------------------------------------------------
/tests/entries/attrs.jsx:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var run = require('../_run');
3 |
4 | export var simple = () => {
5 | let elem = run(
6 |
7 | );
8 |
9 | assert.deepEqual(
10 | elem, {
11 | tag: 'div',
12 | attrs: {
13 | 'data-test': 'test'
14 | }
15 | }
16 | );
17 | };
18 |
19 | export var js_values = () => {
20 | let fn = () => { 1; };
21 |
22 | let elem = run(
23 |
33 | );
34 |
35 | assert.deepEqual(
36 | elem, {
37 | tag: 'div',
38 | attrs: {
39 | 'data-boolean': true,
40 | 'data-number': 1,
41 | 'data-string': 'str'
42 | },
43 | props: {
44 | 'data-object': {},
45 | 'data-array': [1, 2, 3],
46 | 'data-fn': fn,
47 | 'data-null': null
48 | }
49 | }
50 | );
51 | };
52 |
53 | export var props_transformation = () => {
54 | let elem = run(
55 |
56 | );
57 |
58 | assert.deepEqual(
59 | elem, {
60 | tag: 'div',
61 | attrs: {
62 | 'class': 'test',
63 | 'for': 'target'
64 | }
65 | }
66 | );
67 | };
68 |
69 | export var identifier_attrs = () => {
70 | let elem = run(
71 |
72 | );
73 |
74 | assert.deepEqual(
75 | elem, {
76 | tag: 'div',
77 | attrs: {
78 | 'class': 'test',
79 | 'for': 'target'
80 | }
81 | }
82 | );
83 | };
--------------------------------------------------------------------------------
/tests/_mockdom.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var currentRun;
4 |
5 | var getElement = function() {
6 | var stack = currentRun.stack;
7 |
8 | return stack.length ? stack[stack.length - 1] : null;
9 | };
10 |
11 | module.exports = {
12 | run: function(fn) {
13 | if (currentRun) throw new Error('Cannot run while run');
14 |
15 | currentRun = {
16 | stack: [],
17 | result: []
18 | };
19 |
20 | fn();
21 |
22 | var result = currentRun.result;
23 | currentRun = null;
24 |
25 | // jsx does not support top-level siblings
26 | return result[0];
27 | },
28 | elementOpenStart: function(tag) {
29 | var parent = getElement();
30 | var element = {
31 | tag: tag,
32 | children: [],
33 | props: {},
34 | attrs: {},
35 | // openEnded: false
36 | };
37 |
38 | if (parent) {
39 | parent.children.push(element);
40 | } else {
41 | currentRun.result.push(element);
42 | }
43 |
44 | currentRun.stack.push(element);
45 | },
46 | attr: function(key, val) {
47 | var element = getElement();
48 |
49 | if (!element) {
50 | throw new Error('Cannot set attr when there is no element');
51 | }
52 |
53 | if (element.openEnded) {
54 | // throw new Error('Cannot set attr when element opening is ended');
55 | }
56 |
57 | var type = typeof val;
58 |
59 | if (val === undefined) {
60 | delete element.attrs[key];
61 | } else if (type === 'object' || type === 'function') {
62 | element.props[key] = val;
63 | } else {
64 | element.attrs[key] = val;
65 | }
66 | },
67 | elementOpenEnd: function() {
68 | var element = getElement();
69 |
70 | if (!element) {
71 | throw new Error('Cannot end element opening when there is no element');
72 | }
73 |
74 | if (!Object.keys(element.props).length) {
75 | delete element.props;
76 | }
77 |
78 | if (!Object.keys(element.attrs).length) {
79 | delete element.attrs;
80 | }
81 |
82 | // element.openEnded = true;
83 | },
84 | text: function(text) {
85 | var parent = getElement();
86 | var text = {
87 | type: '#text',
88 | value: text + ''
89 | };
90 |
91 | if (!parent) {
92 | throw new Error('Text cannot be top-level call');
93 | }
94 |
95 | parent.children.push(text);
96 | },
97 | elementClose: function(tag) {
98 | var element = getElement();
99 |
100 | if (!element) {
101 | throw new Error('Cannot close element when there is no one');
102 | }
103 |
104 | if (element.tag !== tag) {
105 | throw new Error('Cannot close element with different tag: actual <' +
106 | element.tag + '>, expected <' + tag + '>');
107 | }
108 |
109 | if (!element.children.length) {
110 | delete element.children;
111 | }
112 |
113 | currentRun.stack.pop();
114 | }
115 | };
--------------------------------------------------------------------------------