UNPKG

riot-compiler

Version:

Compiler for riot .tag files

862 lines (670 loc) 20.6 kB
// istanbul ignore next function safeRegex (re) { var src = re.source var opt = re.global ? 'g' : '' if (re.ignoreCase) opt += 'i' if (re.multiline) opt += 'm' for (var i = 1; i < arguments.length; i++) { src = src.replace('@', '\\' + arguments[i]) } return new RegExp(src, opt) } /** * @module parsers */ var parsers = (function (win) { var _p = {} function _r (name) { var parser = win[name] if (parser) return parser throw new Error('Parser "' + name + '" not loaded.') } function _req (name) { var parts = name.split('.') if (parts.length !== 2) throw new Error('Bad format for parsers._req') var parser = _p[parts[0]][parts[1]] if (parser) return parser throw new Error('Parser "' + name + '" not found.') } function extend (obj, props) { if (props) { for (var prop in props) { /* istanbul ignore next */ if (props.hasOwnProperty(prop)) { obj[prop] = props[prop] } } } return obj } function renderPug (compilerName, html, opts, url) { opts = extend({ pretty: true, filename: url, doctype: 'html' }, opts) return _r(compilerName).render(html, opts) } _p.html = { jade: function (html, opts, url) { /* eslint-disable */ console.log('DEPRECATION WARNING: jade was renamed "pug" - The jade parser will be removed in riot@3.0.0!') /* eslint-enable */ return renderPug('jade', html, opts, url) }, pug: function (html, opts, url) { return renderPug('pug', html, opts, url) } } _p.css = { less: function (tag, css, opts, url) { var ret opts = extend({ sync: true, syncImport: true, filename: url }, opts) _r('less').render(css, opts, function (err, result) { // istanbul ignore next if (err) throw err ret = result.css }) return ret } } _p.js = { es6: function (js, opts, url) { // eslint-disable-line no-unused-vars return _r('Babel').transform( // eslint-disable-line js, extend({ plugins: [ ['transform-es2015-template-literals', { loose: true }], 'transform-es2015-literals', 'transform-es2015-function-name', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', ['transform-es2015-classes', { loose: true }], 'transform-es2015-object-super', 'transform-es2015-shorthand-properties', 'transform-es2015-duplicate-keys', ['transform-es2015-computed-properties', { loose: true }], ['transform-es2015-for-of', { loose: true }], 'transform-es2015-sticky-regex', 'transform-es2015-unicode-regex', 'check-es2015-constants', ['transform-es2015-spread', { loose: true }], 'transform-es2015-parameters', ['transform-es2015-destructuring', { loose: true }], 'transform-es2015-block-scoping', 'transform-es2015-typeof-symbol', ['transform-es2015-modules-commonjs', { allowTopLevelThis: true }], ['transform-regenerator', { async: false, asyncGenerators: false }] ] }, opts )).code }, buble: function (js, opts, url) { opts = extend({ source: url, modules: false }, opts) return _r('buble').transform(js, opts).code }, coffee: function (js, opts) { return _r('CoffeeScript').compile(js, extend({ bare: true }, opts)) }, livescript: function (js, opts) { return _r('livescript').compile(js, extend({ bare: true, header: false }, opts)) }, typescript: function (js, opts) { return _r('typescript')(js, opts) }, none: function (js) { return js } } _p.js.javascript = _p.js.none _p.js.coffeescript = _p.js.coffee _p._req = _req _p.utils = { extend: extend } return _p })(window || global) var S_SQ_STR = /'[^'\n\r\\]*(?:\\(?:\r\n?|[\S\s])[^'\n\r\\]*)*'/.source var S_R_SRC1 = [ /\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//.source, '//.*', S_SQ_STR, S_SQ_STR.replace(/'/g, '"'), '([/`])' ].join('|') var S_R_SRC2 = `${S_R_SRC1.slice(0, -2)}{}])` function skipES6str (code, start, stack) { var re = /[`$\\]/g re.lastIndex = start while (re.exec(code)) { var end = re.lastIndex var c = code[end - 1] if (c === '`') { return end } if (c === '$' && code[end] === '{') { stack.push('`', '}') return end + 1 } re.lastIndex++ } throw new Error('Unclosed ES6 template') } function jsSplitter (code, start) { var re1 = new RegExp(S_R_SRC1, 'g') var re2 var skipRegex = brackets.skipRegex var offset = start | 0 var result = [[]] var stack = [] var re = re1 var lastPos = re.lastIndex = offset var str, ch, idx, end, match while ((match = re.exec(code))) { idx = match.index end = re.lastIndex str = '' ch = match[1] if (ch) { if (ch === '{') { stack.push('}') } else if (ch === '}') { if (stack.pop() !== ch) { throw new Error("Unexpected '}'") } else if (stack[stack.length - 1] === '`') { ch = stack.pop() } } else if (ch === '/') { end = skipRegex(code, idx) if (end > idx + 1) { str = code.slice(idx, end) } } if (ch === '`') { end = skipES6str(code, end, stack) str = code.slice(idx, end) if (stack.length) { re = re2 || (re2 = new RegExp(S_R_SRC2, 'g')) } else { re = re1 } } } else { str = match[0] if (str[0] === '/') { str = str[1] === '*' ? ' ' : '' code = code.slice(offset, idx) + str + code.slice(end) end = idx + str.length str = '' } else if (str.length === 2) { str = '' } } if (str) { result[0].push(code.slice(lastPos, idx)) result.push(str) lastPos = end } re.lastIndex = end } result[0].push(code.slice(lastPos)) return result } riot.parsers = parsers /** * Compiler for riot custom tags * @version v3.6.0 */ var compile = (function () { var extend = parsers.utils.extend /* eslint-enable */ var S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source var S_STRINGS = brackets.R_STRINGS.source var HTML_ATTRS = / *([-\w:\xA0-\xFF]+) ?(?:= ?('[^']*'|"[^"]*"|\S+))?/g var HTML_COMMS = RegExp(/<!--(?!>)[\S\s]*?-->/.source + '|' + S_LINESTR, 'g') var HTML_TAGS = /<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^"'/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"/>]*)*)|\s*)(\/?)>/g var HTML_PACK = />[ \t]+<(-?[A-Za-z]|\/[-A-Za-z])/g var RIOT_ATTRS = ['style', 'src', 'd', 'value'] var VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/ var PRE_TAGS = /<pre(?:\s+(?:[^">]*|"[^"]*")*)?>([\S\s]+?)<\/pre\s*>/gi var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i var IMPORT_STATEMENT = /^\s*import(?!\w|(\s)?\()(?:(?:\s|[^\s'"])*)['|"].*\n?/gm var TRIM_TRAIL = /[ \t]+$/gm var RE_HASEXPR = safeRegex(/@#\d/, 'x01'), RE_REPEXPR = safeRegex(/@#(\d+)/g, 'x01'), CH_IDEXPR = '\x01#', CH_DQCODE = '\u2057', DQ = '"', SQ = "'" function cleanSource (src) { var mm, re = HTML_COMMS if (src.indexOf('\r') !== 1) { src = src.replace(/\r\n?/g, '\n') } re.lastIndex = 0 while ((mm = re.exec(src))) { if (mm[0][0] === '<') { src = RegExp.leftContext + RegExp.rightContext re.lastIndex = mm[3] + 1 } } return src } function parseAttribs (str, pcex) { var list = [], match, type, vexp HTML_ATTRS.lastIndex = 0 str = str.replace(/\s+/g, ' ') while ((match = HTML_ATTRS.exec(str))) { var k = match[1].toLowerCase(), v = match[2] if (!v) { list.push(k) } else { if (v[0] !== DQ) { v = DQ + (v[0] === SQ ? v.slice(1, -1) : v) + DQ } if (k === 'type' && SPEC_TYPES.test(v)) { type = v } else { if (RE_HASEXPR.test(v)) { if (k === 'value') vexp = 1 if (RIOT_ATTRS.indexOf(k) !== -1) k = 'riot-' + k } list.push(k + '=' + v) } } } if (type) { if (vexp) type = DQ + pcex._bp[0] + SQ + type.slice(1, -1) + SQ + pcex._bp[1] + DQ list.push('type=' + type) } return list.join(' ') } function splitHtml (html, opts, pcex) { var _bp = pcex._bp if (html && _bp[4].test(html)) { var jsfn = opts.expr && (opts.parser || opts.type) ? _compileJS : 0, list = brackets.split(html, 0, _bp), expr for (var i = 1; i < list.length; i += 2) { expr = list[i] if (expr[0] === '^') { expr = expr.slice(1) } else if (jsfn) { expr = jsfn(expr, opts).trim() if (expr.slice(-1) === ';') expr = expr.slice(0, -1) } list[i] = CH_IDEXPR + (pcex.push(expr) - 1) + _bp[1] } html = list.join('') } return html } function restoreExpr (html, pcex) { if (pcex.length) { html = html.replace(RE_REPEXPR, function (_, d) { return pcex._bp[0] + pcex[d].trim().replace(/[\r\n]+/g, ' ').replace(/"/g, CH_DQCODE) }) } return html } function _compileHTML (html, opts, pcex) { if (!/\S/.test(html)) return '' html = splitHtml(html, opts, pcex) .replace(HTML_TAGS, function (_, name, attr, ends) { name = name.toLowerCase() ends = ends && !VOID_TAGS.test(name) ? '></' + name : '' if (attr) name += ' ' + parseAttribs(attr, pcex) return '<' + name + ends + '>' }) if (!opts.whitespace) { var p = [] if (/<pre[\s>]/.test(html)) { html = html.replace(PRE_TAGS, function (q) { p.push(q) return '\u0002' }) } html = html.trim().replace(/\s+/g, ' ') if (p.length) html = html.replace(/\u0002/g, function () { return p.shift() }) // eslint-disable-line } if (opts.compact) html = html.replace(HTML_PACK, '><$1') return restoreExpr(html, pcex).replace(TRIM_TRAIL, '') } function compileHTML (html, opts, pcex) { if (Array.isArray(opts)) { pcex = opts opts = {} } else { if (!pcex) pcex = [] if (!opts) opts = {} } pcex._bp = brackets.array(opts.brackets) return _compileHTML(cleanSource(html), opts, pcex) } var JS_ES6SIGN = /^[ \t]*(((?:async|\*)\s*)?([$_A-Za-z][$\w]*))\s*\([^()]*\)\s*{/m function riotjs (js) { var parts = [], match, toes5, pos, method, prefix, name, RE = RegExp const src = jsSplitter(js) js = src.shift().join('<%>') while ((match = js.match(JS_ES6SIGN))) { parts.push(RE.leftContext) js = RE.rightContext pos = skipBody(js) method = match[1] prefix = match[2] || '' name = match[3] toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(name) if (toes5) { name = match[0].replace(method, 'this.' + name + ' =' + prefix + ' function') } else { name = match[0] } parts.push(name, js.slice(0, pos)) js = js.slice(pos) if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)') } if (parts.length) { js = parts.join('') + js } if (src.length) { js = js.replace(/<%>/g, function () { return src.shift() }) } return js function skipBody (s) { var r = /[{}]/g var i = 1 while (i && r.exec(s)) { if (s[r.lastIndex - 1] === '{') ++i else --i } return i ? s.length : r.lastIndex } } function _compileJS (js, opts, type, parserOpts, url) { if (!/\S/.test(js)) return '' if (!type) type = opts.type var parser = opts.parser || type && parsers._req('js.' + type, true) || riotjs return parser(js, parserOpts, url).replace(/\r\n?/g, '\n').replace(TRIM_TRAIL, '') } function compileJS (js, opts, type, userOpts) { if (typeof opts === 'string') { userOpts = type type = opts opts = {} } if (type && typeof type === 'object') { userOpts = type type = '' } if (!userOpts) userOpts = {} return _compileJS(js, opts || {}, type, userOpts.parserOptions, userOpts.url) } var CSS_SELECTOR = RegExp('([{}]|^)[; ]*((?:[^@ ;{}][^{}]*)?[^@ ;{}:] ?)(?={)|' + S_LINESTR, 'g') function scopedCSS (tag, css) { var scope = ':scope' var selectorsBlacklist = ['from', 'to', ':host'] return css.replace(CSS_SELECTOR, function (m, p1, p2) { if (!p2) return m p2 = p2.replace(/[^,]+/g, function (sel) { var s = sel.trim() if (s.indexOf(tag) === 0) { return sel } if (!s || selectorsBlacklist.indexOf(s) > -1 || s.slice(-1) === '%') { return sel } if (s.indexOf(scope) < 0) { s = tag + ' ' + s + ',[data-is="' + tag + '"] ' + s } else { s = s.replace(scope, tag) + ',' + s.replace(scope, '[data-is="' + tag + '"]') } return s }) return p1 ? p1 + ' ' + p2 : p2 }) } function _compileCSS (css, tag, type, opts) { opts = opts || {} if (type) { if (type !== 'css') { var parser = parsers._req('css.' + type, true) css = parser(tag, css, opts.parserOpts || {}, opts.url) } } css = css.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim() if (tag && (!opts.parserOpts || opts.parserOpts.prefixCSS)) css = scopedCSS(tag, css) return css } function compileCSS (css, type, opts) { if (type && typeof type === 'object') { opts = type type = '' } else if (!opts) opts = {} return _compileCSS(css, opts.tagName, type, opts) } var TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i var MISC_ATTR = '\\s*=\\s*(' + S_STRINGS + '|{[^}]+}|\\S+)' var END_TAGS = /\/>\n|^<(?:\/?-?[A-Za-z][-\w\xA0-\xFF]*\s*|-?[A-Za-z][-\w\xA0-\xFF]*\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/ function _q (s, r) { if (!s) return "''" s = SQ + s.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + SQ return r && s.indexOf('\n') !== -1 ? s.replace(/\n/g, '\\n') : s } function mktag (name, html, css, attr, js, imports, opts) { var c = opts.debug ? ',\n ' : ', ', s = '});' if (js && js.slice(-1) !== '\n') s = '\n' + s return imports + 'riot.tag2(\'' + name + SQ + c + _q(html, 1) + c + _q(css) + c + _q(attr) + ', function(opts) {\n' + js + s } function splitBlocks (str) { if (/<[-\w]/.test(str)) { var m, k = str.lastIndexOf('<'), n = str.length while (k !== -1) { m = str.slice(k, n).match(END_TAGS) if (m) { k += m.index + m[0].length m = str.slice(0, k) if (m.slice(-5) === '<-/>\n') m = m.slice(0, -5) return [m, str.slice(k)] } n = k k = str.lastIndexOf('<', k - 1) } } return ['', str] } function getType (attribs) { if (attribs) { var match = attribs.match(TYPE_ATTR) match = match && (match[2] || match[3]) if (match) { return match.replace('text/', '') } } return '' } function getAttrib (attribs, name) { if (attribs) { var match = attribs.match(RegExp('\\s' + name + MISC_ATTR, 'i')) match = match && match[1] if (match) { return (/^['"]/).test(match) ? match.slice(1, -1) : match } } return '' } function unescapeHTML (str) { return str .replace(/&amp;/g, '&') .replace(/&lt;/g, '<') .replace(/&gt;/g, '>') .replace(/&quot;/g, '"') .replace(/&#039;/g, '\'') } function getParserOptions (attribs) { var opts = unescapeHTML(getAttrib(attribs, 'options')) return opts ? JSON.parse(opts) : null } function getCode (code, opts, attribs, base) { var type = getType(attribs), src = getAttrib(attribs, 'src'), jsParserOptions = extend({}, opts.parserOptions.js) if (src) return false return _compileJS( code, opts, type, extend(jsParserOptions, getParserOptions(attribs)), base ) } function cssCode (code, opts, attribs, url, tag) { var parserStyleOptions = extend({}, opts.parserOptions.style), extraOpts = { parserOpts: extend(parserStyleOptions, getParserOptions(attribs)), url: url } return _compileCSS(code, tag, getType(attribs) || opts.style, extraOpts) } function compileTemplate (html, url, lang, opts) { var parser = parsers._req('html.' + lang, true) return parser(html, opts, url) } var CUST_TAG = RegExp(/^([ \t]*)<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^'"/>]+(?:(?:@|\/[^>])[^'"/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/ .source.replace('@', S_STRINGS), 'gim'), SCRIPTS = /<script(\s+[^>]*)?>\n?([\S\s]*?)<\/script\s*>/gi, STYLES = /<style(\s+[^>]*)?>\n?([\S\s]*?)<\/style\s*>/gi function compile (src, opts, url) { var parts = [], included, output = src, defaultParserptions = { template: {}, js: {}, style: { prefixCSS: true } } if (!opts) opts = {} opts.parserOptions = extend(defaultParserptions, opts.parserOptions || {}) included = opts.exclude ? function (s) { return opts.exclude.indexOf(s) < 0 } : function () { return 1 } if (!url) url = '' var _bp = brackets.array(opts.brackets) if (opts.template) { output = compileTemplate(output, url, opts.template, opts.parserOptions.template) } output = cleanSource(output) .replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) { var jscode = '', styles = '', html = '', imports = '', pcex = [] pcex._bp = _bp tagName = tagName.toLowerCase() attribs = attribs && included('attribs') ? restoreExpr( parseAttribs( splitHtml(attribs, opts, pcex), pcex), pcex) : '' if ((body || (body = body2)) && /\S/.test(body)) { if (body2) { if (included('html')) html = _compileHTML(body2, opts, pcex) } else { body = body.replace(RegExp('^' + indent, 'gm'), '') body = body.replace(SCRIPTS, function (_m, _attrs, _script) { if (included('js')) { var code = getCode(_script, opts, _attrs, url) if (code) jscode += (jscode ? '\n' : '') + code } return '' }) body = body.replace(STYLES, function (_m, _attrs, _style) { if (included('css')) { styles += (styles ? ' ' : '') + cssCode(_style, opts, _attrs, url, tagName) } return '' }) var blocks = splitBlocks(body.replace(TRIM_TRAIL, '')) if (included('html')) { html = _compileHTML(blocks[0], opts, pcex) } if (included('js')) { body = _compileJS(blocks[1], opts, null, null, url) if (body) jscode += (jscode ? '\n' : '') + body jscode = jscode.replace(IMPORT_STATEMENT, function (s) { imports += s.trim() + '\n' return '' }) } } } jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : '' if (opts.entities) { parts.push({ tagName: tagName, html: html, css: styles, attribs: attribs, js: jscode, imports: imports }) return '' } return mktag(tagName, html, styles, attribs, jscode, imports, opts) }) if (opts.entities) return parts return output } riot.util.compiler = { compile: compile, html: compileHTML, css: compileCSS, js: compileJS, version: 'v3.6.0' } return compile })()