From 9d38cbb828bc95b8a51510cfc84acaec2ef830b2 Mon Sep 17 00:00:00 2001 From: Avi Marcus Date: Thu, 4 Feb 2016 16:21:34 +0200 Subject: [PATCH] added rt-include, not in playground as the only interesting use case for playground is with url support and that requires changing the entire code to be async --- src/api.js | 1 + src/fsUtil.js | 13 ++++++- src/reactTemplates.js | 81 ++++++++++++++++++++++++--------------- test/data/include.rt | 3 ++ test/data/include.rt.html | 1 + test/src/test.js | 9 ++++- 6 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 test/data/include.rt create mode 100644 test/data/include.rt.html diff --git a/src/api.js b/src/api.js index 6baa75e..e157e66 100644 --- a/src/api.js +++ b/src/api.js @@ -41,6 +41,7 @@ function convertFile(source, target, options, context) { if (shouldAddName) { 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); if (!options.dryRun) { fs.writeFileSync(target, js); diff --git a/src/fsUtil.js b/src/fsUtil.js index a69ed08..4cffa63 100644 --- a/src/fsUtil.js +++ b/src/fsUtil.js @@ -1,5 +1,6 @@ 'use strict'; var fs = require('fs'); +var path = require('path'); /** * @param {string} source @@ -15,6 +16,14 @@ function isStale(source, target) { 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 = { - isStale: isStale -}; \ No newline at end of file + isStale: isStale, + createRelativeReadFileSync: createRelativeReadFileSync +}; diff --git a/src/reactTemplates.js b/src/reactTemplates.js index 1e052a8..25c4745 100644 --- a/src/reactTemplates.js +++ b/src/reactTemplates.js @@ -52,6 +52,8 @@ var scopeAttr = 'rt-scope'; var propsAttr = 'rt-props'; var templateNode = 'rt-template'; var virtualNode = 'rt-virtual'; +var includeNode = 'rt-include'; +var includeSrcAttr = 'src'; /** * @param {Options} options @@ -304,12 +306,17 @@ function convertTagNameToConstructor(tagName, context) { * @param options * @return {Context} */ -function defaultContext(html, options) { +function defaultContext(html, options, reportContext) { + var defaultDefines = {}; + defaultDefines[options.reactImportPath] = 'React'; + defaultDefines[options.lodashImportPath] = '_'; return { boundParams: [], injectedFunctions: [], html: html, - options: options + options: options, + defines: options.defines ? _.clone(options.defines) : defaultDefines, + reportContext: reportContext }; } @@ -328,12 +335,27 @@ function hasNonSimpleChildren(node) { */ function convertHtmlToReact(node, context) { if (node.type === 'tag' || node.type === 'style') { - context = { - boundParams: _.clone(context.boundParams), - injectedFunctions: context.injectedFunctions, - html: context.html, - options: context.options - }; + context = _.defaults({ + boundParams: _.clone(context.boundParams) + }, context); + + 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)}; @@ -490,24 +512,9 @@ function convertTemplateToReact(html, options) { return convertRT(html, context, options); } -/** - * @param {string} html - * @param {CONTEXT} reportContext - * @param {Options?} options - * @return {string} - */ -function convertRT(html, reportContext, options) { +function parseAndConvertHtmlToReact(html, context) { var rootNode = cheerio.load(html, {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true, withStartIndices: true}); - options = getOptions(options); - - 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]); + utils.validate(context.options, context, context.reportContext, rootNode.root()[0]); var rootTags = _.filter(rootNode.root()[0].children, isTag); rootTags = handleSelfClosingHtmlTags(rootTags); if (!rootTags || rootTags.length === 0) { @@ -521,7 +528,7 @@ function convertRT(html, reportContext, options) { } else if (tag.children.length) { 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) { firstTag = tag; } else { @@ -533,8 +540,22 @@ function convertRT(html, reportContext, options) { } else if (firstTag.name === virtualNode) { throw RTCodeError.build(context, firstTag, `Document should not have <${virtualNode}> as root element`); } - var body = convertHtmlToReact(firstTag, context); - var requirePaths = _(defines) + return convertHtmlToReact(firstTag, context); +} + +/** + * @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() .map(def => `"${def}"`) .join(','); @@ -547,8 +568,8 @@ function convertRT(html, reportContext, options) { buildImport = (v, p) => `var ${v} = require('${p}');` } const header = options.flow ? '/* @flow */\n' : ''; - const vars = header + _(defines).map(buildImport).join('\n'); - var data = {body, injectedFunctions: context.injectedFunctions.join('\n'), requireNames: _.values(defines).join(','), requirePaths, vars, name: options.name}; + const vars = header + _(context.defines).map(buildImport).join('\n'); + var data = {body, injectedFunctions: context.injectedFunctions.join('\n'), requireNames: _.values(context.defines).join(','), requirePaths, vars, name: options.name}; var code = generate(data, options); if (options.modules !== 'typescript' && options.modules !== 'jsrt') { code = parseJS(code); diff --git a/test/data/include.rt b/test/data/include.rt new file mode 100644 index 0000000..9552782 --- /dev/null +++ b/test/data/include.rt @@ -0,0 +1,3 @@ +
+ +
diff --git a/test/data/include.rt.html b/test/data/include.rt.html new file mode 100644 index 0000000..8fda287 --- /dev/null +++ b/test/data/include.rt.html @@ -0,0 +1 @@ +
diff --git a/test/src/test.js b/test/src/test.js index a3cf90f..5745aab 100644 --- a/test/src/test.js +++ b/test/src/test.js @@ -3,6 +3,7 @@ var test = require('tape'); var reactTemplates = require('../../src/reactTemplates'); var context = require('../../src/context'); var util = require('./util'); +var fsUtil = require('../../src/fsUtil'); var readFileNormalized = util.readFileNormalized; var compareAndWrite = util.compareAndWrite; var fs = require('fs'); @@ -203,7 +204,8 @@ test('html tests', function (t) { "scope-evaluated-after-repeat2.rt", "scope-evaluated-after-if.rt", "scope-obj.rt", - "repeat-literal-collection.rt" + "repeat-literal-collection.rt", + "include.rt" ]; t.plan(files.length); @@ -211,12 +213,15 @@ test('html tests', function (t) { function check(testFile) { var filename = path.join(dataPath, testFile); + var options = { + readFileSync: fsUtil.createRelativeReadFileSync(filename) + } var code = ''; try { var html = fs.readFileSync(filename).toString(); var expected = readFileNormalized(filename + '.html'); // 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); actual = util.normalizeHtml(actual); expected = util.normalizeHtml(expected);