From 13fffaa55d97a7e9a05d93468eed1429d8bdadb5 Mon Sep 17 00:00:00 2001 From: avim Date: Thu, 4 Dec 2014 14:40:54 +0200 Subject: [PATCH] support React 0.12 & React 0.10/0.11 syntax and dom elements --- package.json | 2 +- src/reactDOMSupport.js | 19 ++++++++++++++++++ src/reactTemplates.js | 45 +++++++++++++++++++++++++++++++----------- test/data/div.rt.js | 2 +- test/data/repeat.rt.js | 5 +++-- test/data/test.rt.js | 8 ++++---- test/src/test.js | 8 ++++---- 7 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 src/reactDOMSupport.js diff --git a/package.json b/package.json index fc76888..125d007 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "escodegen": "^1.4.1", "esprima": "^1.2.2", "lodash": "^2.4.1", - "react": "^0.12.0", "text-table": "^0.2.0" }, "devDependencies": { @@ -39,6 +38,7 @@ "grunt-contrib-watch": "^0.6.1", "grunt-eslint": "^2.1.0", "grunt-node-tap": "^0.1.61", + "react": "^0.12.0", "tape": "^3.0.2" } } diff --git a/src/reactDOMSupport.js b/src/reactDOMSupport.js new file mode 100644 index 0000000..0d5392c --- /dev/null +++ b/src/reactDOMSupport.js @@ -0,0 +1,19 @@ +/** + * Created by avim on 12/4/2014. + */ +var ver0_12_0 = ["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","defs","ellipse","g","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan"]; +var ver0_11_2 = ["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","defs","ellipse","g","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan","injection"]; +var ver0_11_0 = ["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","defs","ellipse","g","line","linearGradient","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan","injection"]; +var ver0_10_0 = ["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","defs","g","line","linearGradient","path","polygon","polyline","radialGradient","rect","stop","svg","text","injection"]; + + +var versions = { + "0.12.1": ver0_12_0, + "0.12.0": ver0_12_0, + "0.11.2": ver0_11_2, + "0.11.1": ver0_11_0, + "0.11.0": ver0_11_0, + "0.10.0": ver0_10_0 +}; + +module.exports = versions; \ No newline at end of file diff --git a/src/reactTemplates.js b/src/reactTemplates.js index fbe4943..1d51169 100644 --- a/src/reactTemplates.js +++ b/src/reactTemplates.js @@ -6,7 +6,7 @@ var cheerio = require('cheerio'); var _ = require('lodash'); var esprima = require('esprima'); var escodegen = require('escodegen'); -var React = require('react/addons'); +var reactDOMSupport = require('./reactDOMSupport'); var stringUtils = require('./stringUtils'); var repeatTemplate = _.template('_.map(<%= collection %>,<%= repeatFunction %>.bind(<%= repeatBinds %>))'); @@ -15,6 +15,8 @@ var propsTemplate = _.template('_.merge({}, <%= generatedProps %>, <%= rtProps % var classSetTemplate = _.template('React.addons.classSet(<%= classSet %>)'); var simpleTagTemplate = _.template('<%= name %>(<%= props %><%= children %>)'); var tagTemplate = _.template('<%= name %>.apply(this,_.flatten([<%= props %><%= children %>]))'); +var simpleTagTemplateCreateElement = _.template('React.createElement(<%= name %>,<%= props %><%= children %>)'); +var tagTemplateCreateElement = _.template('React.createElement.apply(this,_.flatten([<%= name %>,<%= props %><%= children %>]))'); var commentTemplate = _.template(' /* <%= data %> */ '); var templateAMDTemplate = _.template("/*eslint new-cap:0,no-unused-vars:0*/\ndefine([<%= requirePaths %>], function (<%= requireNames %>) {\n'use strict';\n <%= injectedFunctions %>\nreturn function(){ return <%= body %>};\n});"); var templateCommonJSTemplate = _.template("<%= vars %>\n\n'use strict';\n <%= injectedFunctions %>\nmodule.exports = function(){ return <%= body %>};\n"); @@ -25,6 +27,20 @@ var classSetProp = 'rt-class'; var scopeProp = 'rt-scope'; var propsProp = 'rt-props'; +var defaultOptions = {commonJS: false, version: false, force: false, format: 'stylish', targetVersion: '0.12.1'}; + +function shouldUseCreateElement(context) { + switch(context.options.targetVersion) { + case "0.11.2": + case "0.11.1": + case "0.11.0": + case "0.10.0": + return false; + default: + return true; + } +} + var reactSupportedAttributes = ['accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', 'alt', 'async', 'autoComplete', 'autoPlay', 'cellPadding', 'cellSpacing', 'charSet', 'checked', 'classID', 'className', 'cols', 'colSpan', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'defer', 'dir', 'disabled', 'download', 'draggable', 'encType', 'form', 'formNoValidate', 'frameBorder', 'height', 'hidden', 'href', 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'label', 'lang', 'list', 'loop', 'manifest', 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'multiple', 'muted', 'name', 'noValidate', 'open', 'pattern', 'placeholder', 'poster', 'preload', 'radioGroup', 'readOnly', 'rel', 'required', 'role', 'rows', 'rowSpan', 'sandbox', 'scope', 'scrolling', 'seamless', 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcSet', 'start', 'step', 'style', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', 'wmode']; var attributesMapping = {'class': 'className', 'rt-class': 'className'}; _.forEach(reactSupportedAttributes, function (attributeReactName) { @@ -210,15 +226,20 @@ function generateProps(node, context) { }).join(',') + '}'; } -function convertTagNameToConstructor(tagName) { - return React.DOM.hasOwnProperty(tagName) ? 'React.DOM.' + tagName : tagName; +function convertTagNameToConstructor(tagName, context) { + var isHtmlTag = _.contains(reactDOMSupport[context.options.targetVersion], tagName); + if (shouldUseCreateElement(context)) { + return isHtmlTag ? "'"+tagName + "'": tagName; + } + return isHtmlTag ? 'React.DOM.' + tagName : tagName; } -function defaultContext() { +function defaultContext(html,options) { return { boundParams: [], injectedFunctions: [], - html: '' + html: html, + options: options }; } @@ -233,10 +254,11 @@ function convertHtmlToReact(node, context) { context = { boundParams: _.clone(context.boundParams), injectedFunctions: context.injectedFunctions, - html: context.html + html: context.html, + options: context.options }; - var data = {name: convertTagNameToConstructor(node.name)}; + var data = {name: convertTagNameToConstructor(node.name, context)}; if (node.attribs[scopeProp]) { data.scopeMapping = {}; data.scopeName = ''; @@ -277,9 +299,9 @@ function convertHtmlToReact(node, context) { })); if (hasNonSimpleChildren(node)) { - data.body = tagTemplate(data); + data.body = shouldUseCreateElement(context)?tagTemplateCreateElement(data):tagTemplate(data); } else { - data.body = simpleTagTemplate(data); + data.body = shouldUseCreateElement(context)?simpleTagTemplateCreateElement(data):simpleTagTemplate(data); } if (node.attribs[templateProp]) { @@ -331,11 +353,10 @@ function extractDefinesFromJSXTag(html, defines) { */ function convertTemplateToReact(html, options) { var rootNode = cheerio.load(html, {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true, withStartIndices: true}); - options = options || {}; + options = _.defaults({},options,defaultOptions); var defines = {'react/addons': 'React', lodash: '_'}; html = extractDefinesFromJSXTag(html, defines); - var context = defaultContext(); - context.html = html; + var context = defaultContext(html, options); var rootTags = _.filter(rootNode.root()[0].children, function (i) { return i.type === 'tag'; }); if (!rootTags || rootTags.length === 0) { throw new RTCodeError('Document should have a root element'); diff --git a/test/data/div.rt.js b/test/data/div.rt.js index 7cfbead..c6b4303 100644 --- a/test/data/div.rt.js +++ b/test/data/div.rt.js @@ -5,6 +5,6 @@ define([ ], function (React, _) { 'use strict'; return function () { - return React.DOM.div({}); + return React.createElement('div', {}); }; }); \ No newline at end of file diff --git a/test/data/repeat.rt.js b/test/data/repeat.rt.js index 51a845e..c34d481 100644 --- a/test/data/repeat.rt.js +++ b/test/data/repeat.rt.js @@ -13,7 +13,7 @@ define([ return false; } function repeatItems3(items, itemsIndex) { - return React.DOM.div({}, React.DOM.span({ + return React.createElement('div', {}, React.createElement('span', { 'style': { width: 'auto', lineHeight: '5px' @@ -23,7 +23,8 @@ define([ }, 'Mock')); } return function () { - return React.DOM.p.apply(this, _.flatten([ + return React.createElement.apply(this, _.flatten([ + 'p', {}, _.map(this.props.things, repeatItems3.bind(this)) ])); diff --git a/test/data/test.rt.js b/test/data/test.rt.js index 6f4531c..13bd2f9 100644 --- a/test/data/test.rt.js +++ b/test/data/test.rt.js @@ -5,20 +5,20 @@ define([ ], function (React, _) { 'use strict'; return function () { - return React.DOM.div({}, React.DOM.div({ + return React.createElement('div', {}, React.createElement('div', { 'style': { position: 'relative', textAlign: 'center', top: this.props.config.previewTop, height: this.props.config.previewHeight } - }, React.DOM.div({ + }, React.createElement('div', { 'style': { margin: 'auto', height: '100%', width: this.props.config.previewWidth || '100%' } - }, React.DOM.iframe({ + }, React.createElement('iframe', { 'id': 'preview', 'src': 'http://localhost/sites/412?ds=true', 'style': { @@ -26,6 +26,6 @@ define([ height: '100%', border: '0' } - }))), React.DOM.div({}, 'editor\n ', !this.props.editorState.previewMode ? React.DOM.div({}, 'left bar') : null)); + }))), React.createElement('div', {}, 'editor\n ', !this.props.editorState.previewMode ? React.createElement('div', {}, 'left bar') : null)); }; }); \ No newline at end of file diff --git a/test/src/test.js b/test/src/test.js index 4f22eb9..75b0c5e 100644 --- a/test/src/test.js +++ b/test/src/test.js @@ -4,7 +4,7 @@ var reactTemplates = require('../../src/reactTemplates'); var fs = require('fs'); var _ = require('lodash'); var path = require('path'); -var React = require('react'); +var React = require('react/addons'); var cheerio = require('cheerio'); var dataPath = path.resolve(__dirname, '..', 'data'); @@ -19,7 +19,7 @@ test('invalid tests', function (t) { function check(testFile) { var filename = path.join(dataPath, testFile.file); - var html = fs.readFileSync(filename).toString(); + var html = fs.readFileSync(filename).toString().replace(/\r/g, '').trim(); var error = null; try { reactTemplates.convertTemplateToReact(html); @@ -47,7 +47,7 @@ test('conversion test', function (t) { function check(testFile) { var filename = path.join(dataPath, testFile); - var html = fs.readFileSync(filename).toString(); + var html = fs.readFileSync(filename).toString().replace(/\r/g, '').trim(); var expected = fs.readFileSync(filename + '.js').toString().replace(/\r/g, '').trim(); // var expected = fs.readFileSync(filename.replace(".html", ".js")).toString(); var actual = reactTemplates.convertTemplateToReact(html).replace(/\r/g, '').trim(); @@ -77,7 +77,7 @@ test('html tests', function (t) { var expected = fs.readFileSync(filename + '.html').toString().replace(/\r/g, ''); // var expected = fs.readFileSync(filename.replace(".html", ".js")).toString(); var code = reactTemplates.convertTemplateToReact(html).replace(/\r/g, ''); - var defineMap = {react: React, lodash: _}; + var defineMap = {"react/addons": React, lodash: _}; var define = function (requirementsNames, content) { var requirements = _.map(requirementsNames, function (reqName) { return defineMap[reqName];