riot_js-rails 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/lib/riot_js/rails/processors/sprockets_processor_v3.rb +4 -2
- data/lib/riot_js/rails/railtie.rb +2 -2
- data/lib/riot_js/rails/version.rb +1 -1
- data/vendor/assets/javascripts/compiler/brackets.js +252 -0
- data/vendor/assets/javascripts/compiler/compiler.js +968 -10
- data/vendor/assets/javascripts/compiler/parsers.js +195 -21
- data/vendor/assets/javascripts/riot.js +1632 -569
- metadata +3 -3
- data/vendor/assets/javascripts/compiler/compiler_core.js +0 -234
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80b0ba35335567e7fd7bbcf8817e9065ae6a22b3
|
4
|
+
data.tar.gz: 4a1c402cab48fbd2abde9560987698c3e79f9873
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c92c478dc4c74f8a0e34301173c4f3fa4fa6e36b9b2cee0fe9e93a269debb60ba0a655079a9352be55bb42c2dc8a36c7cfa33bf7487b5deb1fdaccca0fa93c7
|
7
|
+
data.tar.gz: e589230ca36f8124f435f7ec815a5850519ad0a2c02d5dc34b02286671f1e42c6ce630005bdfb0181ade0e56551cad4699ded2e7f407e63d189eb7538c103596
|
data/.gitignore
CHANGED
@@ -16,8 +16,10 @@ module RiotJs
|
|
16
16
|
@context.metadata.merge(data: data)
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.register_self(
|
20
|
-
|
19
|
+
def self.register_self(config)
|
20
|
+
config.assets.configure do |env|
|
21
|
+
env.register_engine '.tag', self, mime_type: 'application/javascript'
|
22
|
+
end
|
21
23
|
end
|
22
24
|
|
23
25
|
private
|
@@ -9,7 +9,7 @@ module RiotJs
|
|
9
9
|
config.riot.node_paths = []
|
10
10
|
|
11
11
|
initializer :setup_sprockets do |app|
|
12
|
-
Processor.register_self
|
12
|
+
Processor.register_self config
|
13
13
|
|
14
14
|
if defined?(::Haml)
|
15
15
|
require 'tilt/haml'
|
@@ -20,7 +20,7 @@ module RiotJs
|
|
20
20
|
initializer :add_helpers do |app|
|
21
21
|
helpers = %q{ include ::RiotJs::Rails::Helper }
|
22
22
|
::ActionView::Base.module_eval(helpers)
|
23
|
-
::Rails.application.assets.context_class.class_eval(helpers)
|
23
|
+
::Rails.application.config.assets.context_class.class_eval(helpers)
|
24
24
|
end
|
25
25
|
|
26
26
|
config.after_initialize do |app|
|
@@ -0,0 +1,252 @@
|
|
1
|
+
/**
|
2
|
+
* Brackets support for the node.js version of the riot-compiler
|
3
|
+
* @module
|
4
|
+
*/
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Matches valid, multiline JavaScript comments in almost all its forms.
|
8
|
+
* @const {RegExp}
|
9
|
+
* @static
|
10
|
+
*/
|
11
|
+
var R_MLCOMMS = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//g
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Matches single and double quoted strings. Don't care about inner EOLs, so it
|
15
|
+
* can be used to match HTML strings, but skips escaped quotes as JavaScript does.
|
16
|
+
* Useful to skip strings in values with expressions, e.g. `name={ 'John\'s' }`.
|
17
|
+
* @const {RegExp}
|
18
|
+
* @static
|
19
|
+
*/
|
20
|
+
var R_STRINGS = /"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'/g
|
21
|
+
|
22
|
+
/**
|
23
|
+
* The {@link module:brackets.R_STRINGS|R_STRINGS} source combined with sources of
|
24
|
+
* regexes matching division operators and literal regexes, for use with the RegExp
|
25
|
+
* constructor. The resulting regex captures in `$1` and `$2` a single slash, depending
|
26
|
+
* if it matches a division operator ($1) or a literal regex ($2).
|
27
|
+
* @const {string}
|
28
|
+
* @static
|
29
|
+
*/
|
30
|
+
var S_QBLOCKS = R_STRINGS.source + '|' +
|
31
|
+
/(?:\breturn\s+|(?:[$\w\)\]]|\+\+|--)\s*(\/)(?![*\/]))/.source + '|' +
|
32
|
+
/\/(?=[^*\/])[^[\/\\]*(?:(?:\[(?:\\.|[^\]\\]*)*\]|\\.)[^[\/\\]*)*?(\/)[gim]*/.source
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Hash of regexes for matching JavaScript brackets out of quoted strings and literal
|
36
|
+
* regexes. Used by {@link module:brackets.split|split}, these are heavy, but their
|
37
|
+
* performance is acceptable.
|
38
|
+
* @const {object}
|
39
|
+
*/
|
40
|
+
var FINDBRACES = {
|
41
|
+
'(': RegExp('([()])|' + S_QBLOCKS, 'g'),
|
42
|
+
'[': RegExp('([[\\]])|' + S_QBLOCKS, 'g'),
|
43
|
+
'{': RegExp('([{}])|' + S_QBLOCKS, 'g')
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* The predefined riot brackets
|
48
|
+
* @const {string}
|
49
|
+
* @default
|
50
|
+
*/
|
51
|
+
var DEFAULT = '{ }'
|
52
|
+
|
53
|
+
// Pre-made string and regexes for the default brackets
|
54
|
+
var _pairs = [
|
55
|
+
'{', '}',
|
56
|
+
'{', '}',
|
57
|
+
/{[^}]*}/,
|
58
|
+
/\\([{}])/g,
|
59
|
+
/\\({)|{/g,
|
60
|
+
RegExp('\\\\(})|([[({])|(})|' + S_QBLOCKS, 'g'),
|
61
|
+
DEFAULT
|
62
|
+
]
|
63
|
+
|
64
|
+
// Pre-made string and regexes for the last bracket pair
|
65
|
+
var _cache = []
|
66
|
+
|
67
|
+
/*
|
68
|
+
Private functions
|
69
|
+
---------------------------------------------------------------------------
|
70
|
+
*/
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Rewrite a regex with the default brackets replaced with the custom ones.
|
74
|
+
*
|
75
|
+
* @param {RegExp} re - RegExp with the default riot brackets
|
76
|
+
* @returns {RegExp} The new regex with the default brackets replaced.
|
77
|
+
*/
|
78
|
+
function _rewrite (re) {
|
79
|
+
return RegExp(
|
80
|
+
re.source.replace(/{/g, _cache[2]).replace(/}/g, _cache[3]), re.global ? 'g' : ''
|
81
|
+
)
|
82
|
+
}
|
83
|
+
|
84
|
+
/*
|
85
|
+
Exported methods and properties
|
86
|
+
---------------------------------------------------------------------------
|
87
|
+
*/
|
88
|
+
|
89
|
+
module.exports = {
|
90
|
+
R_STRINGS: R_STRINGS,
|
91
|
+
R_MLCOMMS: R_MLCOMMS,
|
92
|
+
S_QBLOCKS: S_QBLOCKS
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Splits the received string in its template text and expression parts using
|
97
|
+
* balanced brackets detection to avoid require escaped brackets from the users.
|
98
|
+
*
|
99
|
+
* _For internal use by the riot-compiler._
|
100
|
+
*
|
101
|
+
* @param {string} str - Template source to split, can be one expression
|
102
|
+
* @param {number} _ - unused
|
103
|
+
* @param {Array} _bp - Info of custom brackets to use
|
104
|
+
* @returns {Array} Array of alternating template text and expressions.
|
105
|
+
* If _str_ has one unique expression, returns two elements: `["", expression]`.
|
106
|
+
*/
|
107
|
+
module.exports.split = function split (str, _, _bp) {
|
108
|
+
/*
|
109
|
+
Template text is easy: closing brackets are ignored, all we have to do is find
|
110
|
+
the first unescaped bracket. The real work is with the expressions...
|
111
|
+
|
112
|
+
Expressions are not so easy. We can already ignore opening brackets, but finding
|
113
|
+
the correct closing bracket is tricky.
|
114
|
+
Strings and regexes can contain almost any combination of characters and we
|
115
|
+
can't deal with these complexity with our regexes, so let's hide and ignore
|
116
|
+
these. From there, all we need is to detect the bracketed parts and skip
|
117
|
+
them, as they contains most of the common characters used by riot brackets.
|
118
|
+
With that, we have a 90% reliability in the detection, although (hope few) some
|
119
|
+
custom brackets still requires to be escaped.
|
120
|
+
*/
|
121
|
+
var
|
122
|
+
parts = [], // holds the resulting parts
|
123
|
+
match, // reused by both outer and nested searches
|
124
|
+
isexpr, // we are in ttext (0) or expression (1)
|
125
|
+
start, // start position of current template or expression
|
126
|
+
pos, // current position (exec() result)
|
127
|
+
re = _bp[6] // start with *updated* regex for opening bracket
|
128
|
+
|
129
|
+
isexpr = start = re.lastIndex = 0 // re is reused, we must reset lastIndex
|
130
|
+
|
131
|
+
while (match = re.exec(str)) {
|
132
|
+
|
133
|
+
pos = match.index
|
134
|
+
|
135
|
+
if (isexpr) {
|
136
|
+
/*
|
137
|
+
$1: optional escape character,
|
138
|
+
$2: opening js bracket `{[(`,
|
139
|
+
$3: closing riot bracket,
|
140
|
+
$4 & $5: qblocks
|
141
|
+
*/
|
142
|
+
if (match[2]) { // if have a javascript opening bracket,
|
143
|
+
re.lastIndex = skipBraces(str, match[2], re.lastIndex)
|
144
|
+
continue // skip the bracketed block and loop
|
145
|
+
}
|
146
|
+
if (!match[3]) { // if don't have a closing bracket
|
147
|
+
continue // search again
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
/*
|
152
|
+
At this point, we expect an _unescaped_ openning bracket in $2 for text,
|
153
|
+
or a closing bracket in $3 for expression. $1 may be an backslash.
|
154
|
+
*/
|
155
|
+
if (!match[1]) { // ignore it if have an escape char
|
156
|
+
unescapeStr(str.slice(start, pos)) // push part, even if empty
|
157
|
+
start = re.lastIndex // next position is the new start
|
158
|
+
re = _bp[6 + (isexpr ^= 1)] // switch mode and swap regexp
|
159
|
+
re.lastIndex = start // update the regex pointer
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
if (str && start < str.length) { // push remaining part, if we have one
|
164
|
+
unescapeStr(str.slice(start))
|
165
|
+
}
|
166
|
+
|
167
|
+
return parts
|
168
|
+
|
169
|
+
/*
|
170
|
+
Inner Helpers for _split()
|
171
|
+
*/
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Stores the processed string in the array `parts`.
|
175
|
+
* Unescape escaped brackets from expressions.
|
176
|
+
*
|
177
|
+
* @param {string} s - can be template text or an expression
|
178
|
+
*/
|
179
|
+
function unescapeStr (s) {
|
180
|
+
if (isexpr) {
|
181
|
+
parts.push(s && s.replace(_bp[5], '$1'))
|
182
|
+
} else {
|
183
|
+
parts.push(s)
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
/**
|
188
|
+
* Find the closing JS bracket for the current block in the given string.
|
189
|
+
* Skips strings, regexes, and other inner blocks.
|
190
|
+
*
|
191
|
+
* @param {string} s - The searched buffer
|
192
|
+
* @param {string} ch - Opening bracket character
|
193
|
+
* @param {number} ix - Position inside `str` following the opening bracket
|
194
|
+
* @returns {number} Position following the closing bracket.
|
195
|
+
*
|
196
|
+
* @throws Will throw "Unbalanced brackets in ..." if the closing bracket is not found.
|
197
|
+
*/
|
198
|
+
function skipBraces (s, ch, ix) {
|
199
|
+
var
|
200
|
+
mm,
|
201
|
+
rr = FINDBRACES[ch]
|
202
|
+
|
203
|
+
rr.lastIndex = ix
|
204
|
+
ix = 1
|
205
|
+
while (mm = rr.exec(s)) {
|
206
|
+
if (mm[1] &&
|
207
|
+
!(mm[1] === ch ? ++ix : --ix)) break
|
208
|
+
}
|
209
|
+
|
210
|
+
if (ix) {
|
211
|
+
throw new Error('Unbalanced brackets in ...`' + s.slice(start) + '`?')
|
212
|
+
}
|
213
|
+
return rr.lastIndex
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
var
|
218
|
+
INVALIDCH = /[\x00-\x1F<>a-zA-Z0-9'",;\\]/, // invalid characters for brackets
|
219
|
+
ESCAPEDCH = /(?=[[\]()*+?.^$|])/g // this characters must be escaped
|
220
|
+
|
221
|
+
/**
|
222
|
+
* Returns an array with information for the given brackets using a cache for the
|
223
|
+
* last custom brackets pair required by the caller.
|
224
|
+
*
|
225
|
+
* _For internal use by the riot-compiler._
|
226
|
+
*
|
227
|
+
* @param {string} [pair=DEFAULT] - If used, take this pair as base
|
228
|
+
* @returns {Array} Information about the given brackets, in internal format.
|
229
|
+
*
|
230
|
+
* @throws Will throw "Unsupported brackets ..." if _pair_ contains invalid characters
|
231
|
+
* or is not separated by one space.
|
232
|
+
*/
|
233
|
+
module.exports.array = function array (pair) {
|
234
|
+
if (!pair || pair === DEFAULT) return _pairs
|
235
|
+
|
236
|
+
if (_cache[8] !== pair) {
|
237
|
+
_cache = pair.split(' ')
|
238
|
+
|
239
|
+
if (_cache.length !== 2 || INVALIDCH.test(pair)) {
|
240
|
+
throw new Error('Unsupported brackets "' + pair + '"')
|
241
|
+
}
|
242
|
+
_cache = _cache.concat(pair.replace(ESCAPEDCH, '\\').split(' '))
|
243
|
+
|
244
|
+
_cache[4] = _rewrite(_cache[1].length > 1 ? /{[\S\s]*?}/ : _pairs[4])
|
245
|
+
_cache[5] = _rewrite(/\\({|})/g)
|
246
|
+
_cache[6] = _rewrite(_pairs[6]) // for _split()
|
247
|
+
_cache[7] = RegExp('\\\\(' + _cache[3] + ')|([[({])|(' + _cache[3] + ')|' + S_QBLOCKS, 'g')
|
248
|
+
_cache[8] = pair
|
249
|
+
}
|
250
|
+
|
251
|
+
return _cache
|
252
|
+
}
|
@@ -1,16 +1,974 @@
|
|
1
|
-
|
1
|
+
/**
|
2
|
+
* The riot-compiler WIP
|
3
|
+
*
|
4
|
+
* @module compiler
|
5
|
+
* @version WIP
|
6
|
+
* @license MIT
|
7
|
+
* @copyright 2015 Muut Inc. + contributors
|
8
|
+
*/
|
9
|
+
'use strict'
|
2
10
|
|
3
|
-
|
4
|
-
|
11
|
+
var brackets = require('./brackets')
|
12
|
+
var parsers = require('./parsers')
|
13
|
+
var path = require('path')
|
5
14
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
15
|
+
/**
|
16
|
+
* Source for creating regexes matching valid quoted, single-line JavaScript strings.
|
17
|
+
* It recognizes escape characters, including nested quotes and line continuation.
|
18
|
+
* @const {string}
|
19
|
+
*/
|
20
|
+
var S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Source of {@link module:brackets.S_QBLOCKS|brackets.S_QBLOCKS} for creating regexes
|
24
|
+
* matching multiline HTML strings and/or skip literal strings inside expressions.
|
25
|
+
* @const {string}
|
26
|
+
* @todo Bad thing. It recognizes escaped quotes (incorrect for HTML strings) and multiline
|
27
|
+
* strings without line continuation `'\'` (incorrect for expressions). Needs to be fixed
|
28
|
+
* ASAP but the current logic requires it to parse expressions inside attribute values :[
|
29
|
+
*/
|
30
|
+
var S_STRINGS = brackets.R_STRINGS.source
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Matches pairs attribute=value, both quoted and unquoted.
|
34
|
+
* Names can contain almost all iso-8859-1 character set.
|
35
|
+
* Used by {@link module:compiler~parseAttribs|parseAttribs}, assume hidden
|
36
|
+
* expressions and compact spaces (no EOLs).
|
37
|
+
* @const {RegExp}
|
38
|
+
*/
|
39
|
+
var HTML_ATTRS = / *([-\w:\xA0-\xFF]+) ?(?:= ?('[^']*'|"[^"]*"|\S+))?/g
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Matches valid HTML comments (to remove) and JS strings/regexes (to skip).
|
43
|
+
* Used by [cleanSource]{@link module:compiler~cleanSource}.
|
44
|
+
* @const {RegExp}
|
45
|
+
*/
|
46
|
+
var HTML_COMMS = RegExp(/<!--(?!>)[\S\s]*?-->/.source + '|' + S_LINESTR, 'g')
|
47
|
+
|
48
|
+
/**
|
49
|
+
* HTML_TAGS matches opening and self-closing tags, not the content.
|
50
|
+
* Used by {@link module:compiler~_compileHTML|_compileHTML} after hidding
|
51
|
+
* the expressions.
|
52
|
+
*
|
53
|
+
* 2016-01-18: exclude `'\s'` from attr capture to avoid unnecessary call to
|
54
|
+
* {@link module:compiler~parseAttribs|parseAttribs}
|
55
|
+
* @const {RegExp}
|
56
|
+
*/
|
57
|
+
var HTML_TAGS = /<([-\w]+)(?:\s+([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)(\/?)>/g
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Matches boolean HTML attributes, prefixed with `"__"` in the riot tag definition.
|
61
|
+
* Used by {@link module:compiler~parseAttribs|parseAttribs} with lowercase names.
|
62
|
+
* With a long list like this, a regex is faster than `[].indexOf` in most browsers.
|
63
|
+
* @const {RegExp}
|
64
|
+
* @see [attributes.md](https://github.com/riot/compiler/blob/dev/doc/attributes.md)
|
65
|
+
*/
|
66
|
+
var BOOL_ATTRS = RegExp(
|
67
|
+
'^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' +
|
68
|
+
'compact|controls|default|formnovalidate|hidden|ismap|itemscope|loop|' +
|
69
|
+
'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' +
|
70
|
+
'selected|sortable|truespeed|typemustmatch)$')
|
71
|
+
|
72
|
+
/**
|
73
|
+
* These attributes give error when parsed on browsers with an expression in its value.
|
74
|
+
* Ex: `<img src={ exrp_value }>`.
|
75
|
+
* Used by {@link module:compiler~parseAttribs|parseAttribs} with lowercase names only.
|
76
|
+
* @const {Array}
|
77
|
+
* @see [attributes.md](https://github.com/riot/compiler/blob/dev/doc/attributes.md)
|
78
|
+
*/
|
79
|
+
var RIOT_ATTRS = ['style', 'src', 'd']
|
80
|
+
|
81
|
+
/**
|
82
|
+
* HTML5 void elements that cannot be auto-closed.
|
83
|
+
* @const {RegExp}
|
84
|
+
* @see {@link http://www.w3.org/TR/html-markup/syntax.html#syntax-elements}
|
85
|
+
* @see {@link http://www.w3.org/TR/html5/syntax.html#void-elements}
|
86
|
+
*/
|
87
|
+
var VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/
|
88
|
+
|
89
|
+
/**
|
90
|
+
* Matches `<pre>` elements to hide its content ($1) from whitespace compactation.
|
91
|
+
* Used by {@link module:compiler~_compileHTML|_compileHTML} after processing the
|
92
|
+
* attributes and the self-closing tags.
|
93
|
+
* @const {RegExp}
|
94
|
+
*/
|
95
|
+
var PRE_TAGS = /<pre(?:\s+(?:[^">]*|"[^"]*")*)?>([\S\s]+?)<\/pre\s*>/gi
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Matches values of the property 'type' of input elements which cause issues with
|
99
|
+
* invalid values in some browsers. These are compiled with an invalid type (an
|
100
|
+
* expression) so the browser defaults to `type="text"`. At runtime, the type is reset
|
101
|
+
* _after_ its value is replaced with the evaluated expression or an empty value.
|
102
|
+
* @const {RegExp}
|
103
|
+
*/
|
104
|
+
var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Matches trailing spaces and tabs by line.
|
108
|
+
* @const {RegExp}
|
109
|
+
*/
|
110
|
+
var TRIM_TRAIL = /[ \t]+$/gm
|
111
|
+
|
112
|
+
var
|
113
|
+
DQ = '"',
|
114
|
+
SQ = "'"
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Normalizes eols and removes HTML comments without touching the strings,
|
118
|
+
* avoiding unnecesary replacements.
|
119
|
+
* Skip the strings is less expansive than replacing with itself.
|
120
|
+
*
|
121
|
+
* @param {string} src - The HTML source with comments
|
122
|
+
* @returns {string} HTML without comments
|
123
|
+
* @since v2.3.22
|
124
|
+
*/
|
125
|
+
function cleanSource (src) {
|
126
|
+
var
|
127
|
+
mm,
|
128
|
+
re = HTML_COMMS
|
129
|
+
|
130
|
+
if (~src.indexOf('\r')) {
|
131
|
+
src = src.replace(/\r\n?/g, '\n')
|
132
|
+
}
|
133
|
+
|
134
|
+
re.lastIndex = 0
|
135
|
+
while (mm = re.exec(src)) {
|
136
|
+
if (mm[0][0] === '<') {
|
137
|
+
src = RegExp.leftContext + RegExp.rightContext
|
138
|
+
re.lastIndex = mm[3] + 1
|
139
|
+
}
|
140
|
+
}
|
141
|
+
return src
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Parses attributes. Force names to lowercase, enclose the values in double quotes,
|
146
|
+
* and compact spaces.
|
147
|
+
* Take care about issues in some HTML5 input elements with expressions in its value.
|
148
|
+
*
|
149
|
+
* @param {string} str - Attributes, with expressions replaced by their hash
|
150
|
+
* @param {Array} pcex - Has a `_bp` property with info about brackets
|
151
|
+
* @returns {string} Formated attributes
|
152
|
+
*/
|
153
|
+
function parseAttribs (str, pcex) {
|
154
|
+
var
|
155
|
+
list = [],
|
156
|
+
match,
|
157
|
+
type, vexp
|
158
|
+
|
159
|
+
HTML_ATTRS.lastIndex = 0
|
160
|
+
|
161
|
+
str = str.replace(/\s+/g, ' ')
|
162
|
+
|
163
|
+
while (match = HTML_ATTRS.exec(str)) {
|
164
|
+
var
|
165
|
+
k = match[1].toLowerCase(),
|
166
|
+
v = match[2]
|
167
|
+
|
168
|
+
if (!v) {
|
169
|
+
list.push(k)
|
170
|
+
} else {
|
171
|
+
|
172
|
+
if (v[0] !== DQ) {
|
173
|
+
v = DQ + (v[0] === SQ ? v.slice(1, -1) : v) + DQ
|
174
|
+
}
|
175
|
+
|
176
|
+
if (k === 'type' && SPEC_TYPES.test(v)) {
|
177
|
+
type = v
|
178
|
+
} else {
|
179
|
+
if (/\u0001\d/.test(v)) {
|
180
|
+
|
181
|
+
if (k === 'value') vexp = 1
|
182
|
+
else if (BOOL_ATTRS.test(k)) k = '__' + k
|
183
|
+
else if (~RIOT_ATTRS.indexOf(k)) k = 'riot-' + k
|
184
|
+
}
|
185
|
+
|
186
|
+
list.push(k + '=' + v)
|
187
|
+
}
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
if (type) {
|
192
|
+
if (vexp) type = DQ + pcex._bp[0] + SQ + type.slice(1, -1) + SQ + pcex._bp[1] + DQ
|
193
|
+
list.push('type=' + type)
|
194
|
+
}
|
195
|
+
return list.join(' ')
|
196
|
+
}
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Replaces expressions with a marker and runs expressions through the parser,
|
200
|
+
* if any, except those beginning with `"{^"` (hack for riot#1014 and riot#1090).
|
201
|
+
*
|
202
|
+
* @param {string} html - Raw html without comments
|
203
|
+
* @param {object} opts - The options, as passed to the compiler
|
204
|
+
* @param {Array} pcex - To store the extracted expressions
|
205
|
+
* @returns {string} html with its expressions replaced with markers
|
206
|
+
*
|
207
|
+
* @see {@link module:brackets.split|brackets.split}
|
208
|
+
*/
|
209
|
+
function splitHtml (html, opts, pcex) {
|
210
|
+
var _bp = pcex._bp
|
211
|
+
|
212
|
+
if (html && _bp[4].test(html)) {
|
213
|
+
var
|
214
|
+
jsfn = opts.expr && (opts.parser || opts.type) ? _compileJS : 0,
|
215
|
+
list = brackets.split(html, 0, _bp),
|
216
|
+
expr, israw
|
217
|
+
|
218
|
+
for (var i = 1; i < list.length; i += 2) {
|
219
|
+
expr = list[i]
|
220
|
+
if (expr[0] === '^') {
|
221
|
+
expr = expr.slice(1)
|
222
|
+
} else if (jsfn) {
|
223
|
+
israw = expr[0] === '='
|
224
|
+
expr = jsfn(israw ? expr.slice(1) : expr, opts).trim()
|
225
|
+
if (expr.slice(-1) === ';') expr = expr.slice(0, -1)
|
226
|
+
if (israw) expr = '=' + expr
|
227
|
+
}
|
228
|
+
list[i] = '\u0001' + (pcex.push(expr) - 1) + _bp[1]
|
229
|
+
}
|
230
|
+
html = list.join('')
|
231
|
+
}
|
232
|
+
return html
|
233
|
+
}
|
234
|
+
|
235
|
+
/**
|
236
|
+
* Cleans and restores hidden expressions encoding double quotes to prevent issues
|
237
|
+
* with browsers (breaks attributes) and encode `"<>"` for expressions with raw HTML.
|
238
|
+
*
|
239
|
+
* @param {string} html - The HTML source with hidden expresions
|
240
|
+
* @param {Array} pcex - Array with unformatted expressions
|
241
|
+
* @returns {string} HTML with clean expressions in its place.
|
242
|
+
*/
|
243
|
+
function restoreExpr (html, pcex) {
|
244
|
+
if (pcex.length) {
|
245
|
+
html = html
|
246
|
+
.replace(/\u0001(\d+)/g, function (_, d) {
|
247
|
+
var expr = pcex[d]
|
248
|
+
|
249
|
+
if (expr[0] === '=') {
|
250
|
+
expr = expr.replace(brackets.R_STRINGS, function (qs) {
|
251
|
+
return qs
|
252
|
+
.replace(/</g, '<')
|
253
|
+
.replace(/>/g, '>')
|
254
|
+
})
|
255
|
+
}
|
256
|
+
|
257
|
+
return pcex._bp[0] + expr.trim().replace(/[\r\n]+/g, ' ').replace(/"/g, '\u2057')
|
258
|
+
})
|
259
|
+
}
|
260
|
+
return html
|
261
|
+
}
|
262
|
+
|
263
|
+
/**
|
264
|
+
* The internal HTML compiler.
|
265
|
+
*
|
266
|
+
* @param {string} html - Raw HTML string
|
267
|
+
* @param {object} opts - Compilation options received by compile or compileHTML
|
268
|
+
* @param {Array} pcex - To store extracted expressions, must include `_bp`
|
269
|
+
* @returns {string} Parsed HTML code which can be used by `riot.tag2`.
|
270
|
+
*
|
271
|
+
* @see {@link http://www.w3.org/TR/html5/syntax.html}
|
272
|
+
*/
|
273
|
+
function _compileHTML (html, opts, pcex) {
|
274
|
+
|
275
|
+
html = splitHtml(html, opts, pcex)
|
276
|
+
.replace(HTML_TAGS, function (_, name, attr, ends) {
|
277
|
+
|
278
|
+
name = name.toLowerCase()
|
279
|
+
|
280
|
+
ends = ends && !VOID_TAGS.test(name) ? '></' + name : ''
|
281
|
+
|
282
|
+
if (attr) name += ' ' + parseAttribs(attr, pcex)
|
283
|
+
|
284
|
+
return '<' + name + ends + '>'
|
285
|
+
})
|
286
|
+
|
287
|
+
if (!opts.whitespace) {
|
288
|
+
var p = []
|
289
|
+
|
290
|
+
if (/<pre[\s>]/.test(html)) {
|
291
|
+
html = html.replace(PRE_TAGS, function (q) {
|
292
|
+
p.push(q)
|
293
|
+
return '\u0002'
|
294
|
+
})
|
295
|
+
}
|
296
|
+
|
297
|
+
html = html.trim().replace(/\s+/g, ' ')
|
298
|
+
|
299
|
+
if (p.length) html = html.replace(/\u0002/g, function () { return p.shift() })
|
300
|
+
}
|
301
|
+
|
302
|
+
if (opts.compact) html = html.replace(/>[ \t]+<([-\w\/])/g, '><$1')
|
303
|
+
|
304
|
+
return restoreExpr(html, pcex).replace(TRIM_TRAIL, '')
|
305
|
+
}
|
306
|
+
|
307
|
+
/**
|
308
|
+
* Public interface to the internal HTML compiler, parses and formats the HTML part.
|
309
|
+
*
|
310
|
+
* - Runs each expression through the parser and replace it with a marker
|
311
|
+
* - Removes trailing tab and spaces
|
312
|
+
* - Normalizes and formats the attribute-value pairs
|
313
|
+
* - Closes self-closing tags
|
314
|
+
* - Normalizes and restores the expressions
|
315
|
+
*
|
316
|
+
* @param {string} html - Can contain embedded HTML comments and literal whitespace
|
317
|
+
* @param {Object} [opts] - User options.
|
318
|
+
* @param {Array} [pcex] - To store precompiled expressions
|
319
|
+
* @returns {string} The parsed HTML markup, which can be used by `riot.tag2`
|
320
|
+
* @static
|
321
|
+
*/
|
322
|
+
function compileHTML (html, opts, pcex) {
|
323
|
+
|
324
|
+
if (Array.isArray(opts)) {
|
325
|
+
pcex = opts
|
326
|
+
opts = {}
|
327
|
+
} else {
|
328
|
+
if (!pcex) pcex = []
|
329
|
+
if (!opts) opts = {}
|
330
|
+
}
|
331
|
+
|
332
|
+
pcex._bp = brackets.array(opts.brackets)
|
333
|
+
|
334
|
+
return _compileHTML(cleanSource(html), opts, pcex)
|
335
|
+
}
|
336
|
+
|
337
|
+
/**
|
338
|
+
* Matches ES6 methods across multiple lines up to its first curly brace.
|
339
|
+
* Used by the {@link module:compiler~riotjs|riotjs} parser.
|
340
|
+
*
|
341
|
+
* 2016-01-18: rewritten to capture only the method name (performant)
|
342
|
+
* @const {RegExp}
|
343
|
+
*/
|
344
|
+
var JS_ES6SIGN = /^[ \t]*([$_A-Za-z][$\w]*)\s*\([^()]*\)\s*{/m
|
345
|
+
|
346
|
+
/**
|
347
|
+
* Regex for remotion of multiline and single-line JavaScript comments, merged with
|
348
|
+
* {@link module:brackets.S_QBLOCKS|brackets.S_QBLOCKS} to skip literal string and regexes.
|
349
|
+
* Used by the {@link module:compiler~riotjs|riotjs} parser.
|
350
|
+
*
|
351
|
+
* 2016-01-18: rewritten to not capture the brackets (reduces 9 steps)
|
352
|
+
* @const {RegExp}
|
353
|
+
*/
|
354
|
+
var JS_ES6END = RegExp('[{}]|' + brackets.S_QBLOCKS, 'g')
|
355
|
+
|
356
|
+
/**
|
357
|
+
* Regex for remotion of multiline and single-line JavaScript comments, merged with
|
358
|
+
* {@link module:brackets.S_QBLOCKS|brackets.S_QBLOCKS} to skip literal string and regexes.
|
359
|
+
* @const {RegExp}
|
360
|
+
*/
|
361
|
+
var JS_COMMS = RegExp(brackets.R_MLCOMMS.source + '|//[^\r\n]*|' + brackets.S_QBLOCKS, 'g')
|
362
|
+
|
363
|
+
/**
|
364
|
+
* Default parser for JavaScript, supports ES6-like method syntax
|
365
|
+
*
|
366
|
+
* @param {string} js - Raw JavaScript code
|
367
|
+
* @returns {string} Code with ES6 methods converted to ES5, comments removed.
|
368
|
+
*/
|
369
|
+
function riotjs (js) {
|
370
|
+
var
|
371
|
+
parts = [],
|
372
|
+
match,
|
373
|
+
toes5,
|
374
|
+
pos,
|
375
|
+
name,
|
376
|
+
RE = RegExp
|
377
|
+
|
378
|
+
if (~js.indexOf('/')) js = rmComms(js, JS_COMMS)
|
379
|
+
|
380
|
+
while (match = js.match(JS_ES6SIGN)) {
|
381
|
+
|
382
|
+
parts.push(RE.leftContext)
|
383
|
+
js = RE.rightContext
|
384
|
+
pos = skipBody(js, JS_ES6END)
|
385
|
+
|
386
|
+
name = match[1]
|
387
|
+
toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(name)
|
388
|
+
name = toes5 ? match[0].replace(name, 'this.' + name + ' = function') : match[0]
|
389
|
+
parts.push(name, js.slice(0, pos))
|
390
|
+
js = js.slice(pos)
|
391
|
+
|
392
|
+
if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)')
|
393
|
+
}
|
394
|
+
|
395
|
+
return parts.length ? parts.join('') + js : js
|
396
|
+
|
397
|
+
function rmComms (s, r, m) {
|
398
|
+
r.lastIndex = 0
|
399
|
+
while (m = r.exec(s)) {
|
400
|
+
if (m[0][0] === '/' && !m[1] && !m[2]) {
|
401
|
+
s = RE.leftContext + ' ' + RE.rightContext
|
402
|
+
r.lastIndex = m[3] + 1
|
403
|
+
}
|
404
|
+
}
|
405
|
+
return s
|
406
|
+
}
|
407
|
+
|
408
|
+
function skipBody (s, r) {
|
409
|
+
var m, i = 1
|
410
|
+
|
411
|
+
r.lastIndex = 0
|
412
|
+
while (i && (m = r.exec(s))) {
|
413
|
+
if (m[0] === '{') ++i
|
414
|
+
else if (m[0] === '}') --i
|
415
|
+
}
|
416
|
+
return i ? s.length : r.lastIndex
|
417
|
+
}
|
418
|
+
}
|
419
|
+
|
420
|
+
/**
|
421
|
+
* Internal JavaScript compilation.
|
422
|
+
*
|
423
|
+
* @param {string} js - Raw JavaScript code
|
424
|
+
* @param {object} opts - Compiler options
|
425
|
+
* @param {string} type - Parser name, one of {@link module:parsers.js|parsers.js}
|
426
|
+
* @param {object} parserOpts - User options passed to the parser
|
427
|
+
* @param {string} url - Of the file being compiled, passed to the parser
|
428
|
+
* @returns {string} Compiled code, eols normalized, trailing spaces removed
|
429
|
+
*
|
430
|
+
* @throws Will throw "JS parser not found" if the JS parser cannot be loaded.
|
431
|
+
* @see {@link module:compiler.compileJS|compileJS}
|
432
|
+
*/
|
433
|
+
function _compileJS (js, opts, type, parserOpts, url) {
|
434
|
+
if (!/\S/.test(js)) return ''
|
435
|
+
if (!type) type = opts.type
|
436
|
+
|
437
|
+
var parser = opts.parser || (type ? parsers.js[type] : riotjs)
|
438
|
+
|
439
|
+
if (!parser) {
|
440
|
+
throw new Error('JS parser not found: "' + type + '"')
|
441
|
+
}
|
442
|
+
return parser(js, parserOpts, url).replace(/\r\n?/g, '\n').replace(TRIM_TRAIL, '')
|
443
|
+
}
|
444
|
+
|
445
|
+
/**
|
446
|
+
* Public interface to the internal JavaScript compiler, runs the parser with
|
447
|
+
* the JavaScript code, defaults to `riotjs`.
|
448
|
+
*
|
449
|
+
* - If the given code is empty or whitespaces only, returns an empty string
|
450
|
+
* - Determines the parser to use, by default the internal riotjs function
|
451
|
+
* - Call the parser, the default {@link module:compiler~riotjs|riotjs} removes comments,
|
452
|
+
* converts ES6 method signatures to ES5 and bind to `this` if neccesary
|
453
|
+
* - Normalizes line-endings and trims trailing spaces before return the result
|
454
|
+
*
|
455
|
+
* @param {string} js - Buffer with the javascript code
|
456
|
+
* @param {Object} [opts] - Compiler options (DEPRECATED parameter, don't use it)
|
457
|
+
* @param {string} [type=riotjs] - Parser name, one of {@link module:parsers.js|parsers.js}
|
458
|
+
* @param {Object} [userOpts={}] - User options
|
459
|
+
* @param {string} [userOpts.url=process.cwd] - Url of the .tag file (passed to the parser)
|
460
|
+
* @param {object} [userOpts.parserOpts={}] - User options (passed to the parser)
|
461
|
+
* @returns {string} Parsed JavaScript, eols normalized, trailing spaces removed
|
462
|
+
* @static
|
463
|
+
*
|
464
|
+
* @see {@link module:compiler~_compileJS|_compileJS}
|
465
|
+
*/
|
466
|
+
function compileJS (js, opts, type, userOpts) {
|
467
|
+
if (typeof opts === 'string') {
|
468
|
+
userOpts = type
|
469
|
+
type = opts
|
470
|
+
opts = {}
|
471
|
+
}
|
472
|
+
if (type && typeof type === 'object') {
|
473
|
+
userOpts = type
|
474
|
+
type = ''
|
475
|
+
}
|
476
|
+
if (!userOpts) userOpts = {}
|
477
|
+
|
478
|
+
return _compileJS(js, opts || {}, type, userOpts.parserOptions, userOpts.url)
|
479
|
+
}
|
480
|
+
|
481
|
+
var CSS_SELECTOR = RegExp('([{}]|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + S_LINESTR, 'g')
|
482
|
+
|
483
|
+
/**
|
484
|
+
* Parses styles enclosed in a "scoped" tag (`scoped` was removed from HTML5).
|
485
|
+
* The "css" string is received without comments or surrounding spaces.
|
486
|
+
*
|
487
|
+
* @param {string} tag - Tag name of the root element
|
488
|
+
* @param {string} css - The CSS code
|
489
|
+
* @returns {string} CSS with the styles scoped to the root element
|
490
|
+
*/
|
491
|
+
function scopedCSS (tag, css) {
|
492
|
+
var scope = ':scope'
|
493
|
+
|
494
|
+
return css.replace(CSS_SELECTOR, function (m, p1, p2) {
|
495
|
+
|
496
|
+
if (!p2) return m
|
497
|
+
|
498
|
+
p2 = p2.replace(/[^,]+/g, function (sel) {
|
499
|
+
var s = sel.trim()
|
500
|
+
|
501
|
+
if (!s || s === 'from' || s === 'to' || s.slice(-1) === '%') {
|
502
|
+
return sel
|
503
|
+
}
|
504
|
+
|
505
|
+
if (s.indexOf(scope) < 0) {
|
506
|
+
s = tag + ' ' + s + ',[riot-tag="' + tag + '"] ' + s
|
507
|
+
} else {
|
508
|
+
s = s.replace(scope, tag) + ',' +
|
509
|
+
s.replace(scope, '[riot-tag="' + tag + '"]')
|
510
|
+
}
|
511
|
+
return sel.slice(-1) === ' ' ? s + ' ' : s
|
512
|
+
})
|
513
|
+
|
514
|
+
return p1 ? p1 + ' ' + p2 : p2
|
515
|
+
})
|
516
|
+
}
|
517
|
+
|
518
|
+
/**
|
519
|
+
* Internal CSS compilation.
|
520
|
+
* Runs any parser for style blocks and calls scopedCSS if required.
|
521
|
+
*
|
522
|
+
* @param {string} css - Raw CSS
|
523
|
+
* @param {string} tag - Tag name to which the style belongs to
|
524
|
+
* @param {string} [type=css] - Parser name to run
|
525
|
+
* @param {object} [opts={}] - User options
|
526
|
+
* @returns {string} The compiled style, whitespace compacted and trimmed
|
527
|
+
*
|
528
|
+
* @throws Will throw "CSS parser not found" if the CSS parser cannot be loaded.
|
529
|
+
* @throws Using the _scoped_ option with no tagName will throw an error.
|
530
|
+
* @see {@link module:compiler.compileCSS|compileCSS}
|
531
|
+
*/
|
532
|
+
function _compileCSS (css, tag, type, opts) {
|
533
|
+
var scoped = (opts || (opts = {})).scoped
|
534
|
+
|
535
|
+
if (type) {
|
536
|
+
if (type === 'scoped-css') {
|
537
|
+
scoped = true
|
538
|
+
} else if (parsers.css[type]) {
|
539
|
+
css = parsers.css[type](tag, css, opts.parserOpts || {}, opts.url)
|
540
|
+
} else if (type !== 'css') {
|
541
|
+
throw new Error('CSS parser not found: "' + type + '"')
|
542
|
+
}
|
543
|
+
}
|
544
|
+
|
545
|
+
css = css.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim()
|
546
|
+
|
547
|
+
if (scoped) {
|
548
|
+
if (!tag) {
|
549
|
+
throw new Error('Can not parse scoped CSS without a tagName')
|
550
|
+
}
|
551
|
+
css = scopedCSS(tag, css)
|
552
|
+
}
|
553
|
+
return css
|
554
|
+
}
|
555
|
+
|
556
|
+
/**
|
557
|
+
* Public API, wrapper of the internal {@link module:compiler~_compileCSS|_compileCSS}
|
558
|
+
* function, rearranges its parameters and makes these can be omitted.
|
559
|
+
*
|
560
|
+
* - If the given code is empty or whitespaces only, returns an empty string
|
561
|
+
* - Determines the parser to use, none by default
|
562
|
+
* - Call the parser, if any
|
563
|
+
* - Normalizes line-endings and trims trailing spaces
|
564
|
+
* - Call the {@link module:compiler~scopedCSS|scopedCSS} function if required by the
|
565
|
+
* parameter _opts_
|
566
|
+
*
|
567
|
+
* @param {string} css - Raw style block
|
568
|
+
* @param {string} [type] - Parser name, one of {@link module:parsers.css|parsers.css}
|
569
|
+
* @param {object} [opts] - User options
|
570
|
+
* @param {boolean} [opts.scoped] - Convert to Scoped CSS (requires _tagName_)
|
571
|
+
* @param {string} [opts.tagName] - Name of the root tag owner of the styles
|
572
|
+
* @param {string} [opts.url=process.cwd] - Url of the .tag file
|
573
|
+
* @param {object} [opts.parserOpts={}] - Options for the parser
|
574
|
+
* @returns {string} The processed style block
|
575
|
+
* @static
|
576
|
+
*
|
577
|
+
* @see {@link module:compiler~_compileCSS|_compileCSS}
|
578
|
+
*/
|
579
|
+
function compileCSS (css, type, opts) {
|
580
|
+
if (type && typeof type === 'object') {
|
581
|
+
opts = type
|
582
|
+
type = ''
|
583
|
+
} else if (!opts) opts = {}
|
584
|
+
|
585
|
+
return _compileCSS(css, opts.tagName, type, opts)
|
586
|
+
}
|
587
|
+
|
588
|
+
/**
|
589
|
+
* The "defer" attribute is used to ignore `script` elements (useful for SSR).
|
590
|
+
*
|
591
|
+
* This regex is used by {@link module:compiler~getCode|getCode} to check and by
|
592
|
+
* {@link module:compiler.compile|compile} to remove the keyword `defer` from
|
593
|
+
* `<script>` tags.
|
594
|
+
* @const {RegExp}
|
595
|
+
*/
|
596
|
+
var DEFER_ATTR = /\sdefer(?=\s|>|$)/i
|
597
|
+
|
598
|
+
/**
|
599
|
+
* Matches attributes 'type=value', for `<script>` and `<style>` tags.
|
600
|
+
* This regex does not expect expressions nor escaped quotes.
|
601
|
+
* @const {RegExp}
|
602
|
+
*/
|
603
|
+
var TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i
|
604
|
+
|
605
|
+
/**
|
606
|
+
* Source string for creating generic regexes matching pairs of `attribute=value`. Used
|
607
|
+
* by {@link module:compiler~getAttrib|getAttrib} for the `option`, `src`, and `charset`
|
608
|
+
* attributes, handles escaped quotes and unquoted JSON objects with no nested brackets.
|
609
|
+
* @const {string}
|
610
|
+
*/
|
611
|
+
var MISC_ATTR = '\\s*=\\s*(' + S_STRINGS + '|{[^}]+}|\\S+)'
|
612
|
+
|
613
|
+
/**
|
614
|
+
* Matches the last HTML tag ending a line. This can be one of:
|
615
|
+
* - self-closing tag
|
616
|
+
* - closing tag
|
617
|
+
* - tag without attributes
|
618
|
+
* - void tag with (optional) attributes
|
619
|
+
*
|
620
|
+
* Be aware that this regex still can be fooled by strange code like:
|
621
|
+
* ```js
|
622
|
+
* x <y -y>
|
623
|
+
* z
|
624
|
+
* ```
|
625
|
+
* @const {RegExp}
|
626
|
+
*/
|
627
|
+
var END_TAGS = /\/>\n|^<(?:\/?[-\w]+\s*|[-\w]+\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/
|
628
|
+
|
629
|
+
/**
|
630
|
+
* Encloses the given string in single quotes.
|
631
|
+
*
|
632
|
+
* 2016-01-18: we must escape single quotes and backslashes before quoting the
|
633
|
+
* string, but there's no need to care about line-endings unless is required,
|
634
|
+
* as each submodule normalizes the lines.
|
635
|
+
*
|
636
|
+
* @param {string} s - The unquoted, source string
|
637
|
+
* @param {number} r - If 1, escape embeded EOLs in the source
|
638
|
+
* @returns {string} Quoted string, with escaped single-quotes and backslashes.
|
639
|
+
*/
|
640
|
+
function _q (s, r) {
|
641
|
+
if (!s) return "''"
|
642
|
+
s = SQ + s.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + SQ
|
643
|
+
return r && ~s.indexOf('\n') ? s.replace(/\n/g, '\\n') : s
|
644
|
+
}
|
645
|
+
|
646
|
+
/**
|
647
|
+
* Generates code to call the `riot.tag2` function with the processed parts.
|
648
|
+
*
|
649
|
+
* @param {string} name - The tag name
|
650
|
+
* @param {string} html - HTML (can contain embeded eols)
|
651
|
+
* @param {string} css - Styles
|
652
|
+
* @param {string} attribs - Root attributes
|
653
|
+
* @param {string} js - JavaScript "constructor"
|
654
|
+
* @param {Array} pcex - Expressions
|
655
|
+
* @returns {string} Code to call `riot.tag2`
|
656
|
+
*/
|
657
|
+
function mktag (name, html, css, attribs, js, pcex) {
|
658
|
+
var
|
659
|
+
c = ', ',
|
660
|
+
s = '}' + (pcex.length ? ', ' + _q(pcex._bp[8]) : '') + ');'
|
661
|
+
|
662
|
+
if (js && js.slice(-1) !== '\n') s = '\n' + s
|
663
|
+
|
664
|
+
return 'riot.tag2(\'' + name + SQ +
|
665
|
+
c + _q(html, 1) +
|
666
|
+
c + _q(css) +
|
667
|
+
c + _q(attribs) + ', function(opts) {\n' + js + s
|
668
|
+
}
|
669
|
+
|
670
|
+
/**
|
671
|
+
* Used by the main {@link module:compiler.compile|compile} function, separates the
|
672
|
+
* HTML and JS parts of the tag. The last HTML element (can be a `<script>` block) must
|
673
|
+
* terminate a line.
|
674
|
+
*
|
675
|
+
* @param {string} str - Tag content, normalized, without attributes
|
676
|
+
* @returns {Array} Parts: `[HTML, JavaScript]`
|
677
|
+
*/
|
678
|
+
function splitBlocks (str) {
|
679
|
+
if (/<[-\w]/.test(str)) {
|
680
|
+
var
|
681
|
+
m,
|
682
|
+
k = str.lastIndexOf('<'),
|
683
|
+
n = str.length
|
684
|
+
|
685
|
+
while (~k) {
|
686
|
+
m = str.slice(k, n).match(END_TAGS)
|
687
|
+
if (m) {
|
688
|
+
k += m.index + m[0].length
|
689
|
+
return [str.slice(0, k), str.slice(k)]
|
690
|
+
}
|
691
|
+
n = k
|
692
|
+
k = str.lastIndexOf('<', k - 1)
|
693
|
+
}
|
694
|
+
}
|
695
|
+
return ['', str]
|
696
|
+
}
|
697
|
+
|
698
|
+
/**
|
699
|
+
* Returns the value of the 'type' attribute, with the prefix "text/" removed.
|
700
|
+
*
|
701
|
+
* @param {string} attribs - The attributes list
|
702
|
+
* @returns {string} Attribute value, defaults to empty string
|
703
|
+
*/
|
704
|
+
function getType (attribs) {
|
705
|
+
if (attribs) {
|
706
|
+
var match = attribs.match(TYPE_ATTR)
|
707
|
+
|
708
|
+
match = match && (match[2] || match[3])
|
709
|
+
if (match) {
|
710
|
+
return match.replace('text/', '')
|
711
|
+
}
|
712
|
+
}
|
713
|
+
return ''
|
714
|
+
}
|
715
|
+
|
716
|
+
/**
|
717
|
+
* Returns the value of any attribute, or the empty string for missing attribute.
|
718
|
+
*
|
719
|
+
* @param {string} attribs - The attribute list
|
720
|
+
* @param {string} name - Attribute name
|
721
|
+
* @returns {string} Attribute value, defaults to empty string
|
722
|
+
*/
|
723
|
+
function getAttrib (attribs, name) {
|
724
|
+
if (attribs) {
|
725
|
+
var match = attribs.match(RegExp('\\s' + name + MISC_ATTR, 'i'))
|
726
|
+
|
727
|
+
match = match && match[1]
|
728
|
+
if (match) {
|
729
|
+
return (/^['"]/).test(match) ? match.slice(1, -1) : match
|
730
|
+
}
|
731
|
+
}
|
732
|
+
return ''
|
733
|
+
}
|
734
|
+
|
735
|
+
/**
|
736
|
+
* Gets the parser options from the "options" attribute.
|
737
|
+
*
|
738
|
+
* @param {string} attribs - The attribute list
|
739
|
+
* @returns {object} Parsed options, or null if no options
|
740
|
+
*/
|
741
|
+
function getParserOptions (attribs) {
|
742
|
+
var opts = getAttrib(attribs, 'options')
|
743
|
+
|
744
|
+
return opts ? JSON.parse(opts) : null
|
745
|
+
}
|
746
|
+
|
747
|
+
/**
|
748
|
+
* Gets the parsed code for the received JavaScript code.
|
749
|
+
* The node version can read the code from the file system. The filename is
|
750
|
+
* specified through the _src_ attribute and can be absolute or relative to _base_.
|
751
|
+
*
|
752
|
+
* @param {string} code - The unprocessed JavaScript code
|
753
|
+
* @param {object} opts - Compiler options
|
754
|
+
* @param {string} attribs - Attribute list
|
755
|
+
* @param {string} base - Full filename or path of the file being processed
|
756
|
+
* @returns {string} Parsed code
|
757
|
+
*/
|
758
|
+
function getCode (code, opts, attribs, base) {
|
759
|
+
var type = getType(attribs)
|
760
|
+
|
761
|
+
var src = getAttrib(attribs, 'src')
|
762
|
+
|
763
|
+
if (src) {
|
764
|
+
if (DEFER_ATTR.test(attribs)) return false
|
765
|
+
|
766
|
+
var charset = getAttrib(attribs, 'charset'),
|
767
|
+
file = path.resolve(path.dirname(base), src)
|
768
|
+
|
769
|
+
code = require('fs').readFileSync(file, charset || 'utf8')
|
770
|
+
}
|
771
|
+
return _compileJS(code, opts, type, getParserOptions(attribs), base)
|
772
|
+
}
|
773
|
+
|
774
|
+
/**
|
775
|
+
* Gets the parsed styles for the received CSS code.
|
776
|
+
*
|
777
|
+
* @param {string} code - Unprocessed CSS
|
778
|
+
* @param {object} opts - Compiler options
|
779
|
+
* @param {string} attribs - Attribute list
|
780
|
+
* @param {string} url - Of the file being processed
|
781
|
+
* @param {string} tag - Tag name of the root element
|
782
|
+
* @returns {string} Parsed styles
|
783
|
+
*/
|
784
|
+
function cssCode (code, opts, attribs, url, tag) {
|
785
|
+
var extraOpts = {
|
786
|
+
parserOpts: getParserOptions(attribs),
|
787
|
+
scoped: attribs && /\sscoped(\s|=|$)/i.test(attribs),
|
788
|
+
url: url
|
789
|
+
}
|
790
|
+
|
791
|
+
return _compileCSS(code, tag, getType(attribs) || opts.style, extraOpts)
|
792
|
+
}
|
793
|
+
|
794
|
+
/**
|
795
|
+
* Runs the external HTML parser for the entire tag file
|
796
|
+
*
|
797
|
+
* @param {string} html - Entire, untouched html received for the compiler
|
798
|
+
* @param {string} url - The source url or file name
|
799
|
+
* @param {string} lang - Parser's name, one of {@link module:parsers.html|parsers.html}
|
800
|
+
* @param {object} opts - Extra option passed to the parser
|
801
|
+
* @returns {string} parsed html
|
802
|
+
*
|
803
|
+
* @throws Will throw "Template parser not found" if the HTML parser cannot be loaded.
|
804
|
+
*/
|
805
|
+
function compileTemplate (html, url, lang, opts) {
|
806
|
+
var parser = parsers.html[lang]
|
807
|
+
|
808
|
+
if (!parser) {
|
809
|
+
throw new Error('Template parser not found: "' + lang + '"')
|
810
|
+
}
|
811
|
+
return parser(html, opts, url)
|
812
|
+
}
|
813
|
+
|
814
|
+
var
|
815
|
+
/**
|
816
|
+
* Matches HTML elements. The opening and closing tags of multiline elements must have
|
817
|
+
* the same indentation (size and type).
|
818
|
+
*
|
819
|
+
* `CUST_TAG` recognizes escaped quotes, allowing its insertion into JS strings inside
|
820
|
+
* unquoted expressions, but disallows the character '>' within unquoted attribute values.
|
821
|
+
* @const {RegExp}
|
822
|
+
*/
|
823
|
+
CUST_TAG = RegExp(/^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:@|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/
|
824
|
+
.source.replace('@', S_STRINGS), 'gim'),
|
825
|
+
/**
|
826
|
+
* Matches `script` elements, capturing its attributes in $1 and its content in $2.
|
827
|
+
* Disallows the character '>' inside quoted or unquoted attribute values.
|
828
|
+
* @const {RegExp}
|
829
|
+
*/
|
830
|
+
SCRIPTS = /<script(\s+[^>]*)?>\n?([\S\s]*?)<\/script\s*>/gi,
|
831
|
+
/**
|
832
|
+
* Matches `style` elements, capturing its attributes in $1 and its content in $2.
|
833
|
+
* Disallows the character '>' inside quoted or unquoted attribute values.
|
834
|
+
* @const {RegExp}
|
835
|
+
*/
|
836
|
+
STYLES = /<style(\s+[^>]*)?>\n?([\S\s]*?)<\/style\s*>/gi
|
837
|
+
|
838
|
+
/**
|
839
|
+
* The main compiler processes all custom tags, one by one.
|
840
|
+
*
|
841
|
+
* - Sends the received source to the html parser, if any is specified
|
842
|
+
* - Normalizes eols, removes HTML comments and trim trailing spaces
|
843
|
+
* - Searches the HTML elements and extract: tag name, root attributes, and content
|
844
|
+
* - Parses the root attributes. Found expressions are stored in `pcex[]`
|
845
|
+
* - Removes _HTML_ comments and trims trailing whitespace from the content
|
846
|
+
* - For one-line tags, process all the content as HTML
|
847
|
+
* - For multiline tags, separates the HTML from any untagged JS block and, from
|
848
|
+
* the html, extract and process the `style` and `script` elements
|
849
|
+
* - Parses the remaining html, found expressions are added to `pcex[]`
|
850
|
+
* - Parses the untagged JavaScript block, if any
|
851
|
+
* - If the `entities` option was received, returns an object with the parts,
|
852
|
+
* if not, returns the code neccesary to call the `riot.tag2` function to
|
853
|
+
* create a Tag instance at runtime.
|
854
|
+
*
|
855
|
+
* In .tag files, a custom tag can span multiple lines, but there should be no other
|
856
|
+
* elements at the start of the line (comments inclusive). Custom tags in html files
|
857
|
+
* don't have this restriction.
|
858
|
+
*
|
859
|
+
* @param {string} src - String with zero or more custom riot tags
|
860
|
+
* @param {Object} [opts={}] - User options
|
861
|
+
* @param {string} [url=./.] - Filename or url of the file being processed
|
862
|
+
* @returns {string} JavaScript code to build a Tag by the `riot.tag2` function
|
863
|
+
* @static
|
864
|
+
*/
|
865
|
+
function compile (src, opts, url) {
|
866
|
+
var
|
867
|
+
parts = [],
|
868
|
+
included
|
869
|
+
|
870
|
+
if (!opts) opts = {}
|
871
|
+
|
872
|
+
included = opts.exclude
|
873
|
+
? function (s) { return opts.exclude.indexOf(s) < 0 } : function () { return 1 }
|
874
|
+
|
875
|
+
if (!url) url = process.cwd() + '/.'
|
876
|
+
|
877
|
+
var _bp = brackets.array(opts.brackets)
|
878
|
+
|
879
|
+
if (opts.template) {
|
880
|
+
src = compileTemplate(src, url, opts.template, opts.templateOptions)
|
881
|
+
}
|
882
|
+
|
883
|
+
src = cleanSource(src)
|
884
|
+
.replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) {
|
885
|
+
var
|
886
|
+
jscode = '',
|
887
|
+
styles = '',
|
888
|
+
html = '',
|
889
|
+
pcex = []
|
890
|
+
|
891
|
+
pcex._bp = _bp
|
892
|
+
|
893
|
+
tagName = tagName.toLowerCase()
|
894
|
+
|
895
|
+
attribs = attribs && included('attribs')
|
896
|
+
? restoreExpr(
|
897
|
+
parseAttribs(
|
898
|
+
splitHtml(attribs, opts, pcex),
|
899
|
+
pcex),
|
900
|
+
pcex) : ''
|
901
|
+
|
902
|
+
if ((body || (body = body2)) && /\S/.test(body)) {
|
903
|
+
|
904
|
+
if (body2) {
|
905
|
+
|
906
|
+
if (included('html')) html = _compileHTML(body2, opts, pcex)
|
907
|
+
} else {
|
908
|
+
|
909
|
+
body = body.replace(RegExp('^' + indent, 'gm'), '')
|
910
|
+
|
911
|
+
body = body.replace(STYLES, function (_m, _attrs, _style) {
|
912
|
+
if (included('css')) {
|
913
|
+
styles += (styles ? ' ' : '') + cssCode(_style, opts, _attrs, url, tagName)
|
914
|
+
}
|
915
|
+
return ''
|
916
|
+
})
|
917
|
+
|
918
|
+
body = body.replace(SCRIPTS, function (_m, _attrs, _script) {
|
919
|
+
if (included('js')) {
|
920
|
+
var code = getCode(_script, opts, _attrs, url)
|
921
|
+
|
922
|
+
if (code === false) return _m.replace(DEFER_ATTR, '')
|
923
|
+
if (code) jscode += (jscode ? '\n' : '') + code
|
924
|
+
}
|
925
|
+
return ''
|
926
|
+
})
|
927
|
+
|
928
|
+
var blocks = splitBlocks(body.replace(TRIM_TRAIL, ''))
|
929
|
+
|
930
|
+
if (included('html')) {
|
931
|
+
html = _compileHTML(blocks[0], opts, pcex)
|
932
|
+
}
|
933
|
+
|
934
|
+
if (included('js')) {
|
935
|
+
body = _compileJS(blocks[1], opts, null, null, url)
|
936
|
+
if (body) jscode += (jscode ? '\n' : '') + body
|
937
|
+
}
|
938
|
+
}
|
939
|
+
}
|
940
|
+
|
941
|
+
jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : ''
|
942
|
+
|
943
|
+
if (opts.entities) {
|
944
|
+
parts.push({
|
945
|
+
tagName: tagName,
|
946
|
+
html: html,
|
947
|
+
css: styles,
|
948
|
+
attribs: attribs,
|
949
|
+
js: jscode
|
950
|
+
})
|
951
|
+
return ''
|
952
|
+
}
|
953
|
+
|
954
|
+
return mktag(tagName, html, styles, attribs, jscode, pcex)
|
955
|
+
})
|
956
|
+
|
957
|
+
if (opts.entities) return parts
|
958
|
+
|
959
|
+
if (opts.debug && url.slice(-2) !== '/.') {
|
960
|
+
if (/^[\\/]/.test(url)) url = path.relative('.', url)
|
961
|
+
src = '//src: ' + url.replace(/\\/g, '/') + '\n' + src
|
962
|
+
}
|
963
|
+
return src
|
964
|
+
}
|
10
965
|
|
11
966
|
module.exports = {
|
12
967
|
compile: compile,
|
13
968
|
html: compileHTML,
|
14
|
-
style:
|
15
|
-
|
16
|
-
|
969
|
+
style: _compileCSS,
|
970
|
+
css: compileCSS,
|
971
|
+
js: compileJS,
|
972
|
+
parsers: parsers,
|
973
|
+
version: 'WIP'
|
974
|
+
}
|