├── .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 | [![Build Status](https://travis-ci.org/Naddiseo/babel-plugin-mjsx.svg?branch=master)](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 =
2 |
3 |
; 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 | --------------------------------------------------------------------------------