riot-compiler
Advanced tools
Comparing version 2.3.13 to 2.3.16-beta
@@ -1,21 +0,50 @@ | ||
# COMPILER CHANGES | ||
# Compiler Changes | ||
## v2.3.12 | ||
### v2.3.16 | ||
- Regression of optimized regexes not working in IE9/10. | ||
- Fix #36 : removed the excluded strings from the ouput. | ||
- Fix: avoid changing the global brackets when the compiler is called with other brackets (requires riot-tmpl v2.3.17). | ||
- Preparation for recognize the raw-html flag `=` (can change in the final implementation). | ||
- A new property `version` (string) is included in the compiler set. | ||
### v2.3.15 (unpublished from npm) | ||
- Preparation for use as ES6 module through [rollup.js](http://rollupjs.org/) | ||
- Update devDependencies, including jspreproc v0.2.5 with an important fix. | ||
- Partial regression of fix [riot#1120](https://github.com/riot/riot/issues/1120), `tmpl` can parse double-quotes within expressions, encoding double-quotes generates issues. | ||
### v2.3.14 | ||
- The prefix `__` for boolean attributes is not used anymore. This IE8 hack and it is not neccessary for current supported versions. | ||
- Option `exclude` for ignore parts of the tag. This is an array with one or more of 'html', 'css', 'attribs', 'js'. | ||
- Removed `inert` from the boolean attributes list, this html5 attribute was dropped from the specs. | ||
- Fixed normalization of root attributes, was not working as expected. Example updated. | ||
### v2.3.13 | ||
- Fixed the `style` option for setting the CSS parser through the `options` object. | ||
- Fixed an issue in preservation of line endings in the generated html markup. | ||
- Fixed tests, coverage is 100% again. | ||
- Updated [doc/guide.md](https://github.com/riot/compiler/blob/master/doc/guide.md) and [doc/attributes.md](https://github.com/riot/compiler/blob/master/doc/attrbutes.md) with the latest features. | ||
### v2.3.12 | ||
- Gets rid of the zero-indentation restriction for custom tags, now you can indent these tags, but the opening and closing tag must have exactly the same indentation (length and type). All the tag will be unindented by this amount. | ||
- Support for `src` and `charset` attributes in `<script>` tags for reading JavaScript sources from the file system - [riot#507](https://github.com/riot/riot/issues/507) | ||
- Support for `src` and `charset` attributes in `<script>` tags for reading JavaScript sources from the file system - [riot#1116](https://github.com/riot/riot/issues/1116), [riot#507](https://github.com/riot/riot/issues/507) | ||
- The `compile` function can return separate parts by setting the new `entities` option. These parts has unescaped newlines. | ||
- New attribute `options` for `script` and `style` tags will append/overwrite attributes in the default configuration object of the parser at tag level. | ||
- Fix [riot#1261](https://github.com/riot/riot/issues/1261): `<pre>` tag does not preserve neither `\n` nor `\t`. | ||
- Fix [riot#1261](https://github.com/riot/riot/issues/1261) : `<pre>` tag does not preserve neither `\n` nor `\t`. | ||
Now whitespace within `<pre>` tags is always preserved. | ||
- Fix [riot#1358](https://github.com/riot/riot/issues/1358): Empty style in tag (scoped) breaks. | ||
- Fix [riot#1358](https://github.com/riot/riot/issues/1358) : Empty style in tag (scoped) breaks. | ||
## v2.3.11 | ||
### v2.3.11 | ||
- New type="babel" supports babel-core v6.x. You must `npm install babel-preset-es2015` too, for this works. | ||
Use type="es6" for babel and babel-core v5.8.x and bellow. | ||
- Fix [riot#1306](https://github.com/riot/riot/issues/1306): Compiler preserves newlines in class objects, causing "Unterminated String Constant" errors. | ||
- Fix [riot#1314](https://github.com/riot/riot/issues/1314): `settings.brackets` no longer works. | ||
Use type="es6" for babel and babel-core v5.8.x and bellow - [riot#1039](https://github.com/riot/riot/issues/1039) | ||
- Fix [riot#1306](https://github.com/riot/riot/issues/1306) : Compiler preserves newlines in class objects, causing "Unterminated String Constant" errors. | ||
- Fix [riot#1314](https://github.com/riot/riot/issues/1314) : `settings.brackets` no longer works. | ||
- Fix [riot#1309](https://github.com/riot/riot/issues/1309) : Tag renders js instead of content when no attributes present. | ||
## v2.3.0 | ||
### v2.3.0 | ||
@@ -29,9 +58,9 @@ This is a complete rewrite and the first solo version of the compiler. | ||
Outside of expressions, all backslashes are preserved. | ||
- Double quotes inside expressions are converted to `"`, to avoid issues with HTML markup | ||
- Double quotes inside expressions are converted to `"`, to avoid issues with HTML markup. | ||
- Fix [riot#1207](https://github.com/riot/riot/issues/1207) : Riot compiler/parser breaks indentation. | ||
- Fix [riot#1120](https://github.com/riot/riot/issues/1120) : Double quotes break Riot attributes | ||
- Fix [riot#1207](https://github.com/riot/riot/issues/1207): Riot compiler/parser breaks indentation. | ||
- Fix [riot#1120](https://github.com/riot/riot/issues/1120): Double quotes break Riot attributes | ||
Enhancements | ||
- The compiler loads the brackets in runtime on each tag, allowing use of different brackets. [riot#1122](https://github.com/riot/riot/issues/1122) related. | ||
- Multiple `<script>` blocks. These can have different types and are merged with the untagged script block, if any. | ||
@@ -41,5 +70,5 @@ - More flexible formats in ES6 style method definitions. | ||
- Better recognition of expressions. Now you can use almost any character, even in unquoted expressions (expressions containing the `>` operator needs to be enclosed in quotes) - [riot#744](https://github.com/riot/riot/issues/744) | ||
- If the first character inside an expression is `^`, the expression is not passed to any parser. This is some sort of type=none at expression level - [riot#543](https://github.com/riot/riot/issues/543) and [riot#1090](https://github.com/riot/riot/issues/1090) | ||
- If the first character inside an expression is `^`, the expression is not passed to any parser. This is some sort of type=none at expression level - [riot#543](https://github.com/riot/riot/issues/543), [riot#1090](https://github.com/riot/riot/issues/1090) | ||
- Type es6 now supports babel-core - [riot#1039](https://github.com/riot/riot/issues/1039) | ||
- New logic for scoped style blocks, if a style contains the ":scoped" selector, this is replaced by the name of the root element, if not, the name is prepended - [riot#912](https://github.com/riot/riot/issues/912) | ||
- `type="scoped-css"` for `style` tags is deprecated, use only `scoped` or `scoped="scoped"` |
@@ -1,154 +0,147 @@ | ||
/* riot-compiler v2.3.13, @license MIT, (c) 2015 Muut Inc. + contributors */ | ||
;(function (root, factory) { | ||
/* riot-compiler v2.3.16-beta, @license MIT, (c) 2015 Muut Inc. + contributors */ | ||
'use strict' // eslint-disable-line | ||
/* istanbul ignore else */ | ||
if (typeof module === 'object' && module.exports) { | ||
module.exports = factory(require('riot-tmpl')) | ||
} | ||
else if (typeof define === 'function' && define.amd) { | ||
define(['riot-tmpl'], factory) | ||
} | ||
else if (root) { | ||
root.compiler = factory(root.riot.util) | ||
} | ||
/** | ||
* @module parsers | ||
*/ | ||
var parsers = (function () { | ||
var _mods = {} | ||
})(this, function (_tmpl) { | ||
'use strict' // eslint-disable-line | ||
function _try(name, req) { //eslint-disable-line complexity | ||
var parser | ||
/** | ||
* @module parsers | ||
*/ | ||
var parsers = (function () { | ||
var _mods = {} | ||
function _try(name, req) { //eslint-disable-line complexity | ||
function fn(r) { | ||
try { | ||
_mods[name] = require(r) | ||
} | ||
catch (e) { | ||
_mods[name] = null | ||
} | ||
return _mods[name] | ||
function fn(r) { | ||
try { | ||
_mods[name] = require(r) | ||
} | ||
switch (name) { | ||
case 'es6': | ||
/* istanbul ignore next */ | ||
return fn('babel') || fn('babel-core') | ||
case 'babel': | ||
req = 'babel-core' | ||
break | ||
case 'none': | ||
case 'javascript': | ||
return _js.none | ||
case 'typescript': | ||
req = name + '-simple' | ||
break | ||
case 'coffee': | ||
case 'coffeescript': | ||
req = 'coffee-script' | ||
break | ||
/* istanbul ignore next */ | ||
case 'scss': | ||
case 'sass': | ||
req = 'node-sass' | ||
break | ||
default: | ||
if (!req) req = name | ||
break | ||
catch (e) { | ||
_mods[name] = null | ||
} | ||
return fn(req) | ||
return _mods[name] | ||
} | ||
function _req(name, req) { | ||
return name in _mods ? _mods[name] : _try(name, req) | ||
switch (name) { | ||
case 'es6': | ||
/* istanbul ignore next */ | ||
return fn('babel') || fn('babel-core') | ||
case 'babel': | ||
req = 'babel-core' | ||
break | ||
case 'none': | ||
case 'javascript': | ||
return _js.none | ||
case 'typescript': | ||
req = name + '-simple' | ||
break | ||
case 'coffee': | ||
case 'coffeescript': | ||
req = 'coffee-script' | ||
break | ||
case 'scss': | ||
case 'sass': | ||
req = 'node-sass' | ||
break | ||
default: | ||
if (!req) req = name | ||
break | ||
} | ||
parser = fn(req) | ||
var _html = { | ||
jade: function (html, opts) { | ||
return _req('jade').render(html, extend({pretty: true, doctype: 'html'}, opts)) | ||
} | ||
return parser | ||
} | ||
function _req(name, req) { | ||
return name in _mods ? _mods[name] : _try(name, req) | ||
} | ||
var _html = { | ||
jade: function (html, opts, url) { | ||
return _req('jade').render(html, extend({ | ||
pretty: true, | ||
filename: url, | ||
doctype: 'html' | ||
}, opts)) | ||
} | ||
} | ||
var _css = { | ||
sass: function(tag, css, opts) { | ||
var sass = _req('sass') | ||
var _css = { | ||
sass: function(tag, css, opts, url) { | ||
var sass = _req('sass') | ||
return sass.renderSync(extend({ | ||
data: css, | ||
indentedSyntax: true, | ||
omitSourceMapUrl: true, | ||
outputStyle: 'compact' | ||
}, opts)).css + '' | ||
}, | ||
scss: function(tag, css, opts) { | ||
var sass = _req('sass') | ||
return sass.renderSync(extend({ | ||
data: css, | ||
indentedSyntax: true, | ||
omitSourceMapUrl: true, | ||
outputStyle: 'compact' | ||
}, opts)).css + '' | ||
}, | ||
scss: function(tag, css, opts, url) { | ||
var sass = _req('sass') | ||
return sass.renderSync(extend({ | ||
data: css, | ||
indentedSyntax: false, | ||
omitSourceMapUrl: true, | ||
outputStyle: 'compact' | ||
}, opts)).css + '' | ||
}, | ||
less: function(tag, css, opts) { | ||
var less = _req('less'), | ||
ret | ||
return sass.renderSync(extend({ | ||
data: css, | ||
indentedSyntax: false, | ||
omitSourceMapUrl: true, | ||
outputStyle: 'compact' | ||
}, opts)).css + '' | ||
}, | ||
less: function(tag, css, opts, url) { | ||
var less = _req('less'), | ||
ret | ||
less.render(css, extend({ | ||
sync: true, | ||
compress: true | ||
}, opts), function (err, result) { | ||
// istanbul ignore next | ||
if (err) throw err | ||
ret = result.css | ||
}) | ||
return ret | ||
}, | ||
stylus: function (tag, css, opts) { | ||
var | ||
stylus = _req('stylus'), nib = _req('nib') | ||
/* istanbul ignore next: can't run both */ | ||
return nib ? | ||
stylus(css).use(nib()).import('nib').render() : stylus.render(css) | ||
} | ||
less.render(css, extend({ | ||
sync: true, | ||
compress: true | ||
}, opts), function (err, result) { | ||
// istanbul ignore next | ||
if (err) throw err | ||
ret = result.css | ||
}) | ||
return ret | ||
}, | ||
stylus: function (tag, css, opts, url) { | ||
var | ||
stylus = _req('stylus'), nib = _req('nib') | ||
/* istanbul ignore next: can't run both */ | ||
return nib ? | ||
stylus(css).use(nib()).import('nib').render() : stylus.render(css) | ||
} | ||
} | ||
var _js = { | ||
none: function (js, opts) { | ||
return js | ||
}, | ||
livescript: function (js, opts) { | ||
return _req('livescript').compile(js, extend({bare: true, header: false}, opts)) | ||
}, | ||
typescript: function (js, opts) { | ||
return _req('typescript')(js, opts).replace(/\r\n?/g, '\n') | ||
}, | ||
es6: function (js, opts) { | ||
return _req('es6').transform(js, extend({ | ||
blacklist: ['useStrict', 'strict', 'react'], sourceMaps: false, comments: false | ||
}, opts)).code | ||
}, | ||
babel: function (js, opts) { | ||
js = 'function __parser_babel_wrapper__(){' + js + '}' | ||
return _req('babel').transform(js, | ||
extend({ | ||
presets: ['es2015'] | ||
}, opts) | ||
).code.replace(/["']use strict["'];[\r\n]+/, '').slice(38, -2) | ||
}, | ||
coffee: function (js, opts) { | ||
return _req('coffee').compile(js, extend({bare: true}, opts)) | ||
} | ||
var _js = { | ||
none: function (js, opts, url) { | ||
return js | ||
}, | ||
livescript: function (js, opts, url) { | ||
return _req('livescript').compile(js, extend({bare: true, header: false}, opts)) | ||
}, | ||
typescript: function (js, opts, url) { | ||
return _req('typescript')(js, opts).replace(/\r\n?/g, '\n') | ||
}, | ||
es6: function (js, opts, url) { | ||
return _req('es6').transform(js, extend({ | ||
blacklist: ['useStrict', 'strict', 'react'], sourceMaps: false, comments: false | ||
}, opts)).code | ||
}, | ||
babel: function (js, opts, url) { | ||
return _req('babel').transform(js, | ||
extend({ | ||
filename: url | ||
}, opts) | ||
).code | ||
}, | ||
coffee: function (js, opts, url) { | ||
return _req('coffee').compile(js, extend({bare: true}, opts)) | ||
} | ||
} | ||
_js.javascript = _js.none | ||
_js.coffeescript = _js.coffee | ||
_js.javascript = _js.none | ||
_js.coffeescript = _js.coffee | ||
return {html: _html, css: _css, js: _js, _req: _req} | ||
return {html: _html, css: _css, js: _js, _req: _req} | ||
})() | ||
})() | ||
var brackets = require('riot-tmpl').brackets | ||
/** | ||
@@ -158,459 +151,478 @@ * @module compiler | ||
var brackets = _tmpl.brackets //eslint-disable-line no-redeclare | ||
// istanbul ignore next | ||
if (!brackets.version) { | ||
throw new Error('This compiler version requires riot-tmpl v2.3.16 or above') | ||
} | ||
function _regEx(str, opt) { return new RegExp(str, opt) } | ||
function _regEx(str, opt) { return new RegExp(str, opt) } | ||
var | ||
var | ||
BOOL_ATTRS = _regEx( | ||
'^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' + | ||
'compact|controls|default|formnovalidate|hidden|inert|ismap|itemscope|loop|' + | ||
'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' + | ||
'selected|sortable|truespeed|typemustmatch)$'), | ||
BOOL_ATTRS = _regEx( | ||
'^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' + | ||
'compact|controls|default|formnovalidate|hidden|ismap|itemscope|loop|' + | ||
'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' + | ||
'selected|sortable|truespeed|typemustmatch)$'), | ||
RIOT_ATTRS = ['style', 'src', 'd'], | ||
RIOT_ATTRS = ['style', 'src', 'd'], | ||
VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/, | ||
VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/, | ||
HTML_ATTR = /\s*([-\w:\.\xA0-\xFF]+)\s*(?:=\s*('[^']+'|"[^"]+"|\S+))?/g, | ||
HTML_ATTR = /\s*([-\w:\xA0-\xFF]+)\s*(?:=\s*('[^']+'|"[^"]+"|\S+))?/g, | ||
TRIM_TRAIL = /[ \t]+$/gm, | ||
TRIM_TRAIL = /[ \t]+$/gm | ||
_bp = null | ||
var path = require('path') | ||
var path = require('path') | ||
function q(s) { | ||
return "'" + (s ? s | ||
.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r') : | ||
'') + "'" | ||
} | ||
function q(s) { | ||
return "'" + (s ? s | ||
.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r') : | ||
'') + "'" | ||
} | ||
function mktag(name, html, css, attrs, js, pcex) { | ||
var | ||
c = ', ', | ||
s = '}' + (pcex.length ? ', ' + q(pcex._bp[8]) : '') + ');' | ||
function mktag(name, html, css, attrs, js, pcex) { | ||
var | ||
c = ', ', | ||
s = '}' + (pcex.length ? ', ' + q(_bp[8]) : '') + ');' | ||
if (js && js.slice(-1) !== '\n') s = '\n' + s | ||
if (js && js.slice(-1) !== '\n') s = '\n' + s | ||
return 'riot.tag2(\'' + name + "'" + c + q(html) + c + q(css) + c + q(attrs) + | ||
', function(opts) {\n' + js + s | ||
} | ||
return 'riot.tag2(\'' + name + "'" + c + q(html) + c + q(css) + c + q(attrs) + | ||
', function(opts) {\n' + js + s | ||
} | ||
function extend(obj, props) { | ||
for (var prop in props) { | ||
/* istanbul ignore next */ | ||
if (props.hasOwnProperty(prop)) { | ||
obj[prop] = props[prop] | ||
} | ||
function extend(obj, props) { | ||
for (var prop in props) { | ||
/* istanbul ignore next */ | ||
if (props.hasOwnProperty(prop)) { | ||
obj[prop] = props[prop] | ||
} | ||
return obj | ||
} | ||
return obj | ||
} | ||
function parseAttrs(str) { | ||
var | ||
list = [], | ||
match, | ||
k, v, | ||
DQ = '"' | ||
HTML_ATTR.lastIndex = 0 | ||
function parseAttrs(str, pcex) { | ||
var | ||
list = [], | ||
match, | ||
k, v, | ||
_bp = pcex._bp, | ||
DQ = '"' | ||
while (match = HTML_ATTR.exec(str)) { | ||
HTML_ATTR.lastIndex = 0 | ||
k = match[1].toLowerCase() | ||
v = match[2] | ||
str = str.replace(/\s+/g, ' ') | ||
if (!v) { | ||
list.push(k) | ||
} | ||
else { | ||
while (match = HTML_ATTR.exec(str)) { | ||
if (v[0] !== DQ) | ||
v = DQ + (v[0] === "'" ? v.slice(1, -1) : v) + DQ | ||
k = match[1].toLowerCase() | ||
v = match[2] | ||
if (k === 'type' && v.toLowerCase() === '"number"') { | ||
v = DQ + _bp[0] + "'number'" + _bp[1] + DQ | ||
} | ||
else if (/\u0001\d/.test(v)) { | ||
if (!v) { | ||
list.push(k) | ||
} | ||
else { | ||
if (BOOL_ATTRS.test(k)) { | ||
k = '__' + k | ||
} | ||
else if (~RIOT_ATTRS.indexOf(k)) { | ||
k = 'riot-' + k | ||
} | ||
if (v[0] !== DQ) | ||
v = DQ + (v[0] === "'" ? v.slice(1, -1) : v) + DQ | ||
if (k === 'type' && v.toLowerCase() === '"number"') { | ||
v = DQ + _bp[0] + "'number'" + _bp[1] + DQ | ||
} | ||
else if (/\u0001\d/.test(v)) { | ||
if (BOOL_ATTRS.test(k)) { | ||
k = '__' + k | ||
} | ||
else if (~RIOT_ATTRS.indexOf(k)) { | ||
k = 'riot-' + k | ||
} | ||
} | ||
list.push(k + '=' + v) | ||
} | ||
list.push(k + '=' + v) | ||
} | ||
return list.join(' ') | ||
} | ||
return list.join(' ') | ||
} | ||
function splitHtml(html, opts, pcex) { | ||
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), | ||
expr | ||
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) | ||
if (/;\s*$/.test(expr)) expr = expr.slice(0, expr.search(/;\s*$/)) | ||
} | ||
list[i] = '\u0001' + (pcex.push(expr.replace(/[\r\n]+/g, ' ').trim()) - 1) + _bp[1] | ||
for (var i = 1; i < list.length; i += 2) { | ||
expr = list[i] | ||
if (expr[0] === '^') | ||
expr = expr.slice(1) | ||
else if (jsfn) { | ||
var israw = expr[0] === '=' | ||
expr = jsfn(israw ? expr.slice(1) : expr, opts).trim() | ||
if (expr.slice(-1) === ';') expr = expr.slice(0, -1) | ||
if (israw) expr = '=' + expr | ||
} | ||
html = list.join('') | ||
list[i] = '\u0001' + (pcex.push(expr.replace(/[\r\n]+/g, ' ').trim()) - 1) + _bp[1] | ||
} | ||
return html | ||
html = list.join('') | ||
} | ||
return html | ||
} | ||
function restoreExpr(html, pcex) { | ||
if (pcex.length) { | ||
html = html | ||
.replace(/\u0001(\d+)/g, function (_, d) { | ||
return _bp[0] + pcex[d].replace(/"/g, '"') | ||
}) | ||
} | ||
return html | ||
function restoreExpr(html, pcex) { | ||
if (pcex.length) { | ||
html = html | ||
.replace(/\u0001(\d+)/g, function (_, d) { | ||
var expr = pcex[d] | ||
if (expr[0] === '=') { | ||
expr = expr.replace(brackets.R_STRINGS, function (qs) { | ||
return qs | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
}) | ||
} | ||
return pcex._bp[0] + expr | ||
}) | ||
} | ||
return html | ||
} | ||
var | ||
HTML_COMMENT = /<!--(?!>)[\S\s]*?-->/g, | ||
HTML_TAGS = /<([-\w]+)\s*([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)(\/?)>/g | ||
var | ||
HTML_COMMENT = /<!--(?!>)[\S\s]*?-->/g, | ||
HTML_TAGS = /<([-\w]+)\s*([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)(\/?)>/g, | ||
PRE_TAG = _regEx( | ||
/<pre(?:\s+[^'">]+(?:(?:@Q)[^'">]*)*|\s*)?>([\S\s]*?)<\/pre\s*>/ | ||
.source.replace('@Q', brackets.R_STRINGS.source), 'gi') | ||
function compileHTML(html, opts, pcex, intc) { | ||
function compileHTML(html, opts, pcex ) { | ||
if (!intc) { | ||
_bp = brackets.array(opts.brackets) | ||
html = html.replace(/\r\n?/g, '\n').replace(HTML_COMMENT, '').replace(TRIM_TRAIL, '') | ||
} | ||
if (!pcex) pcex = [] | ||
var intf = (pcex || (pcex = []))._intflag | ||
if (!intf) | ||
html = html.replace(/\r\n?/g, '\n').replace(HTML_COMMENT, '').replace(TRIM_TRAIL, '') | ||
html = splitHtml(html, opts, pcex) | ||
.replace(HTML_TAGS, function (_, name, attr, ends) { | ||
if (!pcex._bp) pcex._bp = brackets.array(opts.brackets) | ||
name = name.toLowerCase() | ||
html = splitHtml(html, opts, pcex) | ||
.replace(HTML_TAGS, function (_, name, attr, ends) { | ||
ends = ends && !VOID_TAGS.test(name) ? '></' + name : '' | ||
name = name.toLowerCase() | ||
if (attr) name += ' ' + parseAttrs(attr) | ||
ends = ends && !VOID_TAGS.test(name) ? '></' + name : '' | ||
return '<' + name + ends + '>' | ||
}) | ||
if (attr) name += ' ' + parseAttrs(attr, pcex) | ||
if (!opts.whitespace) { | ||
var p = [], | ||
pre = /<pre(?:\s+[^'">]+(?:(?:"[^"]*"|'[^']*')[^'">]*)*|\s*)>[\s\S]*<\/pre\s*>/gi | ||
return '<' + name + ends + '>' | ||
}) | ||
html = html.replace(pre, function (q) { | ||
return '\u0002' + (p.push(q) - 1) + '~' }).trim().replace(/\s+/g, ' ') | ||
if (!opts.whitespace) { | ||
if (/<pre[\s>]/.test(html)) { | ||
var p = [] | ||
html = html.replace(PRE_TAG, function (q) | ||
{ return p.push(q) && '\u0002' }).trim().replace(/\s+/g, ' ') | ||
// istanbul ignore else | ||
if (p.length) | ||
html = html.replace(/\u0002(\d+)~/g, function (q, n) { return p[n] }) | ||
html = html.replace(/\u0002/g, function (_) { return p.shift() }) | ||
} | ||
if (opts.compact) html = html.replace(/> <([-\w\/])/g, '><$1') | ||
return restoreExpr(html, pcex) | ||
else | ||
html = html.trim().replace(/\s+/g, ' ') | ||
} | ||
var | ||
if (opts.compact) html = html.replace(/> <([-\w\/])/g, '><$1') | ||
JS_RMCOMMS = _regEx( | ||
'(' + brackets.S_QBLOCKS + ')|' + brackets.R_MLCOMMS.source + '|//[^\r\n]*', | ||
'g'), | ||
return restoreExpr(html, pcex) | ||
} | ||
JS_ES6SIGN = /^([ \t]*)([$_A-Za-z][$\w]*)\s*(\([^()]*\)\s*{)/m | ||
var | ||
JS_RMCOMMS = _regEx('(' + brackets.S_QBLOCKS + ')|' + brackets.R_MLCOMMS.source + '|//[^\r\n]*', 'g'), | ||
JS_ES6SIGN = /^([ \t]*)([$_A-Za-z][$\w]*)\s*(\([^()]*\)\s*{)/m | ||
function riotjs(js) { | ||
var | ||
match, | ||
toes5, | ||
parts = [], | ||
pos | ||
function riotjs(js) { | ||
var | ||
match, | ||
toes5, | ||
parts = [], | ||
pos | ||
js = js.replace(JS_RMCOMMS, function (m, q) { return q ? m : ' ' }) | ||
js = js.replace(JS_RMCOMMS, function (m, q) { return q ? m : ' ' }) | ||
while (match = js.match(JS_ES6SIGN)) { | ||
while (match = js.match(JS_ES6SIGN)) { | ||
parts.push(RegExp.leftContext) | ||
js = RegExp.rightContext | ||
pos = skipBlock(js) | ||
parts.push(RegExp.leftContext) | ||
js = RegExp.rightContext | ||
pos = skipBlock(js) | ||
toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(match[2]) | ||
if (toes5) | ||
match[0] = match[1] + 'this.' + match[2] + ' = function' + match[3] | ||
toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(match[2]) | ||
if (toes5) | ||
match[0] = match[1] + 'this.' + match[2] + ' = function' + match[3] | ||
parts.push(match[0], js.slice(0, pos)) | ||
js = js.slice(pos) | ||
if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)') | ||
} | ||
parts.push(match[0], js.slice(0, pos)) | ||
js = js.slice(pos) | ||
if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)') | ||
} | ||
return parts.length ? parts.join('') + js : js | ||
return parts.length ? parts.join('') + js : js | ||
function skipBlock(str) { | ||
var | ||
re = _regEx('([{}])|' + brackets.S_QBLOCKS, 'g'), | ||
level = 1, | ||
match | ||
function skipBlock(str) { | ||
var | ||
re = _regEx('([{}])|' + brackets.S_QBLOCKS, 'g'), | ||
level = 1, | ||
match | ||
while (level && (match = re.exec(str))) { | ||
if (match[1]) | ||
match[1] === '{' ? ++level : --level | ||
} | ||
return level ? str.length : re.lastIndex | ||
while (level && (match = re.exec(str))) { | ||
if (match[1]) | ||
match[1] === '{' ? ++level : --level | ||
} | ||
return level ? str.length : re.lastIndex | ||
} | ||
} | ||
function compileJS(js, opts, type, parserOpts) { | ||
if (!js) return '' | ||
if (!type) type = opts.type | ||
function compileJS(js, opts, type, parserOpts, url) { | ||
if (!js) return '' | ||
if (!type) type = opts.type | ||
var parser = opts.parser || (type ? parsers.js[type] : riotjs) | ||
if (!parser) | ||
throw new Error('JS parser not found: "' + type + '"') | ||
var parser = opts.parser || (type ? parsers.js[type] : riotjs) | ||
if (!parser) | ||
throw new Error('JS parser not found: "' + type + '"') | ||
return parser(js, parserOpts).replace(TRIM_TRAIL, '') | ||
} | ||
return parser(js, parserOpts, url).replace(TRIM_TRAIL, '') | ||
} | ||
var CSS_SELECTOR = _regEx('(}|{|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + brackets.R_STRINGS.source, 'g') | ||
var CSS_SELECTOR = _regEx('(}|{|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + brackets.R_STRINGS.source, 'g') | ||
function scopedCSS(tag, style) { | ||
var scope = ':scope' | ||
function scopedCSS(tag, style) { | ||
var scope = ':scope' | ||
return style.replace(CSS_SELECTOR, function (m, p1, p2) { | ||
return style.replace(CSS_SELECTOR, function (m, p1, p2) { | ||
if (!p2) return m | ||
if (!p2) return m | ||
p2 = p2.replace(/[^,]+/g, function (sel) { | ||
var s = sel.trim() | ||
p2 = p2.replace(/[^,]+/g, function (sel) { | ||
var s = sel.trim() | ||
if (s && s !== 'from' && s !== 'to' && s.slice(-1) !== '%') { | ||
if (s && s !== 'from' && s !== 'to' && s.slice(-1) !== '%') { | ||
if (s.indexOf(scope) < 0) s = scope + ' ' + s | ||
s = s.replace(scope, tag) + ',' + | ||
s.replace(scope, '[riot-tag="' + tag + '"]') | ||
} | ||
return sel.slice(-1) === ' ' ? s + ' ' : s | ||
}) | ||
return p1 ? p1 + ' ' + p2 : p2 | ||
if (s.indexOf(scope) < 0) s = scope + ' ' + s | ||
s = s.replace(scope, tag) + ',' + | ||
s.replace(scope, '[riot-tag="' + tag + '"]') | ||
} | ||
return sel.slice(-1) === ' ' ? s + ' ' : s | ||
}) | ||
} | ||
function compileCSS(style, tag, type, scoped, opts) { | ||
return p1 ? p1 + ' ' + p2 : p2 | ||
}) | ||
} | ||
if (type) { | ||
if (type === 'scoped-css') { | ||
scoped = true | ||
} | ||
else if (parsers.css[type]) { | ||
style = parsers.css[type](tag, style, opts) | ||
} | ||
else if (type !== 'css') { | ||
throw new Error('CSS parser not found: "' + type + '"') | ||
} | ||
function compileCSS(style, tag, type, scoped, opts) { | ||
if (type) { | ||
if (type === 'scoped-css') { | ||
scoped = true | ||
} | ||
else if (parsers.css[type]) { | ||
style = parsers.css[type](tag, style, opts) | ||
} | ||
else if (type !== 'css') { | ||
throw new Error('CSS parser not found: "' + type + '"') | ||
} | ||
} | ||
style = style.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim() | ||
style = style.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim() | ||
return scoped ? scopedCSS(tag, style) : style | ||
} | ||
return scoped ? scopedCSS(tag, style) : style | ||
} | ||
var | ||
TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i, | ||
MISC_ATTR = /\s*=\s*("(?:\\[\S\s]|[^"\\]*)*"|'(?:\\[\S\s]|[^'\\]*)*'|\{[^}]+}|\S+)/.source | ||
var | ||
TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i, | ||
MISC_ATTR = /\s*=\s*("(?:\\[\S\s]|[^"\\]*)*"|'(?:\\[\S\s]|[^'\\]*)*'|\{[^}]+}|\S+)/.source | ||
function getType(str) { | ||
function getType(str) { | ||
if (str) { | ||
var match = str.match(TYPE_ATTR) | ||
str = match && (match[2] || match[3]) | ||
} | ||
return str ? str.replace('text/', '') : '' | ||
if (str) { | ||
var match = str.match(TYPE_ATTR) | ||
str = match && (match[2] || match[3]) | ||
} | ||
return str ? str.replace('text/', '') : '' | ||
} | ||
function getAttr(str, name) { | ||
function getAttr(str, name) { | ||
if (str) { | ||
var | ||
re = _regEx('\\s' + name + MISC_ATTR, 'i'), | ||
match = str.match(re) | ||
str = match && match[1] | ||
if (str) | ||
return /^['"]/.test(str) ? str.slice(1, -1) : str | ||
} | ||
return '' | ||
if (str) { | ||
var | ||
re = _regEx('\\s' + name + MISC_ATTR, 'i'), | ||
match = str.match(re) | ||
str = match && match[1] | ||
if (str) | ||
return (/^['"]/).test(str) ? str.slice(1, -1) : str | ||
} | ||
return '' | ||
} | ||
// get the parser options from the options attribute | ||
function getParserOptions(attrs) { | ||
var opts = getAttr(attrs, 'options') | ||
// convert the string into a valid js object | ||
if (opts) opts = JSON.parse(opts) | ||
return opts | ||
} | ||
function getParserOptions(attrs) { | ||
var opts = getAttr(attrs, 'options') | ||
// Runs the custom or default parser on the received JavaScript code. | ||
// The CLI version can read code from the file system (experimental) | ||
function getCode(code, opts, attrs, url) { | ||
var type = getType(attrs), | ||
parserOpts = getParserOptions(attrs) | ||
if (opts) opts = JSON.parse(opts) | ||
return opts | ||
} | ||
//#if READ_JS_SRC | ||
function getCode(code, opts, attrs, url) { | ||
var type = getType(attrs), | ||
parserOpts = getParserOptions(attrs) | ||
if (url) { | ||
var src = getAttr(attrs, 'src') | ||
if (src && url) { | ||
if (src) { | ||
var | ||
charset = getAttr(attrs, 'charset'), | ||
file = path.resolve(path.dirname(url), src) | ||
code = require('fs').readFileSync(file, {encoding: charset || 'utf8'}) | ||
code = require('fs').readFileSync(file, charset || 'utf8') | ||
} | ||
//#endif | ||
return compileJS(code, opts, type, parserOpts) | ||
} | ||
return compileJS(code, opts, type, parserOpts, url) | ||
} | ||
// Matches HTML tag ending a line. This regex still can be fooled by code as: | ||
// ```js | ||
// x <y && y > | ||
// z | ||
// ``` | ||
var END_TAGS = /\/>\n|^<(?:\/[\w\-]+\s*|[\w\-]+(?:\s+(?:[-\w:\xA0-\xFF][\S\s]*?)?)?)>\n/ | ||
var END_TAGS = /\/>\n|^<(?:\/[\w\-]+\s*|[\w\-]+(?:\s+(?:[-\w:\xA0-\xFF][\S\s]*?)?)?)>\n/ | ||
function splitBlocks(str) { | ||
var k, m | ||
function splitBlocks(str) { | ||
var k, m | ||
/* istanbul ignore next: this if() can't be true, but just in case... */ | ||
if (str[str.length - 1] === '>') | ||
return [str, ''] | ||
/* istanbul ignore next: this if() can't be true, but just in case... */ | ||
if (str[str.length - 1] === '>') return [str, ''] | ||
k = str.lastIndexOf('<') // first probable open tag | ||
while (~k) { | ||
if (m = str.slice(k).match(END_TAGS)) { | ||
k += m.index + m[0].length | ||
return [str.slice(0, k), str.slice(k)] | ||
} | ||
k = str.lastIndexOf('<', k -1) | ||
k = str.lastIndexOf('<') | ||
while (~k) { | ||
if (m = str.slice(k).match(END_TAGS)) { | ||
k += m.index + m[0].length | ||
return [str.slice(0, k), str.slice(k)] | ||
} | ||
return ['', str] | ||
k = str.lastIndexOf('<', k -1) | ||
} | ||
return ['', str] | ||
} | ||
// Runs the external HTML parser for the entire tag file | ||
function compileTemplate(lang, html, opts) { | ||
var parser = parsers.html[lang] | ||
function compileTemplate(html, url, lang, opts) { | ||
var parser = parsers.html[lang] | ||
if (!parser) | ||
throw new Error('Template parser not found: "' + lang + '"') | ||
if (!parser) | ||
throw new Error('Template parser not found: "' + lang + '"') | ||
return parser(html, opts) | ||
} | ||
return parser(html, opts, url) | ||
} | ||
/* | ||
CUST_TAG regex don't allow unquoted expressions containing the `>` operator. | ||
STYLE and SCRIPT disallows the operator `>` at all. | ||
var | ||
CUST_TAG = _regEx( | ||
/^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:@Q|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/ | ||
.source.replace('@Q', brackets.R_STRINGS.source), 'gim'), | ||
STYLE = /<style(\s+[^>]*)?>\n?([^<]*(?:<(?!\/style\s*>)[^<]*)*)<\/style\s*>/gi, | ||
SCRIPT = _regEx(STYLE.source.replace(/tyle/g, 'cript'), 'gi') | ||
The beta.4 CUST_TAG regex is fast, with RegexBuddy I get 76 steps and 14 backtracks on | ||
the test/specs/fixtures/treeview.tag :) but fails with nested tags of the same name :( | ||
With a greedy * operator, we have ~500 and 200bt, it is acceptable. So let's fix this. | ||
*/ | ||
function compile(src, opts, url) { | ||
var | ||
CUST_TAG = /^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\s\S]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/gim, | ||
STYLE = /<style(\s+[^>]*)?>\n?([^<]*(?:<(?!\/style\s*>)[^<]*)*)<\/style\s*>/gi, | ||
SCRIPT = _regEx(STYLE.source.replace(/tyle/g, 'cript'), 'gi') | ||
parts = [], | ||
exclude | ||
function compile(src, opts, url) { | ||
var label, parts = [] | ||
if (!opts) opts = {} | ||
if (!opts) opts = {} | ||
url = url || process.cwd() | ||
_bp = brackets.array(opts.brackets) | ||
exclude = opts.exclude || false | ||
function included(s) { return !(exclude && ~exclude.indexOf(s)) } | ||
if (opts.template) | ||
src = compileTemplate(opts.template, src, opts.templateOptions) | ||
var _bp = brackets.array(opts.brackets) | ||
/* istanbul ignore next */ | ||
label = !url ? '' : path.isAbsolute(url) ? path.relative('.', url) : url | ||
if (label) | ||
label = '//src: ' + label.replace(/\\/g, '/') + '\n' | ||
if (opts.template) | ||
src = compileTemplate(src, url, opts.template, opts.templateOptions) | ||
src = label + src | ||
.replace(/\r\n?/g, '\n') | ||
.replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) { | ||
src = src | ||
.replace(/\r\n?/g, '\n') | ||
.replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) { | ||
var | ||
jscode = '', | ||
styles = '', | ||
html = '', | ||
pcex = [] | ||
var | ||
jscode = '', | ||
styles = '', | ||
html = '', | ||
pcex = [] | ||
tagName = tagName.toLowerCase() | ||
pcex._bp = _bp | ||
pcex._intflag = 1 | ||
attribs = !attribs ? '' : restoreExpr(parseAttrs(splitHtml(attribs, opts, pcex)), pcex) | ||
tagName = tagName.toLowerCase() | ||
if (body2) body = body2 | ||
attribs = attribs && included('attribs') ? | ||
restoreExpr(parseAttrs(splitHtml(attribs, opts, pcex), pcex), pcex) : '' | ||
if (body && (body = body.replace(HTML_COMMENT, '')) && /\S/.test(body)) { | ||
if (body2) body = body2 | ||
if (body2) | ||
html = compileHTML(body2, opts, pcex, 1) | ||
else { | ||
body = body.replace(_regEx('^' + indent, 'gm'), '') | ||
if (body && (body = body.replace(HTML_COMMENT, '')) && /\S/.test(body)) { | ||
body = body.replace(STYLE, function (_, _attrs, _style) { | ||
var scoped = _attrs && /\sscoped(\s|=|$)/i.test(_attrs), | ||
csstype = getType(_attrs) || opts.style | ||
styles += (styles ? ' ' : '') + | ||
compileCSS(_style, tagName, csstype, scoped, getParserOptions(_attrs)) | ||
return '' | ||
}) | ||
if (body2) { | ||
/* istanbul ignore next */ | ||
html = included('html') ? compileHTML(body2, opts, pcex, 1, url) : '' | ||
} | ||
else { | ||
body = body.replace(_regEx('^' + indent, 'gm'), '') | ||
body = body.replace(SCRIPT, function (_, _attrs, _script) { | ||
jscode += (jscode ? '\n' : '') + getCode(_script, opts, _attrs, url) | ||
return '' | ||
}) | ||
body = body.replace(STYLE, included('css') ? function (_, _attrs, _style) { | ||
var scoped = _attrs && /\sscoped(\s|=|$)/i.test(_attrs), | ||
csstype = getType(_attrs) || opts.style | ||
styles += (styles ? ' ' : '') + | ||
compileCSS(_style, tagName, csstype, scoped, getParserOptions(_attrs), url) | ||
return '' | ||
} : '') | ||
var blocks = splitBlocks(body.replace(TRIM_TRAIL, '')) | ||
body = body.replace(SCRIPT, included('js') ? function (_, _attrs, _script) { | ||
jscode += (jscode ? '\n' : '') + getCode(_script, opts, _attrs, url) | ||
return '' | ||
} : '') | ||
var blocks = splitBlocks(body.replace(TRIM_TRAIL, '')) | ||
if (included('html')) { | ||
body = blocks[0] | ||
if (body) | ||
html = compileHTML(body, opts, pcex, 1) | ||
} | ||
if (included('js')) { | ||
body = blocks[1] | ||
if (/\S/.test(body)) | ||
jscode += (jscode ? '\n' : '') + compileJS(body, opts) | ||
jscode += (jscode ? '\n' : '') + compileJS(body, opts, null, null, url) | ||
} | ||
} | ||
} | ||
jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : '' | ||
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 | ||
}) | ||
return '' | ||
} | ||
if (opts.entities) { | ||
parts.push({ | ||
tagName: tagName, | ||
html: html, | ||
css: styles, | ||
attribs: attribs, | ||
js: jscode | ||
}) | ||
return '' | ||
} | ||
return mktag(tagName, html, styles, attribs, jscode, pcex) | ||
}) | ||
return mktag(tagName, html, styles, attribs, jscode, pcex) | ||
}) | ||
return opts.entities ? parts : src | ||
} | ||
if (opts.entities) return parts | ||
return { | ||
compile: compile, | ||
html: compileHTML, | ||
style: compileCSS, | ||
js: compileJS, | ||
parsers: parsers | ||
if (url && opts.debug) { | ||
/* istanbul ignore if */ | ||
if (path.isAbsolute(url)) url = path.relative('.', url) | ||
src = '//src: ' + url.replace(/\\/g, '/') + '\n' + src | ||
} | ||
}) | ||
return src | ||
} | ||
module.exports = { | ||
compile: compile, | ||
html: compileHTML, | ||
style: compileCSS, | ||
js: compileJS, | ||
parsers: parsers, | ||
version: 'v2.3.16-beta' | ||
} |
/** | ||
* Compiler for riot custom tags | ||
* @version v2.3.13 | ||
*/ | ||
/** | ||
* @module parsers | ||
@@ -13,3 +8,4 @@ */ | ||
function _try(name, req) { //eslint-disable-line no-redeclare | ||
function _try(name, req) { //eslint-disable-line complexity | ||
var parser | ||
@@ -27,3 +23,8 @@ switch (name) { | ||
} | ||
return _mods[name] = window[req] | ||
parser = window[req] | ||
if (!parser) | ||
throw new Error(req + ' parser not found.') | ||
return parser | ||
} | ||
@@ -36,4 +37,8 @@ | ||
var _html = { | ||
jade: function (html, opts) { | ||
return _req('jade').render(html, extend({pretty: true, doctype: 'html'}, opts)) | ||
jade: function (html, opts, url) { | ||
return _req('jade').render(html, extend({ | ||
pretty: true, | ||
filename: url, | ||
doctype: 'html' | ||
}, opts)) | ||
} | ||
@@ -43,3 +48,17 @@ } | ||
var _css = { | ||
stylus: function (tag, css, opts) { | ||
less: function(tag, css, opts, url) { | ||
var less = _req('less'), | ||
ret | ||
less.render(css, extend({ | ||
sync: true, | ||
compress: true | ||
}, opts), function (err, result) { | ||
// istanbul ignore next | ||
if (err) throw err | ||
ret = result.css | ||
}) | ||
return ret | ||
}, | ||
stylus: function (tag, css, opts, url) { | ||
var | ||
@@ -54,12 +73,12 @@ stylus = _req('stylus'), nib = _req('nib') | ||
var _js = { | ||
none: function (js, opts) { | ||
none: function (js, opts, url) { | ||
return js | ||
}, | ||
livescript: function (js, opts) { | ||
livescript: function (js, opts, url) { | ||
return _req('livescript').compile(js, extend({bare: true, header: false}, opts)) | ||
}, | ||
typescript: function (js, opts) { | ||
typescript: function (js, opts, url) { | ||
return _req('typescript')(js, opts).replace(/\r\n?/g, '\n') | ||
}, | ||
es6: function (js, opts) { | ||
es6: function (js, opts, url) { | ||
return _req('es6').transform(js, extend({ | ||
@@ -69,11 +88,10 @@ blacklist: ['useStrict', 'strict', 'react'], sourceMaps: false, comments: false | ||
}, | ||
babel: function (js, opts) { | ||
js = 'function __parser_babel_wrapper__(){' + js + '}' | ||
babel: function (js, opts, url) { | ||
return _req('babel').transform(js, | ||
extend({ | ||
presets: ['es2015'] | ||
filename: url | ||
}, opts) | ||
).code.replace(/["']use strict["'];[\r\n]+/, '').slice(38, -2) | ||
).code | ||
}, | ||
coffee: function (js, opts) { | ||
coffee: function (js, opts, url) { | ||
return _req('coffee').compile(js, extend({bare: true}, opts)) | ||
@@ -93,7 +111,11 @@ } | ||
/** | ||
* @module compiler | ||
* Compiler for riot custom tags | ||
* @version v2.3.16-beta | ||
*/ | ||
var compile = (function () { | ||
var brackets = riot.util.brackets | ||
// istanbul ignore next | ||
if (!brackets.version) { | ||
throw new Error('This compiler version requires riot-tmpl v2.3.16 or above') | ||
} | ||
@@ -106,3 +128,3 @@ function _regEx(str, opt) { return new RegExp(str, opt) } | ||
'^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' + | ||
'compact|controls|default|formnovalidate|hidden|inert|ismap|itemscope|loop|' + | ||
'compact|controls|default|formnovalidate|hidden|ismap|itemscope|loop|' + | ||
'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' + | ||
@@ -115,8 +137,6 @@ 'selected|sortable|truespeed|typemustmatch)$'), | ||
HTML_ATTR = /\s*([-\w:\.\xA0-\xFF]+)\s*(?:=\s*('[^']+'|"[^"]+"|\S+))?/g, | ||
HTML_ATTR = /\s*([-\w:\xA0-\xFF]+)\s*(?:=\s*('[^']+'|"[^"]+"|\S+))?/g, | ||
TRIM_TRAIL = /[ \t]+$/gm, | ||
TRIM_TRAIL = /[ \t]+$/gm | ||
_bp = null | ||
function q(s) { | ||
@@ -131,3 +151,3 @@ return "'" + (s ? s | ||
c = ', ', | ||
s = '}' + (pcex.length ? ', ' + q(_bp[8]) : '') + ');' | ||
s = '}' + (pcex.length ? ', ' + q(pcex._bp[8]) : '') + ');' | ||
@@ -150,3 +170,3 @@ if (js && js.slice(-1) !== '\n') s = '\n' + s | ||
function parseAttrs(str) { | ||
function parseAttrs(str, pcex) { | ||
var | ||
@@ -156,5 +176,9 @@ list = [], | ||
k, v, | ||
_bp = pcex._bp, | ||
DQ = '"' | ||
HTML_ATTR.lastIndex = 0 | ||
str = str.replace(/\s+/g, ' ') | ||
while (match = HTML_ATTR.exec(str)) { | ||
@@ -193,2 +217,3 @@ | ||
function splitHtml(html, opts, pcex) { | ||
var _bp = pcex._bp | ||
@@ -198,3 +223,3 @@ if (html && _bp[4].test(html)) { | ||
jsfn = opts.expr && (opts.parser || opts.type) ? compileJS : 0, | ||
list = brackets.split(html), | ||
list = brackets.split(html, 0, _bp), | ||
expr | ||
@@ -207,4 +232,6 @@ | ||
else if (jsfn) { | ||
expr = jsfn(expr, opts) | ||
if (/;\s*$/.test(expr)) expr = expr.slice(0, expr.search(/;\s*$/)) | ||
var israw = expr[0] === '=' | ||
expr = jsfn(israw ? expr.slice(1) : expr, opts).trim() | ||
if (expr.slice(-1) === ';') expr = expr.slice(0, -1) | ||
if (israw) expr = '=' + expr | ||
} | ||
@@ -222,3 +249,11 @@ list[i] = '\u0001' + (pcex.push(expr.replace(/[\r\n]+/g, ' ').trim()) - 1) + _bp[1] | ||
.replace(/\u0001(\d+)/g, function (_, d) { | ||
return _bp[0] + pcex[d].replace(/"/g, '"') | ||
var expr = pcex[d] | ||
if (expr[0] === '=') { | ||
expr = expr.replace(brackets.R_STRINGS, function (qs) { | ||
return qs | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
}) | ||
} | ||
return pcex._bp[0] + expr | ||
}) | ||
@@ -231,12 +266,15 @@ } | ||
HTML_COMMENT = /<!--(?!>)[\S\s]*?-->/g, | ||
HTML_TAGS = /<([-\w]+)\s*([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)(\/?)>/g | ||
HTML_TAGS = /<([-\w]+)\s*([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)(\/?)>/g, | ||
PRE_TAG = _regEx( | ||
/<pre(?:\s+[^'">]+(?:(?:@Q)[^'">]*)*|\s*)?>([\S\s]*?)<\/pre\s*>/ | ||
.source.replace('@Q', brackets.R_STRINGS.source), 'gi') | ||
function compileHTML(html, opts, pcex, intc) { | ||
function compileHTML(html, opts, pcex ) { | ||
if (!intc) { | ||
_bp = brackets.array(opts.brackets) | ||
var intf = (pcex || (pcex = []))._intflag | ||
if (!intf) | ||
html = html.replace(/\r\n?/g, '\n').replace(HTML_COMMENT, '').replace(TRIM_TRAIL, '') | ||
} | ||
if (!pcex) pcex = [] | ||
if (!pcex._bp) pcex._bp = brackets.array(opts.brackets) | ||
html = splitHtml(html, opts, pcex) | ||
@@ -249,3 +287,3 @@ .replace(HTML_TAGS, function (_, name, attr, ends) { | ||
if (attr) name += ' ' + parseAttrs(attr) | ||
if (attr) name += ' ' + parseAttrs(attr, pcex) | ||
@@ -256,9 +294,12 @@ return '<' + name + ends + '>' | ||
if (!opts.whitespace) { | ||
var p = [], | ||
pre = /<pre(?:\s+[^'">]+(?:(?:"[^"]*"|'[^']*')[^'">]*)*|\s*)>[\s\S]*<\/pre\s*>/gi | ||
html = html.replace(pre, function (q) { | ||
return '\u0002' + (p.push(q) - 1) + '~' }).trim().replace(/\s+/g, ' ') | ||
if (p.length) | ||
html = html.replace(/\u0002(\d+)~/g, function (q, n) { return p[n] }) | ||
if (/<pre[\s>]/.test(html)) { | ||
var p = [] | ||
html = html.replace(PRE_TAG, function (q) | ||
{ return p.push(q) && '\u0002' }).trim().replace(/\s+/g, ' ') | ||
// istanbul ignore else | ||
if (p.length) | ||
html = html.replace(/\u0002/g, function (_) { return p.shift() }) | ||
} | ||
else | ||
html = html.trim().replace(/\s+/g, ' ') | ||
} | ||
@@ -272,7 +313,3 @@ | ||
var | ||
JS_RMCOMMS = _regEx( | ||
'(' + brackets.S_QBLOCKS + ')|' + brackets.R_MLCOMMS.source + '|//[^\r\n]*', | ||
'g'), | ||
JS_RMCOMMS = _regEx('(' + brackets.S_QBLOCKS + ')|' + brackets.R_MLCOMMS.source + '|//[^\r\n]*', 'g'), | ||
JS_ES6SIGN = /^([ \t]*)([$_A-Za-z][$\w]*)\s*(\([^()]*\)\s*{)/m | ||
@@ -320,3 +357,3 @@ | ||
function compileJS(js, opts, type, parserOpts) { | ||
function compileJS(js, opts, type, parserOpts, url) { | ||
if (!js) return '' | ||
@@ -329,3 +366,3 @@ if (!type) type = opts.type | ||
return parser(js, parserOpts).replace(TRIM_TRAIL, '') | ||
return parser(js, parserOpts, url).replace(TRIM_TRAIL, '') | ||
} | ||
@@ -398,3 +435,3 @@ | ||
if (str) | ||
return /^['"]/.test(str) ? str.slice(1, -1) : str | ||
return (/^['"]/).test(str) ? str.slice(1, -1) : str | ||
} | ||
@@ -404,6 +441,5 @@ return '' | ||
// get the parser options from the options attribute | ||
function getParserOptions(attrs) { | ||
var opts = getAttr(attrs, 'options') | ||
// convert the string into a valid js object | ||
if (opts) opts = JSON.parse(opts) | ||
@@ -413,4 +449,2 @@ return opts | ||
// Runs the custom or default parser on the received JavaScript code. | ||
// The CLI version can read code from the file system (experimental) | ||
function getCode(code, opts, attrs, url) { | ||
@@ -420,19 +454,5 @@ var type = getType(attrs), | ||
//#if READ_JS_SRC | ||
var src = getAttr(attrs, 'src') | ||
if (src && url) { | ||
var | ||
charset = getAttr(attrs, 'charset'), | ||
file = path.resolve(path.dirname(url), src) | ||
code = require('fs').readFileSync(file, {encoding: charset || 'utf8'}) | ||
} | ||
//#endif | ||
return compileJS(code, opts, type, parserOpts) | ||
return compileJS(code, opts, type, parserOpts, url) | ||
} | ||
// Matches HTML tag ending a line. This regex still can be fooled by code as: | ||
// ```js | ||
// x <y && y > | ||
// z | ||
// ``` | ||
var END_TAGS = /\/>\n|^<(?:\/[\w\-]+\s*|[\w\-]+(?:\s+(?:[-\w:\xA0-\xFF][\S\s]*?)?)?)>\n/ | ||
@@ -444,6 +464,5 @@ | ||
/* istanbul ignore next: this if() can't be true, but just in case... */ | ||
if (str[str.length - 1] === '>') | ||
return [str, ''] | ||
if (str[str.length - 1] === '>') return [str, ''] | ||
k = str.lastIndexOf('<') // first probable open tag | ||
k = str.lastIndexOf('<') | ||
while (~k) { | ||
@@ -456,8 +475,6 @@ if (m = str.slice(k).match(END_TAGS)) { | ||
} | ||
return ['', str] | ||
} | ||
// Runs the external HTML parser for the entire tag file | ||
function compileTemplate(lang, html, opts) { | ||
function compileTemplate(html, url, lang, opts) { | ||
var parser = parsers.html[lang] | ||
@@ -468,15 +485,9 @@ | ||
return parser(html, opts) | ||
return parser(html, opts, url) | ||
} | ||
/* | ||
CUST_TAG regex don't allow unquoted expressions containing the `>` operator. | ||
STYLE and SCRIPT disallows the operator `>` at all. | ||
The beta.4 CUST_TAG regex is fast, with RegexBuddy I get 76 steps and 14 backtracks on | ||
the test/specs/fixtures/treeview.tag :) but fails with nested tags of the same name :( | ||
With a greedy * operator, we have ~500 and 200bt, it is acceptable. So let's fix this. | ||
*/ | ||
var | ||
CUST_TAG = /^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\s\S]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/gim, | ||
CUST_TAG = _regEx( | ||
/^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:@Q|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/ | ||
.source.replace('@Q', brackets.R_STRINGS.source), 'gim'), | ||
STYLE = /<style(\s+[^>]*)?>\n?([^<]*(?:<(?!\/style\s*>)[^<]*)*)<\/style\s*>/gi, | ||
@@ -486,14 +497,17 @@ SCRIPT = _regEx(STYLE.source.replace(/tyle/g, 'cript'), 'gi') | ||
function compile(src, opts, url) { | ||
var label, parts = [] | ||
var | ||
parts = [], | ||
exclude | ||
if (!opts) opts = {} | ||
_bp = brackets.array(opts.brackets) | ||
exclude = opts.exclude || false | ||
function included(s) { return !(exclude && ~exclude.indexOf(s)) } | ||
var _bp = brackets.array(opts.brackets) | ||
if (opts.template) | ||
src = compileTemplate(opts.template, src, opts.templateOptions) | ||
src = compileTemplate(src, url, opts.template, opts.templateOptions) | ||
label = url ? '//src: ' + url + '\n' : '' | ||
src = label + src | ||
src = src | ||
.replace(/\r\n?/g, '\n') | ||
@@ -508,5 +522,9 @@ .replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) { | ||
pcex._bp = _bp | ||
pcex._intflag = 1 | ||
tagName = tagName.toLowerCase() | ||
attribs = !attribs ? '' : restoreExpr(parseAttrs(splitHtml(attribs, opts, pcex)), pcex) | ||
attribs = attribs && included('attribs') ? | ||
restoreExpr(parseAttrs(splitHtml(attribs, opts, pcex), pcex), pcex) : '' | ||
@@ -517,29 +535,35 @@ if (body2) body = body2 | ||
if (body2) | ||
html = compileHTML(body2, opts, pcex, 1) | ||
if (body2) { | ||
/* istanbul ignore next */ | ||
html = included('html') ? compileHTML(body2, opts, pcex, 1, url) : '' | ||
} | ||
else { | ||
body = body.replace(_regEx('^' + indent, 'gm'), '') | ||
body = body.replace(STYLE, function (_, _attrs, _style) { | ||
body = body.replace(STYLE, included('css') ? function (_, _attrs, _style) { | ||
var scoped = _attrs && /\sscoped(\s|=|$)/i.test(_attrs), | ||
csstype = getType(_attrs) || opts.style | ||
styles += (styles ? ' ' : '') + | ||
compileCSS(_style, tagName, csstype, scoped, getParserOptions(_attrs)) | ||
compileCSS(_style, tagName, csstype, scoped, getParserOptions(_attrs), url) | ||
return '' | ||
}) | ||
} : '') | ||
body = body.replace(SCRIPT, function (_, _attrs, _script) { | ||
jscode += (jscode ? '\n' : '') + getCode(_script, opts, _attrs) | ||
body = body.replace(SCRIPT, included('js') ? function (_, _attrs, _script) { | ||
jscode += (jscode ? '\n' : '') + getCode(_script, opts, _attrs, url) | ||
return '' | ||
}) | ||
} : '') | ||
var blocks = splitBlocks(body.replace(TRIM_TRAIL, '')) | ||
body = blocks[0] | ||
if (body) | ||
html = compileHTML(body, opts, pcex, 1) | ||
if (included('html')) { | ||
body = blocks[0] | ||
if (body) | ||
html = compileHTML(body, opts, pcex, 1) | ||
} | ||
body = blocks[1] | ||
if (/\S/.test(body)) | ||
jscode += (jscode ? '\n' : '') + compileJS(body, opts) | ||
if (included('js')) { | ||
body = blocks[1] | ||
if (/\S/.test(body)) | ||
jscode += (jscode ? '\n' : '') + compileJS(body, opts, null, null, url) | ||
} | ||
} | ||
@@ -564,8 +588,16 @@ } | ||
return opts.entities ? parts : src | ||
if (opts.entities) return parts | ||
return src | ||
} | ||
riot.util.compiler = { | ||
compile: compile, | ||
html: compileHTML, | ||
style: compileCSS, | ||
js: compileJS, | ||
version: 'v2.3.16-beta' | ||
} | ||
return compile | ||
})() | ||
@@ -30,5 +30,4 @@ Boolean attributes in riot 2.3 | ||
There's no official documentation with a full list of boolean attributes, but we collect | ||
that information from different sources. | ||
Currently riot v2.3.x recognizes these boolean attributes: | ||
There's no official list with all the boolean attributes, but we collect that information from different sources. | ||
Currently riot v2.3.x recognizes these: | ||
@@ -53,6 +52,5 @@ * allowfullscreen - `<iframe>` - WHATWG HTML Living Standard, not in W3C HTML5 | ||
* controls - `<audio>`/`<video>` | ||
* default - `<track>`/`<menuitem>` - menuitem is not yet supported by browsers | ||
* default - `<track>`/`<menuitem>` (menuitem is not yet supported by browsers) | ||
* formnovalidate | ||
* hidden | ||
* inert - deprecated | ||
* itemscope - for html5 Microdata | ||
@@ -66,3 +64,3 @@ * loop - `<audio>`/`<video>` | ||
* sortable - html 5.1 | ||
* typemustmatch | ||
* typemustmatch - `<object>` | ||
@@ -72,4 +70,2 @@ **WARNING:** Please don't use expressions in the `loop` attribute within `<img>` tags. | ||
The `inert` attribute [was dropped](https://html5.org/r/8536) from the html5 spec in 2014. | ||
Tested with [the w3c Validator](https://validator.w3.org/nu) | ||
@@ -87,2 +83,3 @@ | ||
* draggable - not boolean, this is an enumerated attribute: true, false, auto | ||
* inert - this proposed html5 attribute was [dropped](https://html5.org/r/8536) from the spec | ||
* enabled - not in the HTML spec | ||
@@ -89,0 +86,0 @@ * indeterminate - boolean attr, but can't be set with markup |
213
doc/guide.md
@@ -5,6 +5,6 @@ # Compiler Guide (complement, WIP) | ||
In v2.3.12 the compiler handles a more consistent and flexible indentation in both inline and external tag definitions. | ||
From v2.3.13, the compiler handles a more consistent and flexible indentation in both inline and external tag definitions. | ||
The opening tag must begin a line. You can use any tabs or spaces you want. The compiler uses this to find the closing tag and unindent the content, so the closing tag must have _exactly_ the same indentation of the opening tag. | ||
HTML comments and trailing whitespace are removed from the entire tag content (JavaScript comments are removed in the JavaScript block only, so you can not use comment in expressions). | ||
HTML comments and trailing whitespace are removed from the entire tag content, JavaScript comments are removed from the JavaScript block only. You should not use comments in expressions. | ||
@@ -30,13 +30,113 @@ Example: | ||
``` | ||
note the `<pre>` content, this is unindented by 2 too. | ||
## Backslashes and Whitespace | ||
Note the `<pre>` content, this is unindented by 2 too. | ||
From the perspective of the riot compiler and `tmpl`, backslashes in the template are characters with no special meaning. The compiler preserves them in the HTML and expressions, with one exception: backslashes used to escape riot brackets are temporarily removed when the expression is passed to a parser, and finally removed at runtime, before evaluating the expression. | ||
In quoted strings and regexes inside expressions, all whitespace are preserved. | ||
In the html, including quoted text, newlines are converted to spaces and compacted, except if you pass the `whitespace` option to the compiler. With this option | ||
### One-line tags | ||
No matter which options are used, newlines are normalized to `\n` and trailing spaces are removed. | ||
Riot can handle one-line tag definitions like this: | ||
```html | ||
<oneline attrib="x"/> | ||
``` | ||
or this: | ||
```html | ||
<oneline attrib="x"><p><!-- --></p></oneline> | ||
``` | ||
As multiline tags, these can be indentent by any tabs or spaces, but can not contain `style`/`script` tags nor untagged JavaScript code, only html markup. | ||
### Untagged html content | ||
From the Riot Guide: | ||
> Without the `<script>` tag the JavaScript starts where the last HTML tag ends. | ||
If there's no HTML tags within the root tag, riot assumes that the content is JavaScript, so the following does not work: | ||
```html | ||
<my-tag> | ||
I'm html? | ||
</my-tag> | ||
``` | ||
This may seem counterintuitive, but maintains backward compatibility with the behavior of previous versions. | ||
See [The untagged JS block](the-untagged-js-block) for details. | ||
### Whitespace | ||
In the html, including quoted text, newlines are converted to spaces and compacted (successive empty lines are merged into one), except if you pass the `whitespace` option to the compiler, in which case only takes place normalization to Unix-style line endings. | ||
The following table summarizes this behavior with [different options](#compilation-options): | ||
| example | generates | options | ||
| ------- | --------- | ------- | ||
| `<p>\r<p>\n<p> </p>` | `<p> </p> <p> </p>` | (none) | ||
| `<p>\r<p>\n<p> </p>` | `<p>\\n</p>\\n<p> </p>` | `whitespace:true` | ||
| `<p>\r<p>\n<p> </p>` | `<p></p><p></p>` | `compact:true` | ||
| `<p>\r<p>\n<p> </p>` | `<p>\\n</p>\\n<p></p>` | `compact:true, whitespace:true` | ||
**In other parts** | ||
Tag attributes, including these within nested tags, are normalized. This is: | ||
* the attribute name is converted to lowercase | ||
* newlines are converted to compacted spaces | ||
* spacing between name and value is removed | ||
* the value is enclosed in double quotes. | ||
Content of `style` blocks and expressions are trimmed and newlines converted to compacted spaces. | ||
This example shows the behavior with the default options on different parts of a tag: | ||
```html | ||
<my-tag | ||
style=' | ||
top:0; | ||
left:0' expr={ | ||
{ foo:"bar" } | ||
}> | ||
<style> | ||
p { | ||
display: none; | ||
} | ||
</style> | ||
<p/> | ||
click(e) | ||
{} | ||
</my-tag> | ||
``` | ||
will generate this: | ||
```js | ||
riot.tag2('my-tag', '<p></p>', 'p { display: none; }', 'style=" top:0; left:0" expr="{{ foo:"bar" }}"', function(opts) { | ||
this.click = function(e) | ||
{}.bind(this) | ||
}, '{ }'); | ||
``` | ||
**NOTE:** No matter which options you use, newlines are normalized to `\n` and comments and trailing spaces are removed before the parsing begins. | ||
### Brackets and backslashes | ||
From the perspective of the riot compiler, backslashes in the template are characters with no special meaning. | ||
The compiler preserves them with one exception: backslashes inside expressions used to escape riot brackets are removed. This occurs just before the expression is passed to any JS parser. | ||
Actually, with correct JavaScript, the compiler is a bit smarter and does not need escaped brackets _within_ expressions. | ||
However, it is needed for literal opening brackets out of expressions, since there is no way to differentiate from riot brackets. | ||
Example: | ||
```html | ||
<my-tag non-expr="\{ empty:\{} }" expr="{ empty:{} }"></my-tag> | ||
``` | ||
In the first, non-expr attribute, opening brackets must be escaped. | ||
The generated code is: | ||
```js | ||
// This JS comment is at column 0, out of the tag | ||
riot.tag2('my-tag', '', '', 'non-expr="\\{ empty:\\{} }" expr="{empty:{}}"', function(opts) { | ||
click(e) {} | ||
}); // the closing tag is indented by 2 spaces, this comment must be JS | ||
``` | ||
at runtime, the backslashes in `non-expr` will be removed. If you need output a literal backslash in front of brackets, you must use `\\{` to generate `\\\\{`. | ||
## Compilation options | ||
@@ -50,17 +150,49 @@ | ||
| expr | html | boolean | Run expressions through the parser defined with the `type` option | ||
| compact | html | boolean | Remove spaces between tags (minify `</p> <p>` to `</p><p>`) | ||
| compact | html | boolean | Remove spaces between tags (minify `<p> </p> <p> </p>` to `<p></p><p></p>`) | ||
| whitespace | html | boolean | Preserve newlines and tabs. newlines are normalized anyway | ||
| template | html | string | HTML pre-processor. Built-in support for: jade | ||
| type | js | string | JavaScript pre-processor. Built-in support for: es6, babel, coffeescript, typescript, livescript, none | ||
| style | css | string | CSS pre-processor. Built-in support for: jade | ||
| style | css | string | CSS pre-processor. Built-in support for: sass, scss, less | ||
| entities | compile | boolean | Split the tag in its raw parts. | ||
| exclude | compile | array | This is an array of strings with part names to exclude. These names are the same as those generated by the `entities` option: html, css, attribs, js. Ej. `{exclude: ['js']}` | ||
### The `entities` option | ||
This option, new in v2.3.13, causes the `compile` function return an array of objects with the parts of the tags. | ||
Each object contains five properties: tagName, html, attribs, css, and js. Propertie values for non-existent parts are empty strings. | ||
Example: | ||
```html | ||
<script type="riot/tag" id="test1"> | ||
// two custom tags | ||
<tag1 id="id1"><div/></tag1> | ||
<tag2> | ||
<style>#id1 {top:0}</style> | ||
<p/> | ||
click(e) { | ||
} | ||
</tag2> | ||
</script> | ||
<script> | ||
var arr = compiler.compile(document.getElementById('test1').innerHTML, {entities: true}) | ||
</script> | ||
``` | ||
will set `arr` to be: | ||
```js | ||
[ | ||
{tagName: 'tag1', html: '<div></div>', css: '', attribs: 'id="id1"', js: ''}, | ||
{tagName: 'tag2', html: '<p></p>', css: '#id1 {top:0}', attribs: '', js: ' this.click = function(e) {\n }.bind(this);' } | ||
] | ||
``` | ||
Be aware that the `html` and `js` properties can contain raw line endings --i.e. unescaped `\r` and/or `\n`. | ||
## Parser options | ||
In addition to the `type` attribute, script and style tags can include additional configuration at tag level through the `options` attribute. | ||
In addition to the `type` attribute, `script` and `style` tags can include additional configuration at tag level through the `options` attribute. | ||
**NOTE:** This attribute is not an expression, it is a [JSON](http://json.org/) object and must comply with the [JSON specs](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf), i.e. suitable for `JSON.parse`. | ||
This attribute is a [JSON](http://json.org/) object that specifies options for the JS or CSS parser. | ||
The `options` attribute specify options for the JS or CSS parser. | ||
Example: | ||
@@ -80,21 +212,26 @@ ```html | ||
**NOTE:** The `options` attribute must comply with the [JSON specs](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf), i.e. with double-quoted property names and strings, in a format suitable for `JSON.parse` | ||
## The untagged JS block | ||
Where the html ends? or where should I put my JS? | ||
The first action taken by the compiler is send the received source to any htm parser. After that, | ||
it normalizes line endings to `\n` and removes _html_ comments. | ||
Once prepared the source, searches for tags, separate the parts (closing/opening tag, root | ||
attributes, and content). In the content, search the `<style>` blocks, removes and sends their | ||
content to the CSS parser. Next, it does the same for the `<script>` tags. | ||
## The JavaScript | ||
Where the html ends? or where should I put my JavaScript? | ||
### The untagged JS block | ||
The first action taken by the compiler is send the received source to any html parser. | ||
After that, it normalizes line endings to `\n` and removes _html_ comments. | ||
Once prepared the source, searches the tags, separate its parts (closing/opening tag, root | ||
attributes, and content). In the content, one by one, removes the `style` blocks and sends its | ||
content to the CSS parser. Next, it does the same for the `script` tags. | ||
This is done in the entire content. | ||
In the remaining source, look for the last html tag (ending a line). If found, this marks the | ||
end of the html and the beginning of the JavaScript code. If not found, all remaining is | ||
considered JavaScript. | ||
In the remaining content, looks for the last html tag which ends a line. | ||
If found, this closing tag signals the end of the html markup and the beginning of the JavaScript code. | ||
If not found, all remaining is considered JavaScript. | ||
So, you can put html comments, `style`, and `script` blocks anywhere inside the tag. | ||
The only restriction is that the untagged JavaScript code must finish the content. | ||
So, you can put html comments, `style`, and `script` blocks, anywhere inside the tag. | ||
The only restriction is that the untagged JavaScript code must finish the content and you can't use JavaScript comments out of this block. | ||
## Multiple JavaScript blocks | ||
### Multiple JavaScript blocks | ||
@@ -104,3 +241,3 @@ Each JavaScript block in the tag can have different `type` attributes. | ||
## Loading JavaScript from the file system | ||
### Loading JavaScript from the file system (v2.3.13) | ||
@@ -111,4 +248,14 @@ The `src` attribute of the `script` tags inside a riot tag, allows load source files from the file system. | ||
For this feature to work, you need to pass a third parameter to the `compile` function: the name of the file being compiled. | ||
Example: | ||
```js | ||
var compile = require('riot-compile'), | ||
fs = require('fs') | ||
var source = fs.readFileSync(full_filename, 'utf8') | ||
var result = compiler.compile(source, options, full_filename) | ||
``` | ||
So, if we have a mytag.tag and a js/data.js file... | ||
```js | ||
// ./js/data.js file | ||
@@ -125,3 +272,3 @@ this.title = "my title" | ||
is equivalent to | ||
the result is equivalent to | ||
```html | ||
@@ -135,1 +282,9 @@ <my-tag> | ||
``` | ||
## Style | ||
(WIP) | ||
### Scoped style | ||
(WIP) |
@@ -151,2 +151,3 @@ # Compiler | ||
- `sass` | ||
- `scss` | ||
- `stylus` | ||
@@ -153,0 +154,0 @@ |
1007
lib/core.js
@@ -1,630 +0,651 @@ | ||
/** | ||
* @module compiler | ||
*/ | ||
//#if RIOT | ||
var compile = (function () { | ||
var brackets = riot.util.brackets | ||
//#else | ||
//#define READ_JS_SRC | ||
// istanbul ignore next | ||
if (!brackets.version) { | ||
throw new Error('This compiler version requires riot-tmpl v2.3.16 or above') | ||
} | ||
var brackets = _tmpl.brackets //eslint-disable-line no-redeclare | ||
//#endif | ||
function _regEx(str, opt) { return new RegExp(str, opt) } | ||
function _regEx(str, opt) { return new RegExp(str, opt) } | ||
// Looks like, in [jsperf tests](http://jsperf.com/riot-regexp-test-vs-array-indexof) | ||
// RegExp is faster in most browsers, except for very shorty arrays. | ||
var | ||
// Boolean attributes, prefixed with `__` in the riot tag definition. | ||
// See ../doc/attributes.md | ||
// | ||
BOOL_ATTRS = _regEx( | ||
'^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' + | ||
'compact|controls|default|formnovalidate|hidden|ismap|itemscope|loop|' + | ||
'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' + | ||
'selected|sortable|truespeed|typemustmatch)$'), | ||
// Looks like, in [jsperf tests](http://jsperf.com/riot-regexp-test-vs-array-indexof) | ||
// RegExp is faster in most browsers, except for very shorty arrays. | ||
var | ||
// Boolean attributes, prefixed with `__` in the riot tag definition. | ||
// See ../doc/attributes.md | ||
// | ||
BOOL_ATTRS = _regEx( | ||
'^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' + | ||
'compact|controls|default|formnovalidate|hidden|inert|ismap|itemscope|loop|' + | ||
'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' + | ||
'selected|sortable|truespeed|typemustmatch)$'), | ||
// The following attributes give error when parsed on browser with `{ exrp_value }` | ||
// See ../doc/attributes.md | ||
RIOT_ATTRS = ['style', 'src', 'd'], | ||
// The following attributes give error when parsed on browser with `{ exrp_value }` | ||
// See ../doc/attributes.md | ||
RIOT_ATTRS = ['style', 'src', 'd'], | ||
// HTML5 void elements that cannot be auto-closed. | ||
// See: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements | ||
// http://www.w3.org/TR/html5/syntax.html#void-elements | ||
VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/, | ||
// HTML5 void elements that cannot be auto-closed. | ||
// See: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements | ||
// http://www.w3.org/TR/html5/syntax.html#void-elements | ||
VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/, | ||
// Matches attributes. Names can contain almost all iso-8859-1 character set. | ||
HTML_ATTR = /\s*([-\w:\xA0-\xFF]+)\s*(?:=\s*('[^']+'|"[^"]+"|\S+))?/g, | ||
// Matches attributes. Names can contain almost all iso-8859-1 character set. | ||
HTML_ATTR = /\s*([-\w:\.\xA0-\xFF]+)\s*(?:=\s*('[^']+'|"[^"]+"|\S+))?/g, | ||
TRIM_TRAIL = /[ \t]+$/gm | ||
TRIM_TRAIL = /[ \t]+$/gm, | ||
//#if NODE | ||
var path = require('path') | ||
//#endif | ||
_bp = null | ||
//#set $_RIX_TEST = 4 | ||
//#set $_RIX_ESC = 5 | ||
//#set $_RIX_OPEN = 6 | ||
//#set $_RIX_CLOSE = 7 | ||
//#set $_RIX_PAIR = 8 | ||
//#ifndef $_RIX_TEST | ||
var | ||
$_RIX_TEST = 4, // DONT'T FORGET SYNC THE #set BLOCK!!! | ||
$_RIX_ESC = 5, | ||
$_RIX_OPEN = 6, | ||
$_RIX_CLOSE = 7, | ||
$_RIX_PAIR = 8 | ||
//#endif | ||
//#if NODE | ||
var path = require('path') | ||
//#endif | ||
// Escape backslashes and inner single quotes, and enclose s in single quotes | ||
function q(s) { | ||
return "'" + (s ? s | ||
.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r') : | ||
'') + "'" | ||
} | ||
//#set $_RIX_TEST = 4 | ||
//#set $_RIX_ESC = 5 | ||
//#set $_RIX_OPEN = 6 | ||
//#set $_RIX_CLOSE = 7 | ||
//#set $_RIX_PAIR = 8 | ||
//#ifndef $_RIX_TEST | ||
// Generates the `riot.tag2` call with the processed parts. | ||
function mktag(name, html, css, attrs, js, pcex) { | ||
var | ||
$_RIX_TEST = 4, // DONT'T FORGET SYNC THE #set BLOCK!!! | ||
$_RIX_ESC = 5, | ||
$_RIX_OPEN = 6, | ||
$_RIX_CLOSE = 7, | ||
$_RIX_PAIR = 8 | ||
//#endif | ||
c = ', ', | ||
s = '}' + (pcex.length ? ', ' + q(pcex._bp[$_RIX_PAIR]) : '') + ');' | ||
// Escape backslashes and inner single quotes, and enclose s in single quotes | ||
function q(s) { | ||
return "'" + (s ? s | ||
.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r') : | ||
'') + "'" | ||
// give more consistency to the output | ||
if (js && js.slice(-1) !== '\n') s = '\n' + s | ||
return 'riot.tag2(\'' + name + "'" + c + q(html) + c + q(css) + c + q(attrs) + | ||
', function(opts) {\n' + js + s | ||
} | ||
/** | ||
* Merge two javascript object extending the properties of the first one with | ||
* the second | ||
* | ||
* @param {object} obj - source object | ||
* @param {object} props - extra properties | ||
* @returns {object} source object containing the new properties | ||
*/ | ||
function extend(obj, props) { | ||
for (var prop in props) { | ||
/* istanbul ignore next */ | ||
if (props.hasOwnProperty(prop)) { | ||
obj[prop] = props[prop] | ||
} | ||
} | ||
return obj | ||
} | ||
// Generates the `riot.tag2` call with the processed parts. | ||
function mktag(name, html, css, attrs, js, pcex) { | ||
var | ||
c = ', ', | ||
s = '}' + (pcex.length ? ', ' + q(_bp[$_RIX_PAIR]) : '') + ');' | ||
/** | ||
* Parses and format attributes. | ||
* | ||
* @param {string} str - Attributes, with expressions replaced by their hash | ||
* @param {Array} pcex - Has a _bp property with info about brackets | ||
* @returns {string} Formated attributes | ||
*/ | ||
function parseAttrs(str, pcex) { | ||
var | ||
list = [], | ||
match, | ||
k, v, | ||
_bp = pcex._bp, | ||
DQ = '"' | ||
// give more consistency to the output | ||
if (js && js.slice(-1) !== '\n') s = '\n' + s | ||
HTML_ATTR.lastIndex = 0 | ||
return 'riot.tag2(\'' + name + "'" + c + q(html) + c + q(css) + c + q(attrs) + | ||
', function(opts) {\n' + js + s | ||
} | ||
str = str.replace(/\s+/g, ' ') | ||
/** | ||
* Merge two javascript object extending the properties of the first one with | ||
* the second | ||
* @param {object} obj - source object | ||
* @param {object} props - extra properties | ||
* @returns {object} source object containing the new properties | ||
*/ | ||
function extend(obj, props) { | ||
for (var prop in props) { | ||
/* istanbul ignore next */ | ||
if (props.hasOwnProperty(prop)) { | ||
obj[prop] = props[prop] | ||
} | ||
} | ||
return obj | ||
} | ||
while (match = HTML_ATTR.exec(str)) { | ||
/** | ||
* Parses and format attributes. | ||
* | ||
* @param {string} str - Attributes, with expressions replaced by their hash | ||
* @returns {string} Formated attributes | ||
*/ | ||
function parseAttrs(str) { | ||
var | ||
list = [], | ||
match, | ||
k, v, | ||
DQ = '"' | ||
HTML_ATTR.lastIndex = 0 | ||
// all attribute names are converted to lower case | ||
k = match[1].toLowerCase() | ||
v = match[2] | ||
while (match = HTML_ATTR.exec(str)) { | ||
if (!v) { | ||
list.push(k) // boolean attribute without explicit value | ||
} | ||
else { | ||
// all attribute names are converted to lower case | ||
k = match[1].toLowerCase() | ||
v = match[2] | ||
// attribute values must be enclosed in double quotes | ||
if (v[0] !== DQ) | ||
v = DQ + (v[0] === "'" ? v.slice(1, -1) : v) + DQ | ||
if (!v) { | ||
list.push(k) // boolean attribute without explicit value | ||
if (k === 'type' && v.toLowerCase() === '"number"') { | ||
v = DQ + _bp[0] + "'number'" + _bp[1] + DQ // fix #827 by @rsbondi | ||
} | ||
else { | ||
// attribute values must be enclosed in double quotes | ||
if (v[0] !== DQ) | ||
v = DQ + (v[0] === "'" ? v.slice(1, -1) : v) + DQ | ||
if (k === 'type' && v.toLowerCase() === '"number"') { | ||
v = DQ + _bp[0] + "'number'" + _bp[1] + DQ // fix #827 by @rsbondi | ||
else if (/\u0001\d/.test(v)) { | ||
// renames special attributes with expressiones in their value. | ||
if (BOOL_ATTRS.test(k)) { | ||
k = '__' + k | ||
} | ||
else if (/\u0001\d/.test(v)) { | ||
// renames special attributes with expressiones in their value. | ||
if (BOOL_ATTRS.test(k)) { | ||
k = '__' + k | ||
} | ||
else if (~RIOT_ATTRS.indexOf(k)) { | ||
k = 'riot-' + k | ||
} | ||
else if (~RIOT_ATTRS.indexOf(k)) { | ||
k = 'riot-' + k | ||
} | ||
} | ||
// join the key-value pair, with no spaces between the parts | ||
list.push(k + '=' + v) | ||
} | ||
// join the key-value pair, with no spaces between the parts | ||
list.push(k + '=' + v) | ||
} | ||
return list.join(' ') // returns the attribute list | ||
} | ||
return list.join(' ') // returns the attribute list | ||
} | ||
// Replaces expressions in the HTML with a marker, and runs expressions | ||
// through the parser, except those beginning with `{^`. | ||
function splitHtml(html, opts, pcex) { | ||
// Replaces expressions in the HTML with a marker, and runs expressions | ||
// through the parser, except those beginning with `{^`. | ||
function splitHtml(html, opts, pcex) { | ||
var _bp = pcex._bp | ||
// `brackets.split` is a heavy function, so don't call it if not necessary | ||
if (html && _bp[$_RIX_TEST].test(html)) { | ||
var | ||
jsfn = opts.expr && (opts.parser || opts.type) ? compileJS : 0, | ||
list = brackets.split(html), | ||
expr | ||
// `brackets.split` is a heavy function, so don't call it if not necessary | ||
if (html && _bp[$_RIX_TEST].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) | ||
if (/;\s*$/.test(expr)) expr = expr.slice(0, expr.search(/;\s*$/)) | ||
} | ||
list[i] = '\u0001' + (pcex.push(expr.replace(/[\r\n]+/g, ' ').trim()) - 1) + _bp[1] | ||
for (var i = 1; i < list.length; i += 2) { | ||
expr = list[i] | ||
if (expr[0] === '^') | ||
expr = expr.slice(1) | ||
else if (jsfn) { | ||
var israw = expr[0] === '=' | ||
expr = jsfn(israw ? expr.slice(1) : expr, opts).trim() | ||
if (expr.slice(-1) === ';') expr = expr.slice(0, -1) | ||
if (israw) expr = '=' + expr | ||
} | ||
html = list.join('') | ||
list[i] = '\u0001' + (pcex.push(expr.replace(/[\r\n]+/g, ' ').trim()) - 1) + _bp[1] | ||
} | ||
return html | ||
html = list.join('') | ||
} | ||
return html | ||
} | ||
// Restores expressions hidden by splitHtml and escape literal internal brackets | ||
function restoreExpr(html, pcex) { | ||
if (pcex.length) { | ||
html = html | ||
.replace(/\u0001(\d+)/g, function (_, d) { | ||
return _bp[0] + pcex[d].replace(/"/g, '"') | ||
}) | ||
} | ||
return html | ||
// Restores expressions hidden by splitHtml and escape literal internal brackets | ||
function restoreExpr(html, pcex) { | ||
if (pcex.length) { | ||
html = html | ||
.replace(/\u0001(\d+)/g, function (_, d) { | ||
var expr = pcex[d] | ||
if (expr[0] === '=') { | ||
expr = expr.replace(brackets.R_STRINGS, function (qs) { | ||
return qs //.replace(/&/g, '&') // I don't know if this make sense | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
}) | ||
} | ||
return pcex._bp[0] + expr | ||
}) | ||
} | ||
return html | ||
} | ||
//## HTML Compilation | ||
//------------------- | ||
//## HTML Compilation | ||
//------------------- | ||
// `HTML_TAGS` matches only start and self-closing tags, not the content. | ||
var | ||
HTML_COMMENT = /<!--(?!>)[\S\s]*?-->/g, | ||
HTML_TAGS = /<([-\w]+)\s*([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)(\/?)>/g | ||
// `HTML_TAGS` matches only start and self-closing tags, not the content. | ||
var | ||
HTML_COMMENT = /<!--(?!>)[\S\s]*?-->/g, | ||
HTML_TAGS = /<([-\w]+)\s*([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)(\/?)>/g, | ||
PRE_TAG = _regEx( | ||
/<pre(?:\s+[^'">]+(?:(?:@Q)[^'">]*)*|\s*)?>([\S\s]*?)<\/pre\s*>/ | ||
.source.replace('@Q', brackets.R_STRINGS.source), 'gi') | ||
/** | ||
* Parses and formats the HTML text. | ||
* | ||
* @param {string} html - Can contain embedded HTML comments and literal whitespace | ||
* @param {Object} opts - Collected user options. Includes the brackets array in `_bp` | ||
* @param {Array} [pcex] - Keeps precompiled expressions | ||
* @param {number} [intc] - 1 if internal call | ||
* @returns {string} The parsed HTML text | ||
* @see http://www.w3.org/TR/html5/syntax.html | ||
*/ | ||
function compileHTML(html, opts, pcex, intc) { | ||
/** | ||
* Parses and formats the HTML text. | ||
* | ||
* @param {string} html - Can contain embedded HTML comments and literal whitespace | ||
* @param {Object} opts - Collected user options. Includes the brackets array in `_bp` | ||
* @param {Array} [pcex] - Keeps precompiled expressions | ||
* @param {number} [intc] - 1 if internal call | ||
* @returns {string} The parsed HTML text | ||
* @see http://www.w3.org/TR/html5/syntax.html | ||
*/ | ||
function compileHTML(html, opts, pcex /*, url*/) { | ||
// `_bp`is undefined when `compileHTML` is called from tests | ||
if (!intc) { | ||
_bp = brackets.array(opts.brackets) | ||
html = html.replace(/\r\n?/g, '\n').replace(HTML_COMMENT, '').replace(TRIM_TRAIL, '') | ||
} | ||
if (!pcex) pcex = [] | ||
var intf = (pcex || (pcex = []))._intflag | ||
if (!intf) | ||
html = html.replace(/\r\n?/g, '\n').replace(HTML_COMMENT, '').replace(TRIM_TRAIL, '') | ||
// separate the expressions, then parse the tags and their attributes | ||
html = splitHtml(html, opts, pcex) | ||
.replace(HTML_TAGS, function (_, name, attr, ends) { | ||
// force all tag names to lowercase | ||
name = name.toLowerCase() | ||
// close self-closing tag, except if this is a html5 void tag | ||
ends = ends && !VOID_TAGS.test(name) ? '></' + name : '' | ||
// format the attributes | ||
if (attr) name += ' ' + parseAttrs(attr) | ||
// `_bp` is undefined when `compileHTML` is not called by compile | ||
if (!pcex._bp) pcex._bp = brackets.array(opts.brackets) | ||
return '<' + name + ends + '>' | ||
}) | ||
// separate the expressions, then parse the tags and their attributes | ||
html = splitHtml(html, opts, pcex) | ||
.replace(HTML_TAGS, function (_, name, attr, ends) { | ||
// force all tag names to lowercase | ||
name = name.toLowerCase() | ||
// close self-closing tag, except if this is a html5 void tag | ||
ends = ends && !VOID_TAGS.test(name) ? '></' + name : '' | ||
// format the attributes | ||
if (attr) name += ' ' + parseAttrs(attr, pcex) | ||
// tags parsed, now compact whitespace if `opts.whitespace` is not set | ||
if (!opts.whitespace) { | ||
var p = [], | ||
pre = /<pre(?:\s+[^'">]+(?:(?:"[^"]*"|'[^']*')[^'">]*)*|\s*)>[\s\S]*<\/pre\s*>/gi | ||
return '<' + name + ends + '>' | ||
}) | ||
html = html.replace(pre, function (q) { | ||
return '\u0002' + (p.push(q) - 1) + '~' }).trim().replace(/\s+/g, ' ') | ||
// tags parsed, now compact whitespace if `opts.whitespace` is not set | ||
if (!opts.whitespace) { | ||
if (/<pre[\s>]/.test(html)) { | ||
var p = [] | ||
html = html.replace(PRE_TAG, function (q) | ||
{ return p.push(q) && '\u0002' }).trim().replace(/\s+/g, ' ') | ||
// istanbul ignore else | ||
if (p.length) | ||
html = html.replace(/\u0002(\d+)~/g, function (q, n) { return p[n] }) | ||
html = html.replace(/\u0002/g, function (_) { return p.shift() }) | ||
} | ||
else | ||
html = html.trim().replace(/\s+/g, ' ') | ||
} | ||
// for `opts.compact`, remove whitespace between tags | ||
if (opts.compact) html = html.replace(/> <([-\w\/])/g, '><$1') | ||
// for `opts.compact`, remove whitespace between tags | ||
if (opts.compact) html = html.replace(/> <([-\w\/])/g, '><$1') | ||
return restoreExpr(html, pcex) | ||
} | ||
return restoreExpr(html, pcex) | ||
} | ||
// JavaScript Compilation | ||
// ---------------------- | ||
// JavaScript Compilation | ||
// ---------------------- | ||
// JS_RMCOMMS prepares regexp for remotion of multiline and single-line comments | ||
// JS_ES6SIGN matches es6 methods across multiple lines up to their first curly brace | ||
var | ||
JS_RMCOMMS = _regEx('(' + brackets.S_QBLOCKS + ')|' + brackets.R_MLCOMMS.source + '|//[^\r\n]*', 'g'), | ||
JS_ES6SIGN = /^([ \t]*)([$_A-Za-z][$\w]*)\s*(\([^()]*\)\s*{)/m | ||
// Default parser for JavaScript code | ||
function riotjs(js) { | ||
var | ||
// Prepare regexp for remotion of multiline and single-line comments | ||
JS_RMCOMMS = _regEx( | ||
'(' + brackets.S_QBLOCKS + ')|' + brackets.R_MLCOMMS.source + '|//[^\r\n]*', | ||
'g'), | ||
// Matches es6 methods across multiple lines up to their first curly brace | ||
JS_ES6SIGN = /^([ \t]*)([$_A-Za-z][$\w]*)\s*(\([^()]*\)\s*{)/m | ||
match, | ||
toes5, | ||
parts = [], // parsed code | ||
pos | ||
// Default parser for JavaScript code | ||
function riotjs(js) { | ||
var | ||
match, | ||
toes5, | ||
parts = [], // parsed code | ||
pos | ||
// remove comments | ||
js = js.replace(JS_RMCOMMS, function (m, q) { return q ? m : ' ' }) | ||
// remove comments | ||
js = js.replace(JS_RMCOMMS, function (m, q) { return q ? m : ' ' }) | ||
// $1: indentation, | ||
// $2: method name, | ||
// $3: parameters | ||
while (match = js.match(JS_ES6SIGN)) { | ||
// $1: indentation, | ||
// $2: method name, | ||
// $3: parameters | ||
while (match = js.match(JS_ES6SIGN)) { | ||
// save remaining part now -- IE9 changes `rightContext` in `RegExp.test` | ||
parts.push(RegExp.leftContext) | ||
js = RegExp.rightContext | ||
pos = skipBlock(js) // find the closing bracket | ||
// save remaining part now -- IE9 changes `rightContext` in `RegExp.test` | ||
parts.push(RegExp.leftContext) | ||
js = RegExp.rightContext | ||
pos = skipBlock(js) // find the closing bracket | ||
// convert ES6 method signature to ES5 function | ||
toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(match[2]) | ||
if (toes5) | ||
match[0] = match[1] + 'this.' + match[2] + ' = function' + match[3] | ||
// convert ES6 method signature to ES5 function | ||
toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(match[2]) | ||
if (toes5) | ||
match[0] = match[1] + 'this.' + match[2] + ' = function' + match[3] | ||
parts.push(match[0], js.slice(0, pos)) | ||
js = js.slice(pos) | ||
if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)') | ||
} | ||
parts.push(match[0], js.slice(0, pos)) | ||
js = js.slice(pos) | ||
if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)') | ||
} | ||
return parts.length ? parts.join('') + js : js | ||
return parts.length ? parts.join('') + js : js | ||
// Inner helper - find the position following the closing bracket for the current block | ||
function skipBlock(str) { | ||
var | ||
re = _regEx('([{}])|' + brackets.S_QBLOCKS, 'g'), | ||
level = 1, | ||
match | ||
// Inner helper - find the position following the closing bracket for the current block | ||
function skipBlock(str) { | ||
var | ||
re = _regEx('([{}])|' + brackets.S_QBLOCKS, 'g'), | ||
level = 1, | ||
match | ||
while (level && (match = re.exec(str))) { | ||
if (match[1]) | ||
match[1] === '{' ? ++level : --level | ||
} | ||
return level ? str.length : re.lastIndex | ||
while (level && (match = re.exec(str))) { | ||
if (match[1]) | ||
match[1] === '{' ? ++level : --level | ||
} | ||
return level ? str.length : re.lastIndex | ||
} | ||
} | ||
/** | ||
* Runs the parser for the JavaScript code, defaults to `riotjs` | ||
* | ||
* @param {string} js - Buffer with the javascript code | ||
* @param {Object} opts - Options, can include a custom parser function | ||
* @param {string} type - Optional type for parser selection | ||
* @param {Object} parserOpts - Optional parser options | ||
* @returns {string} The parsed JavaScript code | ||
*/ | ||
function compileJS(js, opts, type, parserOpts) { | ||
if (!js) return '' | ||
if (!type) type = opts.type | ||
/** | ||
* Runs the parser for the JavaScript code, defaults to `riotjs` | ||
* | ||
* @param {string} js - Buffer with the javascript code | ||
* @param {Object} opts - Options, can include a custom parser function | ||
* @param {string} [type] - Optional type for parser selection | ||
* @param {Object} [parserOpts] - User options for the parser | ||
* @param {string} [url] - url of the file being compiled | ||
* @returns {string} The parsed JavaScript code | ||
*/ | ||
function compileJS(js, opts, type, parserOpts, url) { | ||
if (!js) return '' | ||
if (!type) type = opts.type | ||
var parser = opts.parser || (type ? parsers.js[type] : riotjs) | ||
if (!parser) | ||
throw new Error('JS parser not found: "' + type + '"') | ||
var parser = opts.parser || (type ? parsers.js[type] : riotjs) | ||
if (!parser) | ||
throw new Error('JS parser not found: "' + type + '"') | ||
return parser(js, parserOpts).replace(TRIM_TRAIL, '') | ||
} | ||
return parser(js, parserOpts, url).replace(TRIM_TRAIL, '') | ||
} | ||
// CSS Compilation | ||
// ---------------- | ||
// See http://www.w3.org/TR/CSS21/ | ||
// CSS Compilation | ||
// ---------------- | ||
// See http://www.w3.org/TR/CSS21/ | ||
// Prepare regex to match CSS selectors, excluding those beginning with '@'. | ||
var CSS_SELECTOR = _regEx('(}|{|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + brackets.R_STRINGS.source, 'g') | ||
// Prepare regex to match CSS selectors, excluding those beginning with '@'. | ||
var CSS_SELECTOR = _regEx('(}|{|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + brackets.R_STRINGS.source, 'g') | ||
// Parses styles enclosed in a "scoped" tag (`scoped` is deprecated in HTML5). | ||
// The "style" string is received without comments or surrounding spaces. | ||
function scopedCSS(tag, style) { | ||
var scope = ':scope' | ||
// Parses styles enclosed in a "scoped" tag (`scoped` is deprecated in HTML5). | ||
// The "style" string is received without comments or surrounding spaces. | ||
function scopedCSS(tag, style) { | ||
var scope = ':scope' | ||
return style.replace(CSS_SELECTOR, function (m, p1, p2) { | ||
// skip quoted strings | ||
if (!p2) return m | ||
return style.replace(CSS_SELECTOR, function (m, p1, p2) { | ||
// skip quoted strings | ||
if (!p2) return m | ||
// we have a selector list, parse each individually | ||
p2 = p2.replace(/[^,]+/g, function (sel) { | ||
var s = sel.trim() | ||
// skips the keywords and percents of css animations | ||
if (s && s !== 'from' && s !== 'to' && s.slice(-1) !== '%') { | ||
// replace the `:scope` pseudo-selector, where it is, with the root tag name; | ||
// if `:scope` was not included, add the tag name as prefix, and mirror all | ||
// to `[riot-tag]` | ||
if (s.indexOf(scope) < 0) s = scope + ' ' + s | ||
s = s.replace(scope, tag) + ',' + | ||
s.replace(scope, '[riot-tag="' + tag + '"]') | ||
} | ||
return sel.slice(-1) === ' ' ? s + ' ' : s // respect (a little) the user style | ||
}) | ||
// add the danling bracket char and return the processed selector list | ||
return p1 ? p1 + ' ' + p2 : p2 | ||
// we have a selector list, parse each individually | ||
p2 = p2.replace(/[^,]+/g, function (sel) { | ||
var s = sel.trim() | ||
// skips the keywords and percents of css animations | ||
if (s && s !== 'from' && s !== 'to' && s.slice(-1) !== '%') { | ||
// replace the `:scope` pseudo-selector, where it is, with the root tag name; | ||
// if `:scope` was not included, add the tag name as prefix, and mirror all | ||
// to `[riot-tag]` | ||
if (s.indexOf(scope) < 0) s = scope + ' ' + s | ||
s = s.replace(scope, tag) + ',' + | ||
s.replace(scope, '[riot-tag="' + tag + '"]') | ||
} | ||
return sel.slice(-1) === ' ' ? s + ' ' : s // respect (a little) the user style | ||
}) | ||
} | ||
// add the danling bracket char and return the processed selector list | ||
return p1 ? p1 + ' ' + p2 : p2 | ||
}) | ||
} | ||
/** | ||
* Runs the parser for style blocks. | ||
* For scoped styles, the `scopedCSS` function is called after any external parser. | ||
* | ||
* @param {string} style - Raw style block | ||
* @param {string} tag - The root tag name to which the style belongs | ||
* @param {string} [type] - One of `parsers.css`: jade, stylus, etc. | ||
* @param {boolean} [scoped] - `true` for scoped styles | ||
* @param {object} opts - get the custom parser options | ||
* @returns {string} The processed style block | ||
*/ | ||
function compileCSS(style, tag, type, scoped, opts) { | ||
/** | ||
* Runs the parser for style blocks. | ||
* For scoped styles, the `scopedCSS` function is called after any external parser. | ||
* | ||
* @param {string} style - Raw style block | ||
* @param {string} tag - The root tag name to which the style belongs | ||
* @param {string} [type] - One of `parsers.css`: jade, stylus, etc. | ||
* @param {boolean} [scoped] - `true` for scoped styles | ||
* @param {object} opts - get the custom parser options | ||
* @returns {string} The processed style block | ||
*/ | ||
function compileCSS(style, tag, type, scoped, opts) { | ||
if (type) { | ||
if (type === 'scoped-css') { // DEPRECATED | ||
scoped = true | ||
} | ||
else if (parsers.css[type]) { | ||
style = parsers.css[type](tag, style, opts) | ||
} | ||
else if (type !== 'css') { | ||
throw new Error('CSS parser not found: "' + type + '"') | ||
} | ||
if (type) { | ||
if (type === 'scoped-css') { // DEPRECATED | ||
scoped = true | ||
} | ||
else if (parsers.css[type]) { | ||
style = parsers.css[type](tag, style, opts) | ||
} | ||
else if (type !== 'css') { | ||
throw new Error('CSS parser not found: "' + type + '"') | ||
} | ||
} | ||
// remove comments, compact and trim whitespace | ||
style = style.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim() | ||
// remove comments, compact and trim whitespace | ||
style = style.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim() | ||
// translate scoped rules if nedded | ||
return scoped ? scopedCSS(tag, style) : style | ||
} | ||
// translate scoped rules if nedded | ||
return scoped ? scopedCSS(tag, style) : style | ||
} | ||
// The main compiler | ||
// ----------------- | ||
// The main compiler | ||
// ----------------- | ||
// Matches the 'type' attribute, for script and style tags | ||
var | ||
TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i, // don't handle escaped quotes :( | ||
MISC_ATTR = /\s*=\s*("(?:\\[\S\s]|[^"\\]*)*"|'(?:\\[\S\s]|[^'\\]*)*'|\{[^}]+}|\S+)/.source | ||
// TYPE_ATTR matches the 'type' attribute, for script and style tags | ||
var | ||
TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i, // don't handle escaped quotes :( | ||
MISC_ATTR = /\s*=\s*("(?:\\[\S\s]|[^"\\]*)*"|'(?:\\[\S\s]|[^'\\]*)*'|\{[^}]+}|\S+)/.source | ||
// Returns the value of the 'type' attribute, with the prefix "text/" removed. | ||
function getType(str) { | ||
// Returns the value of the 'type' attribute, with the prefix "text/" removed. | ||
function getType(str) { | ||
if (str) { | ||
var match = str.match(TYPE_ATTR) | ||
str = match && (match[2] || match[3]) | ||
} | ||
return str ? str.replace('text/', '') : '' | ||
if (str) { | ||
var match = str.match(TYPE_ATTR) | ||
str = match && (match[2] || match[3]) | ||
} | ||
return str ? str.replace('text/', '') : '' | ||
} | ||
// Returns the value of any attribute, or the empty string for missing attribute. | ||
function getAttr(str, name) { | ||
// Returns the value of any attribute, or the empty string for missing attribute. | ||
function getAttr(str, name) { | ||
if (str) { | ||
var | ||
re = _regEx('\\s' + name + MISC_ATTR, 'i'), | ||
match = str.match(re) | ||
str = match && match[1] | ||
if (str) | ||
return /^['"]/.test(str) ? str.slice(1, -1) : str | ||
} | ||
return '' | ||
if (str) { | ||
var | ||
re = _regEx('\\s' + name + MISC_ATTR, 'i'), | ||
match = str.match(re) | ||
str = match && match[1] | ||
if (str) | ||
return (/^['"]/).test(str) ? str.slice(1, -1) : str | ||
} | ||
return '' | ||
} | ||
// get the parser options from the options attribute | ||
function getParserOptions(attrs) { | ||
var opts = getAttr(attrs, 'options') | ||
// convert the string into a valid js object | ||
if (opts) opts = JSON.parse(opts) | ||
return opts | ||
} | ||
// get the parser options from the options attribute | ||
function getParserOptions(attrs) { | ||
var opts = getAttr(attrs, 'options') | ||
// convert the string into a valid js object | ||
if (opts) opts = JSON.parse(opts) | ||
return opts | ||
} | ||
// Runs the custom or default parser on the received JavaScript code. | ||
// The CLI version can read code from the file system (experimental) | ||
function getCode(code, opts, attrs, url) { | ||
var type = getType(attrs), | ||
parserOpts = getParserOptions(attrs) | ||
// Runs the custom or default parser on the received JavaScript code. | ||
// The CLI version can read code from the file system (experimental) | ||
function getCode(code, opts, attrs, url) { | ||
var type = getType(attrs), | ||
parserOpts = getParserOptions(attrs) | ||
//#if READ_JS_SRC | ||
//#if NODE | ||
if (url) { | ||
var src = getAttr(attrs, 'src') | ||
if (src && url) { | ||
if (src) { | ||
var | ||
charset = getAttr(attrs, 'charset'), | ||
file = path.resolve(path.dirname(url), src) | ||
code = require('fs').readFileSync(file, {encoding: charset || 'utf8'}) | ||
code = require('fs').readFileSync(file, charset || 'utf8') | ||
} | ||
//#endif | ||
return compileJS(code, opts, type, parserOpts) | ||
} | ||
//#endif | ||
return compileJS(code, opts, type, parserOpts, url) | ||
} | ||
// Matches HTML tag ending a line. This regex still can be fooled by code as: | ||
// ```js | ||
// x <y && y > | ||
// z | ||
// ``` | ||
var END_TAGS = /\/>\n|^<(?:\/[\w\-]+\s*|[\w\-]+(?:\s+(?:[-\w:\xA0-\xFF][\S\s]*?)?)?)>\n/ | ||
// Matches HTML tag ending a line. This regex still can be fooled by code as: | ||
// ```js | ||
// x <y && y > | ||
// z | ||
// ``` | ||
var END_TAGS = /\/>\n|^<(?:\/[\w\-]+\s*|[\w\-]+(?:\s+(?:[-\w:\xA0-\xFF][\S\s]*?)?)?)>\n/ | ||
function splitBlocks(str) { | ||
var k, m | ||
function splitBlocks(str) { | ||
var k, m | ||
/* istanbul ignore next: this if() can't be true, but just in case... */ | ||
if (str[str.length - 1] === '>') | ||
return [str, ''] | ||
/* istanbul ignore next: this if() can't be true, but just in case... */ | ||
if (str[str.length - 1] === '>') return [str, ''] | ||
k = str.lastIndexOf('<') // first probable open tag | ||
while (~k) { | ||
if (m = str.slice(k).match(END_TAGS)) { | ||
k += m.index + m[0].length | ||
return [str.slice(0, k), str.slice(k)] | ||
} | ||
k = str.lastIndexOf('<', k -1) | ||
k = str.lastIndexOf('<') // first probable open tag | ||
while (~k) { | ||
if (m = str.slice(k).match(END_TAGS)) { | ||
k += m.index + m[0].length | ||
return [str.slice(0, k), str.slice(k)] | ||
} | ||
return ['', str] | ||
k = str.lastIndexOf('<', k -1) | ||
} | ||
return ['', str] | ||
} | ||
// Runs the external HTML parser for the entire tag file | ||
function compileTemplate(lang, html, opts) { | ||
var parser = parsers.html[lang] | ||
// Runs the external HTML parser for the entire tag file | ||
function compileTemplate(html, url, lang, opts) { | ||
var parser = parsers.html[lang] | ||
if (!parser) | ||
throw new Error('Template parser not found: "' + lang + '"') | ||
if (!parser) | ||
throw new Error('Template parser not found: "' + lang + '"') | ||
return parser(html, opts) | ||
} | ||
return parser(html, opts, url) | ||
} | ||
/* | ||
CUST_TAG regex don't allow unquoted expressions containing the `>` operator. | ||
STYLE and SCRIPT disallows the operator `>` at all. | ||
/* | ||
CUST_TAG regex don't allow unquoted expressions containing the `>` operator. | ||
STYLE and SCRIPT disallows the operator `>` at all. | ||
The beta.4 CUST_TAG regex is fast, with RegexBuddy I get 76 steps and 14 backtracks on | ||
the test/specs/fixtures/treeview.tag :) but fails with nested tags of the same name :( | ||
With a greedy * operator, we have ~500 and 200bt, it is acceptable. So let's fix this. | ||
*/ | ||
The beta.4 CUST_TAG regex is fast, with RegexBuddy I get 76 steps and 14 backtracks on | ||
the test/specs/fixtures/treeview.tag :) but fails with nested tags of the same name :( | ||
With a greedy * operator, we have ~500 and 200bt, it is acceptable. So let's fix this. | ||
*/ | ||
// TODO: CUST_TAG fails with unescaped regex in the root attributes having '>' characters. | ||
// We need brackets.split here? | ||
var | ||
CUST_TAG = _regEx( | ||
/^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:@Q|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/ | ||
.source.replace('@Q', brackets.R_STRINGS.source), 'gim'), | ||
STYLE = /<style(\s+[^>]*)?>\n?([^<]*(?:<(?!\/style\s*>)[^<]*)*)<\/style\s*>/gi, | ||
SCRIPT = _regEx(STYLE.source.replace(/tyle/g, 'cript'), 'gi') | ||
/** | ||
* The main compiler processes all custom tags, one by one. | ||
* | ||
* In .tag files, a custom tag can span multiple lines, but there should be no other | ||
* external element sharing the starting and ending lines of the tag (HTML comments | ||
* inclusive) and should not be indented. | ||
* Custom tags in HTML files don't have this restriction. | ||
* | ||
* @param {string} src - String with zero or more custom riot tags. | ||
* @param {Object} [opts] - User options. | ||
* @param {string} [url] - Filename of the riot tag, prepended to the generated code. | ||
* @returns {string} JavaScript code to build the tag later, through riot.tag2 function. | ||
*/ | ||
function compile(src, opts, url) { | ||
var | ||
CUST_TAG = /^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\s\S]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/gim, | ||
STYLE = /<style(\s+[^>]*)?>\n?([^<]*(?:<(?!\/style\s*>)[^<]*)*)<\/style\s*>/gi, | ||
SCRIPT = _regEx(STYLE.source.replace(/tyle/g, 'cript'), 'gi') | ||
parts = [], | ||
exclude | ||
/* | ||
An alternate routine for finding custom tags, not depending in indentation. | ||
Find openning tag, must be the first token in a line, after any whitespace. | ||
if it is a self-close tag, i.e. ends with />. we are done. | ||
Set counter to 1. | ||
Find the next '<tag' or '</tag', if '<tag' found increment counter, else decrement. | ||
When counter reach 0 we are done. | ||
var re1 = _regEx('<' + tagName + '[\\s/>]', 'ig'), re2 = _regEx('</' + tagName + '[\\s>]', 'ig') | ||
re1.lastIndex = TAG_START.lastIndex | ||
while (match1 = re1.) | ||
*/ | ||
if (!opts) opts = {} | ||
/** | ||
* The main compiler processes all custom tags, one by one. | ||
* | ||
* In .tag files, a custom tag can span multiple lines, but there should be no other | ||
* external element sharing the starting and ending lines of the tag (HTML comments | ||
* inclusive) and should not be indented. | ||
* Custom tags in HTML files don't have this restriction. | ||
* | ||
* @param {string} src - String with zero or more custom riot tags. | ||
* @param {Object} [opts] - User options. | ||
* @param {string} [url] - Filename of the riot tag, prepended to the generated code. | ||
* @returns {string} JavaScript code to build the tag later, through riot.tag2 function. | ||
*/ | ||
function compile(src, opts, url) { | ||
var label, parts = [] | ||
//#if NODE | ||
// let's be sure that an url is always defined at least on node | ||
// this will allow the babeljs users using the .babelrc file to configure | ||
// their transpiler setup | ||
url = url || process.cwd() | ||
//#endif | ||
if (!opts) opts = {} | ||
exclude = opts.exclude || false | ||
function included(s) { return !(exclude && ~exclude.indexOf(s)) } | ||
// get a static brackets array for use on the entire source | ||
_bp = brackets.array(opts.brackets) | ||
// get a static brackets array for use on the entire source | ||
var _bp = brackets.array(opts.brackets) | ||
// run any custom html parser before the compilation | ||
if (opts.template) | ||
src = compileTemplate(opts.template, src, opts.templateOptions) | ||
// run any custom html parser before the compilation | ||
if (opts.template) | ||
src = compileTemplate(src, url, opts.template, opts.templateOptions) | ||
// riot #1070 -- isAbsolute does not exists in node 10.x | ||
//#if NODE | ||
/* istanbul ignore next */ | ||
label = !url ? '' : path.isAbsolute(url) ? path.relative('.', url) : url | ||
if (label) | ||
label = '//src: ' + label.replace(/\\/g, '/') + '\n' | ||
//#else | ||
label = url ? '//src: ' + url + '\n' : '' | ||
//#endif | ||
// normalize eols and start processing the tags | ||
src = src | ||
.replace(/\r\n?/g, '\n') | ||
.replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) { | ||
// normalize eols and start processing the tags | ||
src = label + src | ||
.replace(/\r\n?/g, '\n') | ||
.replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) { | ||
// content can have attributes first, then html markup with zero or more script or | ||
// style tags of different types, and finish with an untagged block of javascript code. | ||
var | ||
jscode = '', | ||
styles = '', | ||
html = '', | ||
pcex = [] | ||
// content can have attributes first, then html markup with zero or more script or | ||
// style tags of different types, and finish with an untagged block of javascript code. | ||
var | ||
jscode = '', | ||
styles = '', | ||
html = '', | ||
pcex = [] | ||
pcex._bp = _bp // local copy, in preparation for async compilation | ||
pcex._intflag = 1 | ||
tagName = tagName.toLowerCase() | ||
tagName = tagName.toLowerCase() | ||
// process the attributes, including their expressions | ||
attribs = !attribs ? '' : restoreExpr(parseAttrs(splitHtml(attribs, opts, pcex)), pcex) | ||
// process the attributes, including their expressions | ||
attribs = attribs && included('attribs') ? | ||
restoreExpr(parseAttrs(splitHtml(attribs, opts, pcex), pcex), pcex) : '' | ||
if (body2) body = body2 | ||
if (body2) body = body2 | ||
// remove comments and trim trailing whitespace | ||
if (body && (body = body.replace(HTML_COMMENT, '')) && /\S/.test(body)) { | ||
// remove comments and trim trailing whitespace | ||
if (body && (body = body.replace(HTML_COMMENT, '')) && /\S/.test(body)) { | ||
if (body2) | ||
html = compileHTML(body2, opts, pcex, 1) | ||
else { | ||
body = body.replace(_regEx('^' + indent, 'gm'), '') | ||
if (body2) { | ||
/* istanbul ignore next */ | ||
html = included('html') ? compileHTML(body2, opts, pcex, 1, url) : '' | ||
} | ||
else { | ||
body = body.replace(_regEx('^' + indent, 'gm'), '') | ||
// get and process the style blocks | ||
body = body.replace(STYLE, function (_, _attrs, _style) { | ||
var scoped = _attrs && /\sscoped(\s|=|$)/i.test(_attrs), | ||
csstype = getType(_attrs) || opts.style | ||
styles += (styles ? ' ' : '') + | ||
compileCSS(_style, tagName, csstype, scoped, getParserOptions(_attrs)) | ||
return '' | ||
}) | ||
// get and process the style blocks | ||
// now the script blocks | ||
body = body.replace(SCRIPT, function (_, _attrs, _script) { | ||
//#if READ_JS_SRC | ||
jscode += (jscode ? '\n' : '') + getCode(_script, opts, _attrs, url) | ||
//#else | ||
jscode += (jscode ? '\n' : '') + getCode(_script, opts, _attrs) | ||
//#endif | ||
return '' | ||
}) | ||
body = body.replace(STYLE, included('css') ? function (_, _attrs, _style) { | ||
var scoped = _attrs && /\sscoped(\s|=|$)/i.test(_attrs), | ||
csstype = getType(_attrs) || opts.style | ||
styles += (styles ? ' ' : '') + | ||
compileCSS(_style, tagName, csstype, scoped, getParserOptions(_attrs), url) | ||
return '' | ||
} : '') | ||
// separate the untagged javascript block from the html markup | ||
var blocks = splitBlocks(body.replace(TRIM_TRAIL, '')) | ||
// now the script blocks | ||
body = body.replace(SCRIPT, included('js') ? function (_, _attrs, _script) { | ||
jscode += (jscode ? '\n' : '') + getCode(_script, opts, _attrs, url) | ||
return '' | ||
} : '') | ||
// separate the untagged javascript block from the html markup | ||
var blocks = splitBlocks(body.replace(TRIM_TRAIL, '')) | ||
if (included('html')) { | ||
body = blocks[0] | ||
if (body) | ||
html = compileHTML(body, opts, pcex, 1) | ||
} | ||
if (included('js')) { | ||
body = blocks[1] | ||
if (/\S/.test(body)) | ||
jscode += (jscode ? '\n' : '') + compileJS(body, opts) | ||
jscode += (jscode ? '\n' : '') + compileJS(body, opts, null, null, url) | ||
} | ||
} | ||
} | ||
// give more consistency to the output | ||
jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : '' | ||
// give more consistency to the output | ||
jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : '' | ||
// replace the tag with a call to the riot.tag2 function and we are done | ||
if (opts.entities) { | ||
parts.push({ | ||
tagName: tagName, | ||
html: html, | ||
css: styles, | ||
attribs: attribs, | ||
js: jscode | ||
}) | ||
return '' | ||
} | ||
// replace the tag with a call to the riot.tag2 function and we are done | ||
return mktag(tagName, html, styles, attribs, jscode, pcex) | ||
}) | ||
// replace the tag with a call to the riot.tag2 function and we are done | ||
if (opts.entities) { | ||
parts.push({ | ||
tagName: tagName, | ||
html: html, | ||
css: styles, | ||
attribs: attribs, | ||
js: jscode | ||
}) | ||
return '' | ||
} | ||
return opts.entities ? parts : src | ||
} | ||
// replace the tag with a call to the riot.tag2 function and we are done | ||
return mktag(tagName, html, styles, attribs, jscode, pcex) | ||
}) | ||
//#if RIOT | ||
return compile | ||
if (opts.entities) return parts | ||
})() | ||
//#endif | ||
//#if NODE | ||
// Note: isAbsolute does not exists in node 10.x | ||
if (url && opts.debug) { | ||
/* istanbul ignore if */ | ||
if (path.isAbsolute(url)) url = path.relative('.', url) | ||
src = '//src: ' + url.replace(/\\/g, '/') + '\n' + src | ||
} | ||
//#endif | ||
return src | ||
} |
@@ -14,5 +14,6 @@ /** | ||
*/ | ||
//#if NODE | ||
function _try(name, req) { //eslint-disable-line complexity | ||
var parser | ||
//#if NODE | ||
function fn(r) { | ||
@@ -30,3 +31,3 @@ try { | ||
case 'es6': | ||
/* istanbul ignore next */ | ||
/* istanbul ignore next */ | ||
return fn('babel') || fn('babel-core') // versions 5.8x | ||
@@ -46,3 +47,2 @@ case 'babel': | ||
break | ||
/* istanbul ignore next */ | ||
case 'scss': | ||
@@ -56,6 +56,4 @@ case 'sass': | ||
} | ||
return fn(req) | ||
} | ||
parser = fn(req) | ||
//#else | ||
function _try(name, req) { //eslint-disable-line no-redeclare | ||
@@ -73,6 +71,11 @@ switch (name) { | ||
} | ||
return _mods[name] = window[req] | ||
} | ||
parser = window[req] | ||
if (!parser) | ||
throw new Error(req + ' parser not found.') | ||
//#endif | ||
return parser | ||
} | ||
// Returns a parser instance, null if the parser is not found. | ||
@@ -87,4 +90,8 @@ // Public through the parsers._get function. | ||
var _html = { | ||
jade: function (html, opts) { | ||
return _req('jade').render(html, extend({pretty: true, doctype: 'html'}, opts)) | ||
jade: function (html, opts, url) { | ||
return _req('jade').render(html, extend({ | ||
pretty: true, | ||
filename: url, | ||
doctype: 'html' | ||
}, opts)) | ||
} | ||
@@ -95,3 +102,3 @@ } | ||
//#if NODE | ||
sass: function(tag, css, opts) { // there's no standard sass for browsers | ||
sass: function(tag, css, opts, url) { // there's no standard sass for browsers | ||
var sass = _req('sass') | ||
@@ -106,3 +113,3 @@ | ||
}, | ||
scss: function(tag, css, opts) { // there's no standard sass for browsers | ||
scss: function(tag, css, opts, url) { // there's no standard sass for browsers | ||
var sass = _req('sass') | ||
@@ -117,3 +124,4 @@ | ||
}, | ||
less: function(tag, css, opts) { | ||
//#endif | ||
less: function(tag, css, opts, url) { | ||
var less = _req('less'), | ||
@@ -132,4 +140,3 @@ ret | ||
}, | ||
//#endif | ||
stylus: function (tag, css, opts) { | ||
stylus: function (tag, css, opts, url) { | ||
var | ||
@@ -144,12 +151,12 @@ stylus = _req('stylus'), nib = _req('nib') // optional nib support | ||
var _js = { | ||
none: function (js, opts) { | ||
none: function (js, opts, url) { | ||
return js | ||
}, | ||
livescript: function (js, opts) { | ||
livescript: function (js, opts, url) { | ||
return _req('livescript').compile(js, extend({bare: true, header: false}, opts)) | ||
}, | ||
typescript: function (js, opts) { | ||
typescript: function (js, opts, url) { | ||
return _req('typescript')(js, opts).replace(/\r\n?/g, '\n') | ||
}, | ||
es6: function (js, opts) { | ||
es6: function (js, opts, url) { | ||
return _req('es6').transform(js, extend({ | ||
@@ -159,11 +166,10 @@ blacklist: ['useStrict', 'strict', 'react'], sourceMaps: false, comments: false | ||
}, | ||
babel: function (js, opts) { | ||
js = 'function __parser_babel_wrapper__(){' + js + '}' | ||
babel: function (js, opts, url) { | ||
return _req('babel').transform(js, | ||
extend({ | ||
presets: ['es2015'] | ||
filename: url | ||
}, opts) | ||
).code.replace(/["']use strict["'];[\r\n]+/, '').slice(38, -2) | ||
).code | ||
}, | ||
coffee: function (js, opts) { | ||
coffee: function (js, opts, url) { | ||
return _req('coffee').compile(js, extend({bare: true}, opts)) | ||
@@ -179,5 +185,1 @@ } | ||
})() | ||
//#if RIOT | ||
riot.parsers = parsers | ||
//#endif |
@@ -52,3 +52,6 @@ | ||
function compileTag(src, opts, url) { | ||
globalEval(compile(src, opts, url)) | ||
var code = compile(src, opts) | ||
// Implements #1070 by @beders, for .tag files. | ||
if (url) code += '\n//# sourceURL=' + url + '.js' | ||
globalEval(code) | ||
if (!--scriptsAmount) done() | ||
@@ -55,0 +58,0 @@ } |
{ | ||
"name": "riot-compiler", | ||
"version": "2.3.13", | ||
"version": "2.3.16-beta", | ||
"description": "Compiler for riot .tag files", | ||
"main": "dist/compiler.js", | ||
"jsnext:main": "dist/es6.compiler.js", | ||
"directories": { | ||
@@ -31,11 +32,11 @@ "lib": "lib", | ||
"dependencies": { | ||
"riot-tmpl": "^2.3.12" | ||
"riot-tmpl": "^2.3.17" | ||
}, | ||
"devDependencies": { | ||
"coveralls": "^2.11.4", | ||
"eslint": "^1.7.3", | ||
"eslint": "^1.10.3", | ||
"expect.js": "^0.3.1", | ||
"istanbul": "^0.4.0", | ||
"jspreproc": "^0.2.4", | ||
"mocha": "^2.3.3" | ||
"istanbul": "^0.4.1", | ||
"jspreproc": "^0.2.5", | ||
"mocha": "^2.3.4" | ||
}, | ||
@@ -42,0 +43,0 @@ "author": "Muut, Inc. and other contributors", |
@@ -32,15 +32,15 @@ [![Build Status][travis-image]][travis-url] | ||
* es6 - For `babel` and `babel-core` v5.8.x and below | ||
* babel - For `babel-core` v6.x - You must `npm install babel-preset-es2015` too, for this works. | ||
* babel - For `babel-core` v6.x - You must `npm install babel-preset-es2015` too, for this to work. | ||
[travis-image]:https://img.shields.io/travis/riot/compiler.svg?style=flat-square | ||
[travis-url]:https://travis-ci.org/riot/compiler | ||
[license-image]:http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square | ||
[license-url]:LICENSE.txt | ||
[npm-version-image]:http://img.shields.io/npm/v/riot-compiler.svg?style=flat-square | ||
[npm-downloads-image]:http://img.shields.io/npm/dm/riot-compiler.svg?style=flat-square | ||
[npm-url]:https://npmjs.org/package/riot-compiler | ||
[coverage-image]:https://img.shields.io/coveralls/riot/compiler/master.svg?style=flat-square | ||
[coverage-url]:https://coveralls.io/r/riot/compiler/?branch=master | ||
[codeclimate-image]:https://img.shields.io/codeclimate/github/riot/compiler.svg?style=flat-square | ||
[codeclimate-url]:https://codeclimate.com/github/riot/compiler | ||
[travis-image]: https://img.shields.io/travis/riot/compiler.svg?style=flat-square | ||
[travis-url]: https://travis-ci.org/riot/compiler | ||
[license-image]: http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square | ||
[license-url]: LICENSE.txt | ||
[npm-version-image]: http://img.shields.io/npm/v/riot-compiler.svg?style=flat-square | ||
[npm-downloads-image]: http://img.shields.io/npm/dm/riot-compiler.svg?style=flat-square | ||
[npm-url]: https://npmjs.org/package/riot-compiler | ||
[coverage-image]: https://img.shields.io/coveralls/riot/compiler/master.svg?style=flat-square | ||
[coverage-url]: https://coveralls.io/r/riot/compiler/?branch=master | ||
[codeclimate-image]: https://codeclimate.com/github/riot/compiler/badges/gpa.svg | ||
[codeclimate-url]: https://codeclimate.com/github/riot/compiler |
//src: test/specs/fixtures/oneline.tag | ||
riot.tag2('oneline', '<p>one line</p>', '', '', function(opts) { | ||
riot.tag2('oneline1', '<p>one line</p>', '', 'id="1"', function(opts) { | ||
}); | ||
riot.tag2('oneline2', '', '', 'id="2"', function(opts) { | ||
}); |
//src: test/specs/fixtures/pre.tag | ||
riot.tag2('pre', '<pre>xyz\n cc\n ss</pre>', '', '', function(opts) { | ||
}); | ||
riot.tag2('pre-tag', '<pre>xyz\n cc\n ss</pre> <pre-fake>xyz cc ss</pre-fake> <pre x="{1>2}">xyz\n cc\n ss</pre>', '', '', function(opts) { | ||
}, '{ }'); |
@@ -1,2 +0,2 @@ | ||
riot.tag2('root-attribs', '', '', 'disabled="disabled" style="display:none" id="{_id}"', function(opts) { | ||
riot.tag2('root-attribs', '', '', '__disabled="{x<y}" style="display:none" data-x="{x>0 ? 1 : 0}" data-y="{y<0 ? -1 : 0}" data-s="{\'John\\\'s\'}" id="{_id}"', function(opts) { | ||
}, '{ }'); |
@@ -71,7 +71,7 @@ | ||
it('double quotes in expressions are converted to `"`', function () { | ||
testStr('<p x={ "a" } y="{2}">', '<p x="{"a"}" y="{2}">') | ||
testStr('<p x="{"a"}" y="{2}">', '<p x="{"a"}" y="{2}">') | ||
testStr('<p x=\'{"a"}\' y="{2}">', '<p x="{"a"}" y="{2}">') | ||
testStr('<p x="{""}">', '<p x="{""}">') | ||
it('nested double quotes are supported in expressions', function () { | ||
testStr('<p x={ "a" } y="{2}">', '<p x="{"a"}" y="{2}">') | ||
testStr('<p x="{"a"}" y="{2}">', '<p x="{"a"}" y="{2}">') | ||
testStr('<p x=\'{"a"}\' y="{2}">', '<p x="{"a"}" y="{2}">') | ||
testStr('<p x="{""}">', '<p x="{""}">') | ||
}) | ||
@@ -120,4 +120,13 @@ | ||
it('raw html detection through the `=` flag', function () { | ||
testStr( | ||
'<p>{= \'<\' + myElem + \' style="color: \' + myColor + \';">\\n Click me</\' + myElem + \'>\'}</p>', | ||
'<p>{= \'<\' + myElem + \' style="color: \' + myColor + \';">\\n Click me</\' + myElem + \'>\'}</p>') | ||
testStr( | ||
'<ul><li>{= ["foo", "bar"].join(\'<br/>\') }</li></ul>', | ||
'<ul><li>{= ["foo", "bar"].join(\'<br/>\')}</li></ul>') | ||
}) | ||
}) | ||
}) |
riot.tag2('babel', '<h3>{test}</h3>', '', '', function(opts) { | ||
var type = 'JavaScript'; | ||
this.test = 'This is ' + type; | ||
'use strict'; | ||
var _foo = require('foo'); | ||
var type = 'JavaScript'; | ||
this.test = 'This is ' + type; | ||
}, '{ }'); |
@@ -9,4 +9,4 @@ // | ||
var | ||
basedir = __dirname, | ||
jsdir = path.join(basedir, 'js') | ||
fixtures = __dirname, | ||
expected = path.join(fixtures, 'js') | ||
@@ -33,9 +33,14 @@ function have(mod, req) { | ||
function testParser(name, opts) { | ||
function testParser(name, opts, save) { | ||
var | ||
file = name + (opts.type ? '.' + opts.type : ''), | ||
str1 = cat(basedir, file + '.tag'), | ||
str2 = cat(jsdir, file + '.js') | ||
str1 = cat(fixtures, file + '.tag'), | ||
str2 = cat(expected, file + '.js') | ||
js = compiler.compile(str1, opts || {}, path.join(fixtures, file + '.tag')) | ||
expect(normalize(compiler.compile(str1, opts || {}))).to.be(normalize(str2)) | ||
if (save) | ||
fs.writeFile(path.join(expected, file + '_out.js'), js, function (err) { | ||
if (err) throw err | ||
}) | ||
expect(normalize(js)).to.be(normalize(str2)) | ||
} | ||
@@ -76,4 +81,4 @@ | ||
it('plays with quoted values', function () { | ||
testStr('<a href={ "a" }>', '<a href="{@ "a"}">', opts) | ||
testStr('<a>{"b"}</a>', '<a>{@"b"}</a>', opts) | ||
testStr('<a href={ "a" }>', '<a href="{@ "a"}">', opts) | ||
testStr('<a>{"b"}</a>', '<a>{@"b"}</a>', opts) | ||
}) | ||
@@ -102,3 +107,3 @@ | ||
this.timeout(25000) // first call to babel-core is slooooow! | ||
this.timeout(30000) // first call to babel-core is slooooow! | ||
@@ -244,7 +249,7 @@ // complex.tag | ||
it('Mixing CSS blocks with different type', function () { | ||
it('mixing CSS blocks with different type', function () { | ||
testParser('mixed-css', {}) | ||
}) | ||
it('The style option for setting the CSS parser (v2.3.13)', function () { | ||
it('the style option for setting the CSS parser (v2.3.13)', function () { | ||
var | ||
@@ -269,5 +274,5 @@ source = [ | ||
it('Unknown HTML template parser throws an error', function () { | ||
it('unknown HTML template parser throws an error', function () { | ||
var | ||
str1 = cat(basedir, 'test.tag') | ||
str1 = cat(fixtures, 'test.tag') | ||
@@ -277,5 +282,5 @@ expect(compiler.compile).withArgs(str1, {template: 'unknown'}).to.throwError() | ||
it('Unknown JS & CSS parsers throws an error', function () { | ||
it('unknown JS & CSS parsers throws an error', function () { | ||
var | ||
str1 = cat(basedir, 'test.tag'), | ||
str1 = cat(fixtures, 'test.tag'), | ||
str2 = [ | ||
@@ -297,2 +302,10 @@ '<error>', | ||
it('emiting raw html through the `=` flag, with parser', function () { | ||
// custom parser | ||
compiler.parsers.js.rawhtml = function(js) { | ||
return js.replace(/"/g, '"').replace(/'/g, '"') | ||
} | ||
testParser('raw', { type: 'rawhtml', expr: true }) | ||
}) | ||
}) |
@@ -22,3 +22,3 @@ var fs = require('fs'), | ||
function render(str, name) { | ||
return compiler.compile(str, {}, path.join(fixtures, name)) | ||
return compiler.compile(str, { debug: true }, path.join(fixtures, name)) | ||
} | ||
@@ -30,6 +30,10 @@ | ||
function testFile(name) { | ||
function testFile(name, save) { | ||
var src = cat(fixtures, name + '.tag'), | ||
js = render(src, name + '.tag') | ||
if (save) | ||
fs.writeFile(path.join(expected, name + '_out.js'), js, function (err) { | ||
if (err) throw err | ||
}) | ||
expect(js).to.equal(cat(expected, name + '.js')) | ||
@@ -120,2 +124,6 @@ } | ||
it('Whitespace is compacted in other parts', function() { | ||
testFile('whitespace') | ||
}) | ||
it('Empty tag', function () { | ||
@@ -138,4 +146,4 @@ testFile('empty') | ||
it('the `entities` option give access to the compiled parts', function () { | ||
var parts = compiler.compile(cat(fixtures, 'treeview.tag'), {entities: true}) | ||
it('The `entities` option give access to the compiled parts', function () { | ||
var parts = compiler.compile(cat(fixtures, 'treeview.tag'), {entities: true}), | ||
resarr = [ | ||
@@ -165,2 +173,54 @@ [ 'treeview', | ||
it('The `exclude` option to ignore parts of the tag', function () { | ||
var parts = compiler.compile(cat(fixtures, 'treeview.tag'), { | ||
entities: true, | ||
exclude: ['html', 'js'] | ||
}), | ||
dummyTag = [ | ||
'<my-tag>', | ||
'<p>{ hi }</p>', | ||
'this.hi = "hi"', | ||
'<style scoped>', | ||
' :scope { color: red; }', | ||
'</style>', | ||
'</my-tag>' | ||
].join('\n') | ||
expect(compiler.compile(dummyTag, { | ||
exclude: ['html'] | ||
})).to.be("riot.tag2('my-tag', '', 'my-tag,[riot-tag=\"my-tag\"] { color: red; }', '', function(opts) {\nthis.hi = \"hi\"\n\n});") | ||
expect(compiler.compile(dummyTag, { | ||
exclude: ['html', 'js'] | ||
})).to.be("riot.tag2('my-tag', '', 'my-tag,[riot-tag=\"my-tag\"] { color: red; }', '', function(opts) {\n});") | ||
expect(compiler.compile(dummyTag, { | ||
exclude: ['css'] | ||
})).to.be("riot.tag2('my-tag', '<p>{hi}</p>', '', '', function(opts) {\nthis.hi = \"hi\"\n\n}, '{ }');") | ||
expect(parts[0].html + parts[1].html).to.be('') | ||
expect(parts[0].js + parts[1].js).to.be('') | ||
parts = compiler.compile(cat(fixtures, 'scoped.tag'), { | ||
entities: true, | ||
exclude: ['css'] | ||
}) | ||
expect(parts[0].html).to.not.match(/style/) | ||
expect(parts[0].css).to.be('') | ||
parts = compiler.compile(cat(fixtures, 'root-attribs.tag'), { | ||
entities: true, | ||
exclude: ['attribs'] | ||
}) | ||
expect(parts[0].attribs).to.be('') | ||
}) | ||
it('Output an expression without evaluation by escaping the opening brace', function () { | ||
testFile('print-brackets') | ||
}) | ||
it('Escaping raw html in expressions through the `=` flag', function () { | ||
testFile('raw-html') | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
164644
128
3654
8
1
Updatedriot-tmpl@^2.3.17