Merge remote-tracking branch 'origin/gh-pages' into gh-pages
This commit is contained in:
commit
f30ca08076
35
README.md
35
README.md
|
@ -7,7 +7,7 @@
|
||||||
Lightweight templates for [React](http://facebook.github.io/react/index.html).
|
Lightweight templates for [React](http://facebook.github.io/react/index.html).
|
||||||
|
|
||||||
* No runtime libraries. No magic. Simply precompile your way to clear React code.
|
* No runtime libraries. No magic. Simply precompile your way to clear React code.
|
||||||
* Easy syntax that's similar to HTML, supported by most IDEs.
|
* Easy syntax that's similar to HTML, surpported by most IDEs.
|
||||||
* Clear separation of presentation and logic - almost zero HTML in component files.
|
* Clear separation of presentation and logic - almost zero HTML in component files.
|
||||||
* Declarative coding ensures that the HTML that you write and the HTML you inspect look nearly identical.
|
* Declarative coding ensures that the HTML that you write and the HTML you inspect look nearly identical.
|
||||||
* Supports AMD, CommonJS, ES6, Typescript and globals.
|
* Supports AMD, CommonJS, ES6, Typescript and globals.
|
||||||
|
@ -44,8 +44,9 @@ http://plugins.jetbrains.com/plugin/7648
|
||||||
* [rt-scope](#rt-scope)
|
* [rt-scope](#rt-scope)
|
||||||
* [rt-props](#rt-props)
|
* [rt-props](#rt-props)
|
||||||
* [rt-class](#rt-class)
|
* [rt-class](#rt-class)
|
||||||
* [rt-require](#rt-require)
|
* [rt-require](#rt-require-and-using-other-components-in-the-template)
|
||||||
* [rt-template](#rt-template)
|
* [rt-template](#rt-template-and-defining-properties-template-functions)
|
||||||
|
* [rt-include](#rt-include)
|
||||||
* [styles](#styles)
|
* [styles](#styles)
|
||||||
* [event handlers](#event-handlers)
|
* [event handlers](#event-handlers)
|
||||||
|
|
||||||
|
@ -269,6 +270,34 @@ define([
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## rt-include
|
||||||
|
Optionally choose to extract static contents out of rt files.<br>
|
||||||
|
rt-include is a "macro" that takes a text file (e.g svg/html/xml) and injects it into the file as if it was part of the original markup.
|
||||||
|
|
||||||
|
###### Sample:
|
||||||
|
given `main.rt`:
|
||||||
|
```html
|
||||||
|
<div>
|
||||||
|
<rt-include src="./my-icon.svg" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
and `my-icon.svg`:
|
||||||
|
```html
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect height="50" width="50" style="fill: #00f"/>
|
||||||
|
</svg>
|
||||||
|
```
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
```html
|
||||||
|
<div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect height="50" width="50" style="fill: #00f"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
## style
|
## style
|
||||||
React templates allow the settings of styles inline in HTML, optionally returning an object from the evaluation context. By default, style names will be converted from hyphen-style to camelCase-style naming.
|
React templates allow the settings of styles inline in HTML, optionally returning an object from the evaluation context. By default, style names will be converted from hyphen-style to camelCase-style naming.
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ function convertFile(source, target, options, context) {
|
||||||
if (shouldAddName) {
|
if (shouldAddName) {
|
||||||
options.name = reactTemplates.normalizeName(path.basename(source, path.extname(source))) + 'RT';
|
options.name = reactTemplates.normalizeName(path.basename(source, path.extname(source))) + 'RT';
|
||||||
}
|
}
|
||||||
|
options.readFileSync = fsUtil.createRelativeReadFileSync(source);
|
||||||
var js = options.modules === 'jsrt' ? convertJSRTToJS(html, context, options) : convertRT(html, context, options);
|
var js = options.modules === 'jsrt' ? convertJSRTToJS(html, context, options) : convertRT(html, context, options);
|
||||||
if (!options.dryRun) {
|
if (!options.dryRun) {
|
||||||
fs.writeFileSync(target, js);
|
fs.writeFileSync(target, js);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} source
|
* @param {string} source
|
||||||
|
@ -15,6 +16,14 @@ function isStale(source, target) {
|
||||||
return sourceTime.getTime() > targetTime.getTime();
|
return sourceTime.getTime() > targetTime.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createRelativeReadFileSync(baseFile) {
|
||||||
|
var basePath = path.dirname(baseFile);
|
||||||
|
return function(filename) {
|
||||||
|
return fs.readFileSync(path.resolve(basePath, filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isStale: isStale
|
isStale: isStale,
|
||||||
|
createRelativeReadFileSync: createRelativeReadFileSync
|
||||||
};
|
};
|
|
@ -52,6 +52,10 @@ var scopeAttr = 'rt-scope';
|
||||||
var propsAttr = 'rt-props';
|
var propsAttr = 'rt-props';
|
||||||
var templateNode = 'rt-template';
|
var templateNode = 'rt-template';
|
||||||
var virtualNode = 'rt-virtual';
|
var virtualNode = 'rt-virtual';
|
||||||
|
var includeNode = 'rt-include';
|
||||||
|
var includeSrcAttr = 'src';
|
||||||
|
|
||||||
|
var reactTemplatesSelfClosingTags = [includeNode];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Options} options
|
* @param {Options} options
|
||||||
|
@ -304,12 +308,17 @@ function convertTagNameToConstructor(tagName, context) {
|
||||||
* @param options
|
* @param options
|
||||||
* @return {Context}
|
* @return {Context}
|
||||||
*/
|
*/
|
||||||
function defaultContext(html, options) {
|
function defaultContext(html, options, reportContext) {
|
||||||
|
var defaultDefines = {};
|
||||||
|
defaultDefines[options.reactImportPath] = 'React';
|
||||||
|
defaultDefines[options.lodashImportPath] = '_';
|
||||||
return {
|
return {
|
||||||
boundParams: [],
|
boundParams: [],
|
||||||
injectedFunctions: [],
|
injectedFunctions: [],
|
||||||
html: html,
|
html: html,
|
||||||
options: options
|
options: options,
|
||||||
|
defines: options.defines ? _.clone(options.defines) : defaultDefines,
|
||||||
|
reportContext: reportContext
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,12 +337,27 @@ function hasNonSimpleChildren(node) {
|
||||||
*/
|
*/
|
||||||
function convertHtmlToReact(node, context) {
|
function convertHtmlToReact(node, context) {
|
||||||
if (node.type === 'tag' || node.type === 'style') {
|
if (node.type === 'tag' || node.type === 'style') {
|
||||||
context = {
|
context = _.defaults({
|
||||||
boundParams: _.clone(context.boundParams),
|
boundParams: _.clone(context.boundParams)
|
||||||
injectedFunctions: context.injectedFunctions,
|
}, context);
|
||||||
html: context.html,
|
|
||||||
options: context.options
|
if (node.type === 'tag' && node.name === includeNode) {
|
||||||
};
|
var srcFile = node.attribs[includeSrcAttr];
|
||||||
|
if (!srcFile) {
|
||||||
|
throw RTCodeError.build(context, node, 'rt-include must supply a source attribute');
|
||||||
|
}
|
||||||
|
if (!context.options.readFileSync) {
|
||||||
|
throw RTCodeError.build(context, node, 'rt-include needs a readFileSync polyfill on options');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var newHtml = context.options.readFileSync(srcFile);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
throw RTCodeError.build(context, node, `rt-include failed to read file '${srcFile}'`);
|
||||||
|
}
|
||||||
|
context.html = newHtml;
|
||||||
|
return parseAndConvertHtmlToReact(newHtml, context);
|
||||||
|
}
|
||||||
|
|
||||||
var data = {name: convertTagNameToConstructor(node.name, context)};
|
var data = {name: convertTagNameToConstructor(node.name, context)};
|
||||||
|
|
||||||
|
@ -474,7 +498,8 @@ function handleSelfClosingHtmlTags(nodes) {
|
||||||
.map(function (node) {
|
.map(function (node) {
|
||||||
var externalNodes = [];
|
var externalNodes = [];
|
||||||
node.children = handleSelfClosingHtmlTags(node.children);
|
node.children = handleSelfClosingHtmlTags(node.children);
|
||||||
if (node.type === 'tag' && _.includes(reactSupport.htmlSelfClosingTags, node.name)) {
|
if (node.type === 'tag' && (_.includes(reactSupport.htmlSelfClosingTags, node.name) ||
|
||||||
|
_.includes(reactTemplatesSelfClosingTags, node.name))) {
|
||||||
externalNodes = _.filter(node.children, isTag);
|
externalNodes = _.filter(node.children, isTag);
|
||||||
_.forEach(externalNodes, i => i.parent = node);
|
_.forEach(externalNodes, i => i.parent = node);
|
||||||
node.children = _.reject(node.children, isTag);
|
node.children = _.reject(node.children, isTag);
|
||||||
|
@ -490,24 +515,9 @@ function convertTemplateToReact(html, options) {
|
||||||
return convertRT(html, context, options);
|
return convertRT(html, context, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function parseAndConvertHtmlToReact(html, context) {
|
||||||
* @param {string} html
|
|
||||||
* @param {CONTEXT} reportContext
|
|
||||||
* @param {Options?} options
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function convertRT(html, reportContext, options) {
|
|
||||||
var rootNode = cheerio.load(html, {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true, withStartIndices: true});
|
var rootNode = cheerio.load(html, {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true, withStartIndices: true});
|
||||||
options = getOptions(options);
|
utils.validate(context.options, context, context.reportContext, rootNode.root()[0]);
|
||||||
|
|
||||||
var defaultDefines = {};
|
|
||||||
defaultDefines[options.reactImportPath] = 'React';
|
|
||||||
defaultDefines[options.lodashImportPath] = '_';
|
|
||||||
|
|
||||||
var defines = options.defines ? _.clone(options.defines) : defaultDefines;
|
|
||||||
|
|
||||||
var context = defaultContext(html, options);
|
|
||||||
utils.validate(options, context, reportContext, rootNode.root()[0]);
|
|
||||||
var rootTags = _.filter(rootNode.root()[0].children, isTag);
|
var rootTags = _.filter(rootNode.root()[0].children, isTag);
|
||||||
rootTags = handleSelfClosingHtmlTags(rootTags);
|
rootTags = handleSelfClosingHtmlTags(rootTags);
|
||||||
if (!rootTags || rootTags.length === 0) {
|
if (!rootTags || rootTags.length === 0) {
|
||||||
|
@ -521,7 +531,7 @@ function convertRT(html, reportContext, options) {
|
||||||
} else if (tag.children.length) {
|
} else if (tag.children.length) {
|
||||||
throw RTCodeError.build(context, tag, 'rt-require may have no children');
|
throw RTCodeError.build(context, tag, 'rt-require may have no children');
|
||||||
}
|
}
|
||||||
defines[tag.attribs.dependency] = tag.attribs.as;
|
context.defines[tag.attribs.dependency] = tag.attribs.as;
|
||||||
} else if (firstTag === null) {
|
} else if (firstTag === null) {
|
||||||
firstTag = tag;
|
firstTag = tag;
|
||||||
} else {
|
} else {
|
||||||
|
@ -533,8 +543,22 @@ function convertRT(html, reportContext, options) {
|
||||||
} else if (firstTag.name === virtualNode) {
|
} else if (firstTag.name === virtualNode) {
|
||||||
throw RTCodeError.build(context, firstTag, `Document should not have <${virtualNode}> as root element`);
|
throw RTCodeError.build(context, firstTag, `Document should not have <${virtualNode}> as root element`);
|
||||||
}
|
}
|
||||||
var body = convertHtmlToReact(firstTag, context);
|
return convertHtmlToReact(firstTag, context);
|
||||||
var requirePaths = _(defines)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} html
|
||||||
|
* @param {CONTEXT} reportContext
|
||||||
|
* @param {Options?} options
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
function convertRT(html, reportContext, options) {
|
||||||
|
options = getOptions(options);
|
||||||
|
|
||||||
|
var context = defaultContext(html, options, reportContext);
|
||||||
|
var body = parseAndConvertHtmlToReact(html, context);
|
||||||
|
|
||||||
|
var requirePaths = _(context.defines)
|
||||||
.keys()
|
.keys()
|
||||||
.map(def => `"${def}"`)
|
.map(def => `"${def}"`)
|
||||||
.join(',');
|
.join(',');
|
||||||
|
@ -547,8 +571,8 @@ function convertRT(html, reportContext, options) {
|
||||||
buildImport = (v, p) => `var ${v} = require('${p}');`
|
buildImport = (v, p) => `var ${v} = require('${p}');`
|
||||||
}
|
}
|
||||||
const header = options.flow ? '/* @flow */\n' : '';
|
const header = options.flow ? '/* @flow */\n' : '';
|
||||||
const vars = header + _(defines).map(buildImport).join('\n');
|
const vars = header + _(context.defines).map(buildImport).join('\n');
|
||||||
var data = {body, injectedFunctions: context.injectedFunctions.join('\n'), requireNames: _.values(defines).join(','), requirePaths, vars, name: options.name};
|
var data = {body, injectedFunctions: context.injectedFunctions.join('\n'), requireNames: _.values(context.defines).join(','), requirePaths, vars, name: options.name};
|
||||||
var code = generate(data, options);
|
var code = generate(data, options);
|
||||||
if (options.modules !== 'typescript' && options.modules !== 'jsrt') {
|
if (options.modules !== 'typescript' && options.modules !== 'jsrt') {
|
||||||
code = parseJS(code);
|
code = parseJS(code);
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div>
|
||||||
|
<rt-include src="className.rt">
|
||||||
|
<span>test</span>
|
||||||
|
</div>
|
|
@ -0,0 +1 @@
|
||||||
|
<div><div class="a"></div><span>test</span></div>
|
|
@ -3,6 +3,7 @@ var test = require('tape');
|
||||||
var reactTemplates = require('../../src/reactTemplates');
|
var reactTemplates = require('../../src/reactTemplates');
|
||||||
var context = require('../../src/context');
|
var context = require('../../src/context');
|
||||||
var util = require('./util');
|
var util = require('./util');
|
||||||
|
var fsUtil = require('../../src/fsUtil');
|
||||||
var readFileNormalized = util.readFileNormalized;
|
var readFileNormalized = util.readFileNormalized;
|
||||||
var compareAndWrite = util.compareAndWrite;
|
var compareAndWrite = util.compareAndWrite;
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
@ -203,7 +204,8 @@ test('html tests', function (t) {
|
||||||
"scope-evaluated-after-repeat2.rt",
|
"scope-evaluated-after-repeat2.rt",
|
||||||
"scope-evaluated-after-if.rt",
|
"scope-evaluated-after-if.rt",
|
||||||
"scope-obj.rt",
|
"scope-obj.rt",
|
||||||
"repeat-literal-collection.rt"
|
"repeat-literal-collection.rt",
|
||||||
|
"include.rt"
|
||||||
];
|
];
|
||||||
t.plan(files.length);
|
t.plan(files.length);
|
||||||
|
|
||||||
|
@ -211,12 +213,15 @@ test('html tests', function (t) {
|
||||||
|
|
||||||
function check(testFile) {
|
function check(testFile) {
|
||||||
var filename = path.join(dataPath, testFile);
|
var filename = path.join(dataPath, testFile);
|
||||||
|
var options = {
|
||||||
|
readFileSync: fsUtil.createRelativeReadFileSync(filename)
|
||||||
|
}
|
||||||
var code = '';
|
var code = '';
|
||||||
try {
|
try {
|
||||||
var html = fs.readFileSync(filename).toString();
|
var html = fs.readFileSync(filename).toString();
|
||||||
var expected = readFileNormalized(filename + '.html');
|
var expected = readFileNormalized(filename + '.html');
|
||||||
// var expected = fs.readFileSync(filename.replace(".html", ".js")).toString();
|
// var expected = fs.readFileSync(filename.replace(".html", ".js")).toString();
|
||||||
code = reactTemplates.convertTemplateToReact(html).replace(/\r/g, '');
|
code = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '');
|
||||||
var actual = util.codeToHtml(code);
|
var actual = util.codeToHtml(code);
|
||||||
actual = util.normalizeHtml(actual);
|
actual = util.normalizeHtml(actual);
|
||||||
expected = util.normalizeHtml(expected);
|
expected = util.normalizeHtml(expected);
|
||||||
|
|
Loading…
Reference in New Issue