New syntax for rt-require: "import" "from" (#125)

* New syntaxt for rt-require import="" from=""

* Added warning message for obsolete syntax

* Allow to import multiple names from the same module

* Added tests for rt-require new import syntax

* Removed unneded comment

* Separated rt-require into rt-require and rt-import

* Tests for rt-require and rt-import
This commit is contained in:
nino-porcino 2016-05-15 12:58:24 +02:00 committed by Omer Ganim
parent b2483bd2de
commit c495a02452
23 changed files with 264 additions and 30 deletions

View File

@ -55,10 +55,49 @@ const templates = {
jsrt: templateJSRTTemplate
};
function buildImportTypeScript(d) { /* eslint-disable no-else-return */
if (d.member === '*') {
return `import ${d.alias} = require('${d.moduleName}');`;
} else {
return `import ${d.alias} = require('${d.moduleName}').${d.member};`;
}
/* eslint-enable */
}
function buildImportES6(d) { /* eslint-disable no-else-return */
if (d.member === '*') {
return `import * as ${d.alias} from '${d.moduleName}';`;
} else if (d.member === 'default') {
return `import ${d.alias} from '${d.moduleName}';`;
} else {
return `import { ${d.member} as ${d.alias} } from '${d.moduleName}';`;
}
/* eslint-enable */
}
function buildImportCommonJS(d) { /* eslint-disable no-else-return */
if (d.member === '*') {
return `var ${d.alias} = require('${d.moduleName}');`;
} else {
return `var ${d.alias} = require('${d.moduleName}').${d.member};`;
}
/* eslint-enable */
}
const buildImport = {
typescript: buildImportTypeScript,
es6: buildImportES6,
commonjs: buildImportCommonJS,
amd: buildImportCommonJS,
none: buildImportCommonJS,
jsrt: buildImportCommonJS
};
module.exports = {
htmlSelfClosingTags,
attributesMapping,
classNameProp,
shouldUseCreateElement,
templates
templates,
buildImport
};

View File

@ -53,6 +53,8 @@ const templateNode = 'rt-template';
const virtualNode = 'rt-virtual';
const includeNode = 'rt-include';
const includeSrcAttr = 'src';
const requireAttr = 'rt-require';
const importAttr = 'rt-import';
const reactTemplatesSelfClosingTags = [includeNode];
@ -256,9 +258,10 @@ function convertTagNameToConstructor(tagName, context) {
* @return {Context}
*/
function defaultContext(html, options, reportContext) {
const defaultDefines = {};
defaultDefines[options.reactImportPath] = 'React';
defaultDefines[options.lodashImportPath] = '_';
const defaultDefines = [
{moduleName: options.reactImportPath, alias: 'React', member: '*'},
{moduleName: options.lodashImportPath, alias: '_', member: '*'}
];
return {
boundParams: [],
injectedFunctions: [],
@ -464,6 +467,48 @@ function handleSelfClosingHtmlTags(nodes) {
});
}
function handleRequire(tag, context) {
let moduleName;
let alias;
let member;
if (tag.children.length) {
throw RTCodeError.build(context, tag, `'${requireAttr}' may have no children`);
} else if (tag.attribs.dependency && tag.attribs.as) {
moduleName = tag.attribs.dependency;
member = '*';
alias = tag.attribs.as;
}
if (!moduleName) {
throw RTCodeError.build(context, tag, `'${requireAttr}' needs 'dependency' and 'as' attributes`);
}
context.defines.push({moduleName, member, alias});
}
function handleImport(tag, context) {
let moduleName;
let alias;
let member;
if (tag.children.length) {
throw RTCodeError.build(context, tag, `'${importAttr}' may have no children`);
} else if (tag.attribs.name && tag.attribs.from) {
moduleName = tag.attribs.from;
member = tag.attribs.name;
alias = tag.attribs.as;
if (!alias) {
if (member === '*') {
throw RTCodeError.build(context, tag, "'*' imports must have an 'as' attribute");
} else if (member === 'default') {
throw RTCodeError.build(context, tag, "default imports must have an 'as' attribute");
}
alias = member;
}
}
if (!moduleName) {
throw RTCodeError.build(context, tag, `'${importAttr}' needs 'name' and 'from' attributes`);
}
context.defines.push({moduleName, member, alias});
}
function convertTemplateToReact(html, options) {
const context = require('./context');
return convertRT(html, context, options);
@ -484,13 +529,10 @@ function parseAndConvertHtmlToReact(html, context) {
}
let firstTag = null;
_.forEach(rootTags, tag => {
if (tag.name === 'rt-require') {
if (!tag.attribs.dependency || !tag.attribs.as) {
throw RTCodeError.build(context, tag, "rt-require needs 'dependency' and 'as' attributes");
} else if (tag.children.length) {
throw RTCodeError.build(context, tag, 'rt-require may have no children');
}
context.defines[tag.attribs.dependency] = tag.attribs.as;
if (tag.name === requireAttr) {
handleRequire(tag, context);
} else if (tag.name === importAttr) {
handleImport(tag, context);
} else if (firstTag === null) {
firstTag = tag;
} else {
@ -517,24 +559,16 @@ function convertRT(html, reportContext, options) {
const context = defaultContext(html, options, reportContext);
const body = parseAndConvertHtmlToReact(html, context);
const requirePaths = _(context.defines)
.keys()
.map(def => `"${def}"`)
.join(',');
let buildImport;
if (options.modules === 'typescript') {
buildImport = (v, p) => `import ${v} = require('${p}');`;
} else if (options.modules === 'es6') { // eslint-disable-line
buildImport = (v, p) => `import ${v} from '${p}';`;
} else {
buildImport = (v, p) => `var ${v} = require('${p}');`;
}
const requirePaths = _.map(context.defines, d => `"${d.moduleName}"`).join(',');
const requireNames = _.map(context.defines, d => `${d.alias}`).join(',');
const buildImport = reactSupport.buildImport[options.modules] || reactSupport.buildImport.commonjs;
const requires = _.map(context.defines, buildImport).join('\n');
const header = options.flow ? '/* @flow */\n' : '';
const vars = header + _(context.defines).map(buildImport).join('\n');
const vars = header + requires;
const data = {
body,
injectedFunctions: context.injectedFunctions.join('\n'),
requireNames: _.values(context.defines).join(','),
requireNames,
requirePaths,
vars,
name: options.name

View File

@ -72,6 +72,10 @@ function validate(options, context, reportContext, node) {
const loc = rtError.getNodeLoc(context, node);
reportContext.warn('rt-if without a key', options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end);
}
if (node.type === 'tag' && node.attribs['rt-require'] && (node.attribs.dependency || node.attribs.as)) {
const loc = rtError.getNodeLoc(context, node);
reportContext.warn("'rt-require' is obsolete, use 'rt-import' instead", options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end);
}
if (node.children) {
node.children.forEach(validate.bind(this, options, context, reportContext));
}

View File

@ -1,5 +1,5 @@
import React from 'react/addons';
import _ from 'lodash';
import * as React from 'react/addons';
import * as _ from 'lodash';
export default function () {
return React.createElement('div', {});
}

6
test/data/import.rt Normal file
View File

@ -0,0 +1,6 @@
<rt-import name="member" from="module-name"/>
<rt-import name="member" as="alias2" from="module-name"/>
<rt-import name="*" as="alias3" from="module-name"/>
<rt-import name="default" as="alias4" from="module-name"/>
<div>
</div>

View File

@ -0,0 +1,13 @@
define('div', [
'react/addons',
'lodash',
'module-name',
'module-name',
'module-name',
'module-name'
], function (React, _, member, alias2, alias3, alias4) {
'use strict';
return function () {
return React.createElement('div', {});
};
});

View File

@ -0,0 +1,10 @@
'use strict';
var React = require('react/addons');
var _ = require('lodash');
var member = require('module-name').member;
var alias2 = require('module-name').member;
var alias3 = require('module-name');
var alias4 = require('module-name').default;
module.exports = function () {
return React.createElement('div', {});
};

View File

@ -0,0 +1,9 @@
import * as React from 'react/addons';
import * as _ from 'lodash';
import { member } from 'module-name';
import { member as alias2 } from 'module-name';
import * as alias3 from 'module-name';
import alias4 from 'module-name';
export default function () {
return React.createElement('div', {});
}

View File

@ -0,0 +1,3 @@
var div = function () {
return React.createElement('div', {});
};

11
test/data/import.rt.js Normal file
View File

@ -0,0 +1,11 @@
define([
'react/addons',
'lodash',
'comps/myComp',
'utils/utils'
], function (React, _, myComp, utils) {
'use strict';
return function () {
return React.createElement(myComp, {}, '\n', utils.translate('Hello', 'es'), '\n');
};
});

View File

@ -0,0 +1,10 @@
import React = require('react/addons');
import _ = require('lodash');
import member = require('module-name').member;
import alias2 = require('module-name').member;
import alias3 = require('module-name');
import alias4 = require('module-name').default;
var fn = function() { return React.createElement('div',{}) };
export = fn

View File

@ -0,0 +1,3 @@
<rt-import name="*" from="module" />
<div>
</div>

View File

@ -0,0 +1,3 @@
<rt-import name="default" from="module" />
<div>
</div>

View File

@ -0,0 +1,3 @@
<rt-import />
<div>
</div>

View File

@ -0,0 +1,5 @@
<rt-require>
42
</rt-require>
<div>
</div>

View File

@ -0,0 +1,11 @@
define('div', [
'react/addons',
'lodash',
'comps/myComp',
'utils/utils'
], function (React, _, myComp, utils) {
'use strict';
return function () {
return React.createElement(myComp, {}, '\n', utils.translate('Hello', 'es'), '\n');
};
});

View File

@ -0,0 +1,8 @@
'use strict';
var React = require('react/addons');
var _ = require('lodash');
var myComp = require('comps/myComp');
var utils = require('utils/utils');
module.exports = function () {
return React.createElement(myComp, {}, '\n', utils.translate('Hello', 'es'), '\n');
};

View File

@ -0,0 +1,7 @@
import * as React from 'react/addons';
import * as _ from 'lodash';
import * as myComp from 'comps/myComp';
import * as utils from 'utils/utils';
export default function () {
return React.createElement(myComp, {}, '\n', utils.translate('Hello', 'es'), '\n');
}

View File

@ -0,0 +1,3 @@
var div = function () {
return React.createElement(myComp, {}, '\n', utils.translate('Hello', 'es'), '\n');
};

View File

@ -0,0 +1,8 @@
import React = require('react/addons');
import _ = require('lodash');
import myComp = require('comps/myComp');
import utils = require('utils/utils');
var fn = function() { return React.createElement(myComp,{},"\n",(utils.translate('Hello','es')),"\n") };
export = fn

View File

@ -23,7 +23,11 @@ module.exports = {
{file: 'invalid-js.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 0, 32, 1, 1)},
{file: 'invalid-single-root.rt', issue: new RTCodeError('Document should have no more than a single root element', 12, 23, 2, 1)},
{file: 'invalid-repeat.rt', issue: new RTCodeError('rt-repeat invalid \'in\' expression \'a in b in c\'', 0, 35, 1, 1)},
{file: 'invalid-rt-require.rt', issue: new RTCodeError("rt-require needs 'dependency' and 'as' attributes", 0, 14, 1, 1)},
{file: 'invalid-rt-require-1.rt', issue: new RTCodeError("'rt-require' needs 'dependency' and 'as' attributes", 0, 14, 1, 1)},
{file: 'invalid-rt-require-2.rt', issue: new RTCodeError("'rt-require' may have no children", 0, 32, 1, 1)},
{file: 'invalid-rt-import-1.rt', issue: new RTCodeError("'*' imports must have an 'as' attribute", 0, 36, 1, 1)},
{file: 'invalid-rt-import-2.rt', issue: new RTCodeError("default imports must have an 'as' attribute", 0, 42, 1, 1)},
{file: 'invalid-rt-import-3.rt', issue: new RTCodeError("'rt-import' needs 'name' and 'from' attributes", 0, 13, 1, 1)},
{file: 'invalid-brace.rt', issue: new RTCodeError('Unexpected end of input', 128, 163, 5, 11)},
{file: 'invalid-style.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 10, 39, 2, 5)},
{file: 'invalid-virtual.rt', issue: new RTCodeError('Document should not have <rt-virtual> as root element', 0, 60, 1, 1)}

View File

@ -29,7 +29,7 @@ module.exports = {
});
test('conversion test', t => {
const files = ['div.rt', 'test.rt', 'repeat.rt', 'inputs.rt', 'require.rt', 'virtual.rt'];
const files = ['div.rt', 'test.rt', 'repeat.rt', 'inputs.rt', 'virtual.rt'];
testFiles(t, files);
});
@ -63,7 +63,7 @@ module.exports = {
{source: 'div.rt', expected: 'div.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'div.rt', expected: 'div.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.es6.js', options: {modules: 'es6', name: 'div'}},
{source: 'div.rt', expected: 'div.rt.es6.js', options: {modules: 'es6'}},
{source: 'div.rt', expected: 'div.rt.typescript.ts', options: {modules: 'typescript'}}
];
t.plan(files.length);
@ -78,6 +78,46 @@ module.exports = {
}
});
test('rt-require with all module types', t => {
const files = [
{source: 'require.rt', expected: 'require.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'require.rt', expected: 'require.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'require.rt', expected: 'require.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'require.rt', expected: 'require.rt.es6.js', options: {modules: 'es6'}},
{source: 'require.rt', expected: 'require.rt.typescript.ts', options: {modules: 'typescript'}}
];
t.plan(files.length);
files.forEach(check);
function check(testData) {
const filename = path.join(dataPath, testData.source);
const html = readFileNormalized(filename);
const expected = readFileNormalized(path.join(dataPath, testData.expected));
const actual = reactTemplates.convertTemplateToReact(html, testData.options).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
});
test('rt-import with all module types', t => {
const files = [
{source: 'import.rt', expected: 'import.rt.commonjs.js', options: {modules: 'commonjs'}},
{source: 'import.rt', expected: 'import.rt.amd.js', options: {modules: 'amd', name: 'div'}},
{source: 'import.rt', expected: 'import.rt.globals.js', options: {modules: 'none', name: 'div'}},
{source: 'import.rt', expected: 'import.rt.es6.js', options: {modules: 'es6'}},
{source: 'import.rt', expected: 'import.rt.typescript.ts', options: {modules: 'typescript'}}
];
t.plan(files.length);
files.forEach(check);
function check(testData) {
const filename = path.join(dataPath, testData.source);
const html = readFileNormalized(filename);
const expected = readFileNormalized(path.join(dataPath, testData.expected));
const actual = reactTemplates.convertTemplateToReact(html, testData.options).replace(/\r/g, '').trim();
compareAndWrite(t, actual, expected, filename);
}
});
test('convert jsrt and test source results', t => {
const files = ['simple.jsrt'];
t.plan(files.length);