UNPKG

fis3-parser-rosetta

Version:
301 lines (238 loc) 8.38 kB
var cheerio = require('cheerio'); var jstransform = require('jstransform'); var esprima = require('esprima-fb'); var escodegen = require('escodegen'); var RosettaJsXVisitor = require('./visitors/rosetta-jsx-visitors.js'); var RegisterVisitor = require('./visitors/rosetta-register-visitors.js'); var shimShadowStyles = require('./css.js').shimShadowStyles; var rElement = /<element[^>]*>[\s\S]*<\/element>/i; var rXlang = /^text\/x\-(.*?)$/g; var rLink = /<!--(?!\[)([\s\S]*?)(?:-->|$)|(<link[^>]*(?:\/)?>)/ig; var rImport = /rel=('|")import\1/i; var rHref = /href=('|")(.*?)\1/i; var rClass = /class=('|")(.*?)\1/i; var rRElment = /(<(\w+\-\w+)[^>]*>)([\s\S]*?)(<\/\2>)/ig; function transform(content, file, options) { // element define if (rElement.test(content)) { // 把 template 包裹的内容当原始内容处理。 content = content.replace(/(<template[^>]*>)(?!<\!\[cdata\[)([\s\S]*?)(<\/template>)/ig, function(_, start, body, end) { return start + '<![CDATA[\n' + body + '\n]]>' + end; }); var $ = cheerio.load(content, { recognizeCDATA: true }); var element = $('element'); if (element.length > 1) { throw new Error('One single custom element is allowed here.'); } else if (element.parent().length) { throw new Error('Please keep `element` tag in the root node.'); } else if (!element.attr('name')) { throw new Error('Please set the value of `name` attribute of the `element`.') } else { var inlineScripts = []; element.children().each(function() { if (!~['link', 'style', 'template', 'script'].indexOf(this.name)) { throw new Error('Only following tags are allowed under the `element` tag: `link`, `style`, `template`, `script`') } else if (this.name === 'script' && !this.attribs.src) { inlineScripts.push(this); } return true; }); if (!inlineScripts.length) { throw new Error('In-line `script` tag is mandatory for an custom element.'); } else if (inlineScripts.length > 1) { throw new Error('Only single In-line `script` tag is allowed.'); } content = transformElement($, file, options); } } else { content = transformHTML(content, file, options); } return content; } function findShadowStyle(element, file) { var style = element.children('style'); if (style.length > 1) { throw new Error('Only single `style` tag is allowed under `element` tag.') } return style.length ? style : null; } function findElementScript(element, file) { var found = null; var name = element.attr('name'); element.children('script:not([src])').get().reverse().every(function(item) { item = cheerio(item); var content = item.text(); if (/\bRosetta\s*\(\s*{/i.test(content)) { found = item; item.remove(); return false; } return true; }); if (!found) { throw new Error('Can\'t find `Rosetta({...})` definition.') } return found; } function transformElement($, file, options) { var element = $('element'); var template = $('template'); var tmpl = template.length ? template.text().trim() : ''; var style = findShadowStyle(element, file); var f, content, type, ext; file.rosetta = element.attr('name'); if (style) { content = shimShadowStyles(style ? style.text() : '', element.attr('name')); type = style && style.attr('type'); ext = '.css'; rXlang.lastIndex = 0; if (type && rXlang.test(type)) { ext = '.' + RegExp.$1; } f = fis.file.wrap(file.realpathNoExt + ext); f.cache = file.cache; f.setContent(content); fis.compile.process(f); f.links.forEach(function(derived) { file.addLink(derived); }); file.derived.push(f); file.addRequire(f.getId()); } var script = findElementScript(element, file); content = script.text().trim(); tmpl && (tmpl = transformTemplate(tmpl, element, file, options)); content = transformScript(content, tmpl, element, file); $('link[rel=stylesheet][href], script[src]').each(function() { var filepath = this.attribs[this.name === 'link' ? 'href' : 'src']; var info = fis.project.lookup(filepath, file); $(this).remove(); file.addRequire(info.id); if (info.file) { file.addLink(info.file.subpath); } }); return content; } function transformHTML(content, file, options) { content = content.replace(rLink, function(all, comment, link) { if (comment) { return all; } if (rImport.test(link) && rHref.test(link)) { var href = RegExp.$2; var info = fis.project.lookup(href, file); if (info.file) { file.addRequire(info.id); file.addLink(info.file.subpath); } all = ''; } return all; }); var index = 0; var hash = fis.util.md5(file.subpath); if (options.compileUsage) { content = content.replace(rRElment, function(all, start, tag, body, end) { var opts = { alias: { 'Rosetta.create': 'create' } }; var transformed = jstransform.transform(RosettaJsXVisitor.visitorList, all, opts); var fn = transformed.code; index++; fn = '(function(render, create) {\n render(' + fn + ', "#rs-' + hash + '-' + index + '");\n})(Rosetta.render, Rosetta.create);'; // 走一遍 fis 内核编译。 fn = fis.compile.partial(fn, file, { ext: '.js', isJsLike: true }); return '<input type="hidden" id="rs-' + hash + '-' + index + '" />\n<script type="text/rosetta">\n' + fn + '\n</script>'; }); } else { content = content.replace(/(<(\w+\-\w+)[^>]*>)/ig, function(all, start, tag) { if (rClass.test(start)) { start = start.replace(rClass, function(all, quote, orgin) { return 'class=' + quote + (~orgin.indexOf('r-element') ? '' : 'r-element ') + (~orgin.indexOf('r-invisible') ? '' : 'r-invisible ') + orgin + quote; }); } else { start = start.replace(/<(\w+\-\w+)([^>]*)>/, function(all, tag, params) { return '<' + tag + params + ' class="r-element r-invisible">'; }); } return start; }); } return content; } function transformTemplate(content, element, file, options) { content = content.replace(rLink, function(all, comment, link) { if (comment) { return ''; } if (rImport.test(link) && rHref.test(link)) { var href = RegExp.$2; var info = fis.project.lookup(href, file); if (info.file) { file.addRequire(info.id); file.addLink(info.file.subpath); } all = ''; } return all; }).trim(); // template 部分为 html,在转成 js 前,先走一遍 html 编译 content = fis.compile.partial(content, file, { ext: '.part', isHtmlLike: true }); var wrapperTag = options.isNative ? 'View' : 'div'; var fn = 'function tmpl(tag, refs) { with(tag) { return (<' + wrapperTag + ' class={tag.type}>' + content + '</' + wrapperTag + '>);}};'; try { var transformed = jstransform.transform(RosettaJsXVisitor.visitorList, fn, { alias: { 'Rosetta.create': 'tag.create' } }); } catch(e) { // format var lines = fn.split(/\r\n|\n|\r/).map(function(line, index) { var lineNumber = ((index+1)/1000).toFixed(3).substring(2); if (index === e.lineNumber - 1) { lineNumber = lineNumber.red; } return lineNumber + ': ' + line; }); lines.splice(e.lineNumber, 0, fis.util.pad(' ', e.column + 4) + '^'.red); lines = lines.slice(Math.max(0, e.lineNumber - 5), e.lineNumber + 5); e.message = e.description; e.detail = '\n\n' + lines.join('\n') + '\n'; throw e; } fn = transformed.code; return fn; } function transformScript(content, tmpl, element, file) { var transformed = jstransform.transform(RegisterVisitor.visitorList, content, { tmpl: tmpl, element: element, name: element.attr('name'), moduleId: file.moduleId || file.id }); var content = transformed.code; if (!file.optimizer) { content = reIndent(content); } return content; } function reIndent(code) { var ast = esprima.parse(code); return escodegen.generate(ast); } // expose. module.exports = transform; transform.element = transformElement; transform.html = transformHTML;