├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── package.json
├── src
├── eslint-unknown-property.js
└── index.js
└── test
├── fixtures
├── boolean-attributes
│ ├── actual.js
│ └── expected.js
├── className-attribute
│ ├── actual.js
│ └── expected.js
├── comments
│ ├── actual.js
│ └── expected.js
├── component
│ ├── actual.js
│ └── expected.js
├── deprecated-html
│ ├── actual.js
│ ├── expected.js
│ └── options.json
├── element
│ ├── .babelrc
│ ├── actual.js
│ └── expected.js
├── jsx-compliant
│ ├── actual.js
│ ├── expected.js
│ └── options.json
├── multiple-attributes
│ ├── actual.js
│ └── expected.js
├── nested
│ ├── actual.js
│ └── expected.js
├── reserved-word-attributes
│ ├── actual.js
│ └── expected.js
├── single-attribute
│ ├── actual.js
│ └── expected.js
├── spread
│ ├── actual.js
│ └── expected.js
├── text
│ ├── actual.js
│ └── expected.js
└── variable-as-attribute-name
│ ├── actual.js
│ └── expected.js
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | node_modules
3 | *~
4 | *.swp
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *~
2 | src
3 | index5.js.old
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8.1"
4 | - "7.10"
5 | - "6.11"
6 | - "5.12"
7 | - "4.8"
8 | - "0.12"
9 | - "0.11"
10 | - "iojs"
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Richard Eames
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of babel-plugin-mjsx nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # babel-plugin-mjsx
2 |
3 | [](https://travis-ci.org/Naddiseo/babel-plugin-mjsx)
4 |
5 | Mithril precompilation JSX plugin for babel.
6 |
7 | Installation
8 | ============
9 |
10 | $ npm i babel-plugin-mjsx
11 |
12 | Usage
13 | =====
14 |
15 | Add `babel-plugin-mjsx` to your plugins config:
16 |
17 | $ cat .babelrc
18 | {
19 | "plugins": ["mjsx"]
20 | }
21 |
22 | More information on setting up Babel is available in [Babel's documentation](https://babeljs.io/docs/setup/)
23 |
24 | Available plugin options:
25 |
26 | * `jsxCompliant`: convert attributes to the correct casing, e.g: `onClick` -> `onclick`, or `for` -> `htmlFor`.
27 | * `warnDeprecatedHtml`: Warns of deprecated HTML tags such as `blink` and `center`.
28 |
29 | More information on plugin options syntax is available in [Babel's documentation](http://babeljs.io/docs/plugins/#options)
30 |
31 | Example output:
32 | ```javascript
33 | let a = {t: 1};
34 | let KK =
Component children
;
35 | ```
36 | Transpiled:
37 | ```javascript
38 | var a = { t: 1 };
39 | var KK = {
40 | tag: 'div',
41 | children: [m.component(Component, {
42 | foo: 'bar'
43 | }, ['Component children'])],
44 | attrs: babelHelpers._extends({
45 | style: '1'
46 | }, a)
47 | };
48 | ```
49 |
50 | Change Log
51 | ==========
52 |
53 | v4.1.1
54 |
55 | * Build fix
56 |
57 | v4.1.0
58 |
59 | * Added option to convert html attributes to proper casing
60 |
61 | ```
62 | // Add "jsxCompliant" to babel plugin options:
63 | $ cat .babelrc
64 | {
65 | "plugins":[
66 | ["mjsx", { jsxCompliant: true }]
67 | ]
68 | }
69 | ```
70 |
71 | * Added explicit `warnDeprecatedHtml` option to turn on the deprecated html tags warning added in v3
72 |
73 | ```
74 | // Add "warnDeprecatedHtml" to babel plugin options:
75 | $ cat .babelrc
76 | {
77 | "plugins":[
78 | ["mjsx", { warnDeprecatedHtml: true }]
79 | ]
80 | }
81 | ```
82 |
83 | v4.0.0
84 |
85 | * Changed output to trim whitespace similar to how v1.x did.
86 |
87 | v3.0.0
88 |
89 | * Removed forcing of tags to be "valid" html tags
90 | * Print warning instead of erroring for deprecated html tags
91 |
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "babel-plugin-mjsx",
3 | "version": "4.1.1",
4 | "description": "Mithril precompilation JSX plugin for babel",
5 | "main": "lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/Naddiseo/babel-plugin-mjsx.git"
9 | },
10 | "keywords": [
11 | "mithril",
12 | "babel",
13 | "plugin",
14 | "babel-plugin"
15 | ],
16 | "author": "Richard Eames",
17 | "license": "BSD-3-Clause",
18 | "bugs": {
19 | "url": "https://github.com/Naddiseo/babel-plugin-mjsx/issues"
20 | },
21 | "homepage": "https://github.com/Naddiseo/babel-plugin-mjsx#readme",
22 | "dependencies": {
23 | "babel-plugin-syntax-jsx": "^6.0"
24 | },
25 | "devDependencies": {
26 | "babel-cli": "^6.0",
27 | "babel-core": "^6.0",
28 | "babel-preset-es2015": "^6.0",
29 | "mocha": "^2.2.5"
30 | },
31 | "scripts": {
32 | "build": "babel src --out-dir lib",
33 | "prepublish": "npm run build",
34 | "test": "mocha --compilers js:babel-core/register"
35 | },
36 | "babel": {
37 | "presets": ["es2015"]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/eslint-unknown-property.js:
--------------------------------------------------------------------------------
1 | /*
2 | Much of this file is from eslint-plugin-react project, only changed to work with babel-plugin-mjsx
3 | https://github.com/yannickcr/eslint-plugin-react
4 |
5 | The MIT License (MIT)
6 |
7 | Copyright (c) 2014 Yannick Croissant
8 | */
9 |
10 | const DOM_ATTRIBUTE_NAMES = {
11 | // Standard
12 | 'accept-charset': 'acceptCharset',
13 | 'class': 'className',
14 | 'for': 'htmlFor',
15 | 'http-equiv': 'httpEquiv',
16 | // SVG
17 | 'clip-path': 'clipPath',
18 | 'fill-opacity': 'fillOpacity',
19 | 'font-family': 'fontFamily',
20 | 'font-size': 'fontSize',
21 | 'marker-end': 'markerEnd',
22 | 'marker-mid': 'markerMid',
23 | 'marker-start': 'markerStart',
24 | 'stop-color': 'stopColor',
25 | 'stop-opacity': 'stopOpacity',
26 | 'stroke-dasharray': 'strokeDasharray',
27 | 'stroke-linecap': 'strokeLinecap',
28 | 'stroke-opacity': 'strokeOpacity',
29 | 'stroke-width': 'strokeWidth',
30 | 'text-anchor': 'textAnchor',
31 | 'xlink:actuate': 'xlinkActuate',
32 | 'xlink:arcrole': 'xlinkArcrole',
33 | 'xlink:href': 'xlinkHref',
34 | 'xlink:role': 'xlinkRole',
35 | 'xlink:show': 'xlinkShow',
36 | 'xlink:title': 'xlinkTitle',
37 | 'xlink:type': 'xlinkType',
38 | 'xml:base': 'xmlBase',
39 | 'xml:lang': 'xmlLang',
40 | 'xml:space': 'xmlSpace'
41 | };
42 |
43 | const DOM_PROPERTY_NAMES = {};
44 |
45 | [
46 | // Standard
47 | 'acceptCharset', 'accessKey', 'allowFullScreen', 'allowTransparency', 'autoComplete', 'autoFocus', 'autoPlay',
48 | 'cellPadding', 'cellSpacing', 'charSet', 'classID', 'className', 'colSpan', 'contentEditable', 'contextMenu',
49 | 'crossOrigin', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget',
50 | 'frameBorder', 'hrefLang', 'htmlFor', 'httpEquiv', 'marginHeight', 'marginWidth', 'maxLength', 'mediaGroup',
51 | 'noValidate', 'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onCopy', 'onCut', 'onDoubleClick',
52 | 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop',
53 | 'onFocus', 'onInput', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onMouseDown', 'onMouseEnter', 'onMouseLeave',
54 | 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp', 'onPaste', 'onScroll', 'onSubmit', 'onTouchCancel',
55 | 'onTouchEnd', 'onTouchMove', 'onTouchStart', 'onWheel',
56 | 'radioGroup', 'readOnly', 'rowSpan', 'spellCheck', 'srcDoc', 'srcSet', 'tabIndex', 'useMap',
57 | // Non standard
58 | 'autoCapitalize', 'autoCorrect',
59 | 'autoSave',
60 | 'itemProp', 'itemScope', 'itemType', 'itemRef', 'itemID'
61 | ].forEach(function (x) {
62 | DOM_PROPERTY_NAMES[x] = x.toLowerCase();
63 | })
64 |
65 | export function getStandardName(name) {
66 | if (DOM_ATTRIBUTE_NAMES[name]) {
67 | return DOM_ATTRIBUTE_NAMES[name];
68 | }
69 | else if (DOM_PROPERTY_NAMES[name]) {
70 | return DOM_PROPERTY_NAMES[name];
71 | }
72 | return name;
73 | }
74 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { getStandardName as getJSXCompliantName } from './eslint-unknown-property';
2 |
3 | const deprecatedHTML = (
4 | '^(acronym|applet|basefont|big|blink|center|dir|frame|frameset|isindex|listings|noembed|plaintext|spacer|strike|tt|xmp)$'
5 | );
6 |
7 | const deprecatedRx = new RegExp(deprecatedHTML);
8 |
9 | function D(v) {
10 | console.log(JSON.stringify(v, null, '\t'));
11 | }
12 |
13 | export default function({ types: t }) {
14 | function convertAttributeValue(node) {
15 | if (t.isJSXExpressionContainer(node)) {
16 | return node.expression;
17 | } else {
18 | return node;
19 | }
20 | }
21 |
22 | function fixupPropertyName(propertyName, opts = {}) {
23 | let nameValue = propertyName.name;
24 | if (opts.jsxCompliant) {
25 | if ((t.isIdentifier(propertyName) || t.isJSXIdentifier(propertyName))) {
26 | nameValue = getJSXCompliantName(nameValue);
27 | }
28 | }
29 | else {
30 | // Old/mithril behaviour
31 | if ((t.isIdentifier(propertyName) || t.isJSXIdentifier(propertyName)) && nameValue === 'class') {
32 | nameValue = 'className';
33 | }
34 | }
35 | return t.isValidIdentifier(nameValue) ? t.identifier(nameValue) : t.stringLiteral(nameValue)
36 | }
37 |
38 | function convertAttribute(node, opts = {}) {
39 | let propertyName = fixupPropertyName(node.name, opts);
40 | let value = convertAttributeValue(node.value || t.booleanLiteral(true));
41 |
42 | if (t.isStringLiteral(value)) {
43 | value.value = value.value.replace(/\n\s+/g, " ");
44 | }
45 |
46 | return t.inherits(t.objectProperty(propertyName, value), node);
47 | }
48 |
49 | function _stringLiteralTrimmer(lastNonEmptyLine, lineCount, line, i) {
50 | const isFirstLine = (i === 0);
51 | const isLastLine = (i === lineCount - 1);
52 | const isLastNonEmptyLine = (i === lastNonEmptyLine);
53 |
54 | // replace rendered whitespace tabs with spaces
55 | let trimmedLine = line.replace(/\t/g, " ");
56 |
57 |
58 | // trim leading whitespace
59 | if (!isFirstLine) {
60 | trimmedLine = trimmedLine.replace(/^[ ]+/, "");
61 | }
62 |
63 | // trim trailing whitespace
64 | if (!isLastLine) {
65 | trimmedLine = trimmedLine.replace(/[ ]+$/, "");
66 | }
67 |
68 | if (trimmedLine.length > 0) {
69 | if (!isLastNonEmptyLine) {
70 | trimmedLine += " ";
71 | }
72 |
73 | return trimmedLine;
74 | }
75 | return '';
76 | }
77 |
78 | function cleanStringLiteral(value) {
79 | let lines = value.split(/\r\n|\n|\r/);
80 |
81 | let lastNonEmptyLine = 0;
82 |
83 | for (let i = lines.length - 1; i > 0 ; i--) {
84 | if (lines[i].match(/[^ \t]/)) {
85 | lastNonEmptyLine = i;
86 | break;
87 | }
88 | }
89 |
90 | let str = lines
91 | .map(_stringLiteralTrimmer.bind(null, lastNonEmptyLine, lines.length))
92 | .filter(line => line.length > 0)
93 | .join('')
94 | ;
95 |
96 | if (str.length > 0) {
97 | return t.stringLiteral(str);
98 | }
99 | }
100 |
101 | function buildChildren(node) {
102 | return node.children
103 | .map(convertAttributeValue)
104 | .filter(child => !t.isJSXEmptyExpression(child))
105 | .map(child => {
106 | if (t.isStringLiteral(child) || t.isJSXText(child)) {
107 | return cleanStringLiteral(child.value)
108 | }
109 | return child;
110 | })
111 | .filter(child => !!child)
112 | ;
113 | }
114 |
115 | function flatten(args) {
116 | let flattened = [];
117 | let last = null;
118 |
119 | for (let arg of args) {
120 | if (t.isStringLiteral(arg) && t.isStringLiteral(last)) {
121 | last.value += arg.value;
122 | }
123 | else {
124 | last = arg;
125 | flattened.push(arg);
126 | }
127 | }
128 |
129 | return flattened;
130 | }
131 |
132 | return {
133 | inherits: require('babel-plugin-syntax-jsx'),
134 |
135 | visitor: {
136 | JSXElement(path, {file, opts}) {
137 | let {node, parent, scope} = path;
138 | let open = node.openingElement;
139 | let obj = t.objectExpression([]);
140 | let tag = open.name;
141 | let attrObjs = [];
142 | let props = [];
143 | let isComp = false;
144 |
145 | function pushElemProp(key, value) {
146 | obj.properties.push(t.objectProperty(t.identifier(key), value));
147 | }
148 |
149 | function pushProps() {
150 | if (!props.length) { return; }
151 | attrObjs.push(t.objectExpression(props));
152 | props = [];
153 | }
154 |
155 | if (t.isJSXIdentifier(tag)) {
156 | tag = t.stringLiteral(tag.name);
157 | if (opts.warnDeprecatedHtml && deprecatedRx.test(tag.value)) {
158 | console.log(`Using a deprecated html tag: '${ tag.value }'`);
159 | }
160 | if (tag.value !== tag.value.toLowerCase()) {
161 | isComp = true;
162 | }
163 | }
164 |
165 | pushElemProp('tag', tag);
166 |
167 | if (node.children.length) {
168 | pushElemProp('children', t.arrayExpression(flatten(buildChildren(node))));
169 | }
170 |
171 | for (let attr of open.attributes) {
172 | if (t.isJSXSpreadAttribute(attr)) {
173 | pushProps();
174 | attrObjs.push(attr.argument);
175 | }
176 | else {
177 | props.push(convertAttribute(attr, opts));
178 | }
179 | }
180 | pushProps();
181 |
182 | if (attrObjs.length === 1) {
183 | pushElemProp('attrs', attrObjs[0]);
184 | }
185 | else if (attrObjs.length > 1) {
186 | if (!t.isObjectExpression(attrObjs[0])) {
187 | attrObjs.unshift(t.objectExpression([]));
188 | }
189 |
190 | pushElemProp('attrs',
191 | t.callExpression(
192 | file.addHelper('extends'),
193 | attrObjs
194 | )
195 | );
196 | }
197 |
198 | if (isComp) {
199 | let properties = obj.properties;
200 | let attrs = t.objectExpression([]);
201 | let children = t.arrayExpression([]);
202 |
203 | for (let child of properties) {
204 | if (child.key.name === 'attrs') {
205 | attrs = child.value;
206 | }
207 | else if (child.key.name === 'children') {
208 | children = child.value;
209 | }
210 | }
211 |
212 | path.replaceWith(
213 | t.callExpression(
214 | t.memberExpression(
215 | t.identifier('m'),
216 | t.identifier('component')
217 | ),
218 | [t.identifier(tag.value), attrs, children]
219 | ),
220 | node
221 | );
222 | }
223 | else {
224 | path.replaceWith(obj, node);
225 | }
226 | },
227 | }
228 | };
229 | };
230 |
--------------------------------------------------------------------------------
/test/fixtures/boolean-attributes/actual.js:
--------------------------------------------------------------------------------
1 | result =
;
2 |
--------------------------------------------------------------------------------
/test/fixtures/boolean-attributes/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | attrs: { disabled: true, readonly: false }
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/className-attribute/actual.js:
--------------------------------------------------------------------------------
1 | result =
;
2 |
--------------------------------------------------------------------------------
/test/fixtures/className-attribute/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | attrs: { className: "123" }
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/comments/actual.js:
--------------------------------------------------------------------------------
1 | result =
2 | {/*
3 | child
4 | */}
5 | ;
6 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/comments/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | children: []
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/component/actual.js:
--------------------------------------------------------------------------------
1 | result = Component children
;
2 |
--------------------------------------------------------------------------------
/test/fixtures/component/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
4 |
5 | result = {
6 | tag: "div",
7 | children: [m.component(Component, { foo: "bar" }, ["Component children"])],
8 | attrs: _extends({ style: "1" }, a)
9 | };
10 |
--------------------------------------------------------------------------------
/test/fixtures/deprecated-html/actual.js:
--------------------------------------------------------------------------------
1 | result = NO! ;
2 |
--------------------------------------------------------------------------------
/test/fixtures/deprecated-html/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "blink",
5 | children: ["NO!"]
6 | };
7 |
8 |
--------------------------------------------------------------------------------
/test/fixtures/deprecated-html/options.json:
--------------------------------------------------------------------------------
1 | {
2 | "console": [ "Using a deprecated html tag: 'blink'" ],
3 | "opts": { "warnDeprecatedHtml": true }
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/element/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/element/actual.js:
--------------------------------------------------------------------------------
1 | result = (
);
2 |
--------------------------------------------------------------------------------
/test/fixtures/element/expected.js:
--------------------------------------------------------------------------------
1 | result = {
2 | tag: "div"
3 | };
4 |
--------------------------------------------------------------------------------
/test/fixtures/jsx-compliant/actual.js:
--------------------------------------------------------------------------------
1 | result = (
);
--------------------------------------------------------------------------------
/test/fixtures/jsx-compliant/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | children: [{
6 | tag: "label",
7 | attrs: { htmlFor: "hello" }
8 | }],
9 | attrs: { onclick: function onclick() {} }
10 | };
--------------------------------------------------------------------------------
/test/fixtures/jsx-compliant/options.json:
--------------------------------------------------------------------------------
1 | {
2 | "opts": { "jsxCompliant": true }
3 | }
--------------------------------------------------------------------------------
/test/fixtures/multiple-attributes/actual.js:
--------------------------------------------------------------------------------
1 | result =
;
2 |
--------------------------------------------------------------------------------
/test/fixtures/multiple-attributes/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | attrs: { className: "class1 class2", id: "myid", title: "hello" }
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/nested/actual.js:
--------------------------------------------------------------------------------
1 | result =
2 |
3 | Hello World
4 |
7 |
8 |
9 |
10 |
11 |
12 | bar
13 |
14 |
15 | ;
16 |
--------------------------------------------------------------------------------
/test/fixtures/nested/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "html",
5 | children: [{
6 | tag: "head",
7 | children: [{
8 | tag: "title",
9 | children: ["Hello World"]
10 | }, {
11 | tag: "style",
12 | children: ["* { box-sizing: border-box; }"],
13 | attrs: { type: "text/css" }
14 | }, {
15 | tag: "script",
16 | attrs: { src: "./file.js", type: "text/javascript" }
17 | }, {
18 | tag: "meta",
19 | attrs: { name: "encoding", value: "utf8" }
20 | }]
21 | }, {
22 | tag: "body",
23 | children: [{
24 | tag: "div"
25 | }, {
26 | tag: "a",
27 | children: ["bar"],
28 | attrs: { href: "foo" }
29 | }, {
30 | tag: "br"
31 | }]
32 | }]
33 | };
34 |
--------------------------------------------------------------------------------
/test/fixtures/reserved-word-attributes/actual.js:
--------------------------------------------------------------------------------
1 | result =
;
2 |
--------------------------------------------------------------------------------
/test/fixtures/reserved-word-attributes/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | attrs: { "if": "kw", "for": "kw", of: "notkw", "debugger": "kw", async: "notkw", "this": "kw" }
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/single-attribute/actual.js:
--------------------------------------------------------------------------------
1 | result = (
);
2 |
--------------------------------------------------------------------------------
/test/fixtures/single-attribute/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | attrs: { title: "123" }
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/spread/actual.js:
--------------------------------------------------------------------------------
1 | result = ;
4 |
--------------------------------------------------------------------------------
/test/fixtures/spread/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
4 |
5 | result = {
6 | tag: "div",
7 | children: [{
8 | tag: "div",
9 | attrs: props
10 | }],
11 | attrs: _extends({ className: "test", id: id }, props, { key: "test", "data-expanded": expanded }, props.attrs)
12 | };
13 |
--------------------------------------------------------------------------------
/test/fixtures/text/actual.js:
--------------------------------------------------------------------------------
1 | result =
2 | Hello World!
3 |
Bonjour
4 |
{"What's up doc?"}
5 |
{["More ", · ," Text here"]}
6 |
;
7 |
--------------------------------------------------------------------------------
/test/fixtures/text/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | result = {
4 | tag: "div",
5 | children: ["Hello World!", {
6 | tag: "div",
7 | children: [" Bonjour "]
8 | }, {
9 | tag: "div",
10 | children: ["What's up doc?"]
11 | }, {
12 | tag: "div",
13 | children: [["More ", {
14 | tag: "span",
15 | children: ["·"]
16 | }, " Text here"]]
17 | }]
18 | };
19 |
--------------------------------------------------------------------------------
/test/fixtures/variable-as-attribute-name/actual.js:
--------------------------------------------------------------------------------
1 | let title = "Hello";
2 | // JS calls toString on the key of objects
3 | result =
;
4 |
--------------------------------------------------------------------------------
/test/fixtures/variable-as-attribute-name/expected.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var title = "Hello";
4 | // JS calls toString on the key of objects
5 | result = {
6 | tag: "div",
7 | attrs: { title: title }
8 | };
9 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import fs from 'fs';
3 | import assert from 'assert';
4 | import { transformFileSync } from 'babel-core';
5 | import plugin from '../src';
6 |
7 | /*
8 | NOTE: many tests come from babel-plugin-incremental-dom.
9 |
10 | */
11 |
12 | function trim(str) {
13 | return str.replace(/^\s+|\s+$/, '');
14 | }
15 |
16 |
17 | function resolve(path) {
18 | let expected = '';
19 | try {
20 | expected = fs.readFileSync(path).toString();
21 | } catch (err) {
22 | if (err.code !== 'ENOENT') {
23 | throw err;
24 | }
25 | }
26 | return expected;
27 | }
28 |
29 | function transform(path, opts = {}) {
30 | return transformFileSync(path, {plugins: [[require('../src').default, opts]]}).code;
31 | }
32 |
33 | function parse(json) {
34 | return json ? JSON.parse(json) : {};
35 | }
36 |
37 | function trim(str) {
38 | return str.replace(/^\s+|\s+$/, "");
39 | }
40 |
41 | describe("turn jsx into mithril compliant virtual-dom", () => {
42 | const fixturesDir = path.join(__dirname, 'fixtures');
43 | const original_log = console.log.bind(console);
44 |
45 | fs.readdirSync(fixturesDir).map(caseName => {
46 | it(`should transform ${caseName.split('-').join(' ')}`, () => {
47 | const fixtureDir = path.join(fixturesDir, caseName);
48 | const expected = resolve(path.join(fixtureDir, 'expected.js'));
49 | const opts = parse(resolve(path.join(fixtureDir, 'options.json')));
50 | const throwMsg = opts.throws;
51 | const consoleMsg = opts.console;
52 |
53 | let actual = null;
54 | let logMsgs = [];
55 | function log(msg) {
56 | logMsgs.push(msg);
57 | }
58 |
59 | try {
60 | console.log = log;
61 | actual = transform(path.join(fixtureDir, 'actual.js'), opts.opts || {});
62 | console.log = original_log;
63 | }
64 | catch (err) {
65 | if (throwMsg) {
66 | if (err.message.indexOf(throwMsg) >= 0) {
67 | return;
68 | }
69 | else {
70 | err.message = `Expected error message: ${throwMsg}. Got error message: ${err.message}`;
71 | }
72 | }
73 | throw err;
74 | }
75 |
76 | if (throwMsg) {
77 | throw new Error(`Expected error message: ${throwMsg}. But parsing succeeded.`);
78 | }
79 | else {
80 | assert.equal(trim(actual), trim(expected));
81 | }
82 | if (consoleMsg) {
83 | assert.equal(consoleMsg.length, logMsgs.length);
84 | for (let i = 0; i < consoleMsg.length; i++) {
85 | let expectedMsg = consoleMsg[i];
86 | let actualMsg = logMsgs[i];
87 | assert.equal(expectedMsg, actualMsg);
88 | }
89 | }
90 | });
91 | });
92 | });
93 |
--------------------------------------------------------------------------------