riot_js-rails 0.6.2 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5209662e90ea7c75f0e12cfb17dd327504638d01
4
- data.tar.gz: 49c4adcf8d1244370fc76f1cbdfbb520bb47fa31
3
+ metadata.gz: 7f1f3ab924a0ddd2a63b4a9d73614ab08a2792a9
4
+ data.tar.gz: 07c38e96e874e3c56b6f7ddae718680ab7b569bd
5
5
  SHA512:
6
- metadata.gz: 7cbe0afdfe02b06dc7145d875e3e677b926e6cb43345f710e96881a66058a4e74ed25a472dd8b14485a876e48e4bc4df56efc421553b80ffa8e5abf403cd9f50
7
- data.tar.gz: 451bceae8a8539a844d9ebcb3b5bd64c16f8c2e24c4337bcfc4f5d81dc1597e89a28e147464c6174f51ec2b8a3776dcd57697e12043b363bbf1098faa443d0aa
6
+ metadata.gz: 6eb2e719eebf5c16705da093042c667b4d88bdc438875adffb4cc54208d827509ba6c2c57fa4a8c1ac464980d973bb37c168fd2b03e5bd25363ff239303abd24
7
+ data.tar.gz: d0a18d5eeb06494ad0c28bba921f814cb4c9b24e73040a8fe7d4b82f1a60eb792ec29b71072379303fe0b45f40a418bc76776170b39d6ac4832555f7a687a1e7
data/Rakefile CHANGED
@@ -7,4 +7,12 @@ Rake::TestTask.new do |t|
7
7
  end
8
8
 
9
9
  desc "Run tests"
10
- task :default => :test
10
+ task :default => :test
11
+
12
+ namespace :test do
13
+ desc "Test with various versions of sprockets"
14
+ task :sprockets_versions do
15
+ sh "bash test/test_sprockets_versions.sh"
16
+ end
17
+ end
18
+
@@ -1,23 +1,103 @@
1
1
  require 'riot_js/rails/processors/compiler'
2
2
 
3
- if Gem::Version.new(Sprockets::VERSION) < Gem::Version.new('3.0.0')
4
- require 'riot_js/rails/processors/sprockets_processor_v2'
5
- else
6
- require 'riot_js/rails/processors/sprockets_processor_v3'
7
- end
8
-
9
3
  module RiotJs
10
4
  module Rails
11
- class Processor < SprocketsProcessor
12
5
 
13
- def process
14
- compile_tag
6
+ # Sprockets 2, 3 & 4 interface
7
+ class SprocketsExtensionBase
8
+ attr_reader :default_mime_type
9
+
10
+ def initialize(filename, &block)
11
+ @filename = filename
12
+ @source = block.call
13
+ end
14
+
15
+ def render(context, empty_hash_wtf)
16
+ self.class.run(@filename, @source, context)
17
+ end
18
+
19
+ def self.run(filename, source, context)
20
+ raise 'Not implemented'
21
+ end
22
+
23
+ def self.call(input)
24
+ if input.is_a?(String)
25
+ run("", input, nil)
26
+ else
27
+ filename = input[:filename]
28
+ source = input[:data]
29
+ context = input[:environment].context_class.new(input)
30
+
31
+ result = run(filename, source, context)
32
+ context.metadata.merge(data: result)
33
+ end
15
34
  end
16
35
 
17
36
  private
18
37
 
19
- def compile_tag
20
- ::RiotJs::Rails::Compiler.compile(@data)
38
+ def self.register_self_helper(app, config, file_ext, mime_type_from, mime_type_to, charset=nil)
39
+
40
+ if config.respond_to?(:assets)
41
+ config.assets.configure do |env|
42
+ if env.respond_to?(:register_transformer)
43
+ # Sprockets 3 and 4
44
+ env.register_mime_type mime_type_from, extensions: [file_ext], charset: charset
45
+ env.register_transformer mime_type_from, mime_type_to, self
46
+ elsif env.respond_to?(:register_engine)
47
+ if Sprockets::VERSION.start_with?("3")
48
+ # Sprockets 3 ... is this needed?
49
+ env.register_engine file_ext, self, { mime_type: mime_type_to, silence_deprecation: true }
50
+ else
51
+ # Sprockets 2.12.4
52
+ @default_mime_type = mime_type_to
53
+ env.register_engine file_ext, self
54
+ end
55
+ end
56
+ end
57
+ else
58
+ # Sprockets 2.2.3
59
+ @default_mime_type = mime_type_to
60
+ app.assets.register_engine file_ext, self
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ class Processor < SprocketsExtensionBase
67
+
68
+ def self.run(filename, source, context)
69
+ ::RiotJs::Rails::Compiler.compile(source)
70
+ end
71
+
72
+ def self.register_self(app, config)
73
+ # app is a YourApp::Application
74
+ # config is Rails::Railtie::Configuration that belongs to RiotJs::Rails::Railtie
75
+ register_self_helper(app, config, '.tag', 'text/riot-tag', 'application/javascript', :html)
76
+ end
77
+
78
+ def self.register_nested(app, config, type, charset, tilt_template)
79
+ extention = '.' + type
80
+ if config.respond_to?(:assets)
81
+ config.assets.configure do |env|
82
+ if env.respond_to?(:register_transformer)
83
+ # Sprockets 3 and 4
84
+ env.register_mime_type 'text/riot-tag+'+type, extensions: ['.tag'+extention], charset: charset
85
+ env.register_transformer 'text/riot-tag+'+type, 'application/javascript',
86
+ Proc.new{ |input| Processor.call(tilt_template.new{input[:data]}.render) }
87
+ elsif env.respond_to?(:register_engine)
88
+ if Sprockets::VERSION.start_with?("3")
89
+ # Sprockets 3 ... is this needed?
90
+ env.register_engine extention, tilt_template, { silence_deprecation: true }
91
+ else
92
+ # Sprockets 2.12.4
93
+ env.register_engine extention, tilt_template
94
+ end
95
+ end
96
+ end
97
+ else
98
+ # Sprockets 2
99
+ app.assets.register_engine extention, tilt_template
100
+ end
21
101
  end
22
102
  end
23
103
  end
@@ -9,15 +9,18 @@ module RiotJs
9
9
  config.riot.node_paths = []
10
10
 
11
11
  initializer :setup_sprockets do |app|
12
- Processor.register_self config
12
+ # app is a YourApp::Application
13
+ # config is Rails::Railtie::Configuration that belongs to RiotJs::Rails::Railtie
14
+ Processor.register_self app, config
13
15
 
14
- if defined? ::Haml
16
+ if defined?(::Haml)
15
17
  require 'tilt/haml'
16
- config.assets.register_engine '.haml', ::Tilt::HamlTemplate
18
+ Haml::Template.options[:format] = :html5
19
+ Processor.register_nested(app, config, 'haml', :html, ::Tilt::HamlTemplate)
17
20
  end
18
21
 
19
- if defined? ::Slim
20
- config.assets.register_engine '.slim', ::Slim::Template
22
+ if defined?(::Slim)
23
+ Processor.register_nested(app, config, 'slim', :html, ::Slim::Template)
21
24
  end
22
25
  end
23
26
 
@@ -36,7 +39,6 @@ module RiotJs
36
39
  ENV['NODE_PATH'] = node_paths.join(':')
37
40
  end
38
41
 
39
-
40
42
  def detect_node_global_path
41
43
  prefix = `npm config get prefix`.to_s.chomp("\n")
42
44
  possible_paths = [ "#{prefix}/lib/node", "#{prefix}/lib/node_modules" ]
@@ -1,5 +1,5 @@
1
1
  module RiotJs
2
2
  module Rails
3
- VERSION = '0.6.2'
3
+ VERSION = '0.7.0'
4
4
  end
5
5
  end
@@ -1,7 +1,10 @@
1
+ 'use strict'
2
+
1
3
  /**
2
4
  * Brackets support for the node.js version of the riot-compiler
3
5
  * @module
4
6
  */
7
+ var safeRegex = require('./safe-regex.js')
5
8
 
6
9
  /**
7
10
  * Matches valid, multiline JavaScript comments in almost all its forms.
@@ -128,7 +131,7 @@ module.exports.split = function split (str, _, _bp) {
128
131
 
129
132
  isexpr = start = re.lastIndex = 0 // re is reused, we must reset lastIndex
130
133
 
131
- while (match = re.exec(str)) {
134
+ while ((match = re.exec(str))) {
132
135
 
133
136
  pos = match.index
134
137
 
@@ -202,7 +205,7 @@ module.exports.split = function split (str, _, _bp) {
202
205
 
203
206
  rr.lastIndex = ix
204
207
  ix = 1
205
- while (mm = rr.exec(s)) {
208
+ while ((mm = rr.exec(s))) {
206
209
  if (mm[1] &&
207
210
  !(mm[1] === ch ? ++ix : --ix)) break
208
211
  }
@@ -214,9 +217,8 @@ module.exports.split = function split (str, _, _bp) {
214
217
  }
215
218
  }
216
219
 
217
- var
218
- INVALIDCH = /[\x00-\x1F<>a-zA-Z0-9'",;\\]/, // invalid characters for brackets
219
- ESCAPEDCH = /(?=[[\]()*+?.^$|])/g // this characters must be escaped
220
+ var INVALIDCH = safeRegex(/[@-@<>a-zA-Z0-9'",;\\]/, 'x00', 'x1F') // invalid characters for brackets
221
+ var ESCAPEDCH = /(?=[[\]()*+?.^$|])/g // this characters must be escaped
220
222
 
221
223
  /**
222
224
  * Returns an array with information for the given brackets using a cache for the
@@ -4,13 +4,17 @@
4
4
  * @module compiler
5
5
  * @version WIP
6
6
  * @license MIT
7
- * @copyright 2015 Muut Inc. + contributors
7
+ * @copyright Muut Inc. + contributors
8
8
  */
9
9
  'use strict'
10
10
 
11
- var brackets = require('./brackets')
12
- var parsers = require('./parsers')
13
- var path = require('path')
11
+ var brackets = require('./brackets')
12
+ var parsers = require('./parsers')
13
+ var safeRegex = require('./safe-regex')
14
+ var path = require('path')
15
+
16
+ var extend = require('./parsers/_utils').mixobj
17
+ /* eslint-enable */
14
18
 
15
19
  /**
16
20
  * Source for creating regexes matching valid quoted, single-line JavaScript strings.
@@ -54,20 +58,14 @@ var HTML_COMMS = RegExp(/<!--(?!>)[\S\s]*?-->/.source + '|' + S_LINESTR, 'g')
54
58
  * {@link module:compiler~parseAttribs|parseAttribs}
55
59
  * @const {RegExp}
56
60
  */
57
- var HTML_TAGS = /<([-\w]+)(?:\s+([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)(\/?)>/g
61
+ var HTML_TAGS = /<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)(\/?)>/g
58
62
 
59
63
  /**
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)
64
+ * Matches spaces and tabs between HTML tags
65
+ * Used by the `compact` option.
66
+ * @const RegExp
65
67
  */
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)$')
68
+ var HTML_PACK = />[ \t]+<(-?[A-Za-z]|\/[-A-Za-z])/g
71
69
 
72
70
  /**
73
71
  * These attributes give error when parsed on browsers with an expression in its value.
@@ -76,7 +74,7 @@ var BOOL_ATTRS = RegExp(
76
74
  * @const {Array}
77
75
  * @see [attributes.md](https://github.com/riot/compiler/blob/dev/doc/attributes.md)
78
76
  */
79
- var RIOT_ATTRS = ['style', 'src', 'd']
77
+ var RIOT_ATTRS = ['style', 'src', 'd', 'value']
80
78
 
81
79
  /**
82
80
  * HTML5 void elements that cannot be auto-closed.
@@ -103,6 +101,12 @@ var PRE_TAGS = /<pre(?:\s+(?:[^">]*|"[^"]*")*)?>([\S\s]+?)<\/pre\s*>/gi
103
101
  */
104
102
  var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i
105
103
 
104
+ /**
105
+ * Matches the 'import' statement
106
+ * @const {RegExp}
107
+ */
108
+ var IMPORT_STATEMENT = /^\s*import(?:(?:\s|[^\s'"])*)['|"].*\n?/gm
109
+
106
110
  /**
107
111
  * Matches trailing spaces and tabs by line.
108
112
  * @const {RegExp}
@@ -110,6 +114,10 @@ var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i
110
114
  var TRIM_TRAIL = /[ \t]+$/gm
111
115
 
112
116
  var
117
+ RE_HASEXPR = safeRegex(/@#\d/, 'x01'),
118
+ RE_REPEXPR = safeRegex(/@#(\d+)/g, 'x01'),
119
+ CH_IDEXPR = '\x01#',
120
+ CH_DQCODE = '\u2057',
113
121
  DQ = '"',
114
122
  SQ = "'"
115
123
 
@@ -132,7 +140,7 @@ function cleanSource (src) {
132
140
  }
133
141
 
134
142
  re.lastIndex = 0
135
- while (mm = re.exec(src)) {
143
+ while ((mm = re.exec(src))) {
136
144
  if (mm[0][0] === '<') {
137
145
  src = RegExp.leftContext + RegExp.rightContext
138
146
  re.lastIndex = mm[3] + 1
@@ -160,7 +168,7 @@ function parseAttribs (str, pcex) {
160
168
 
161
169
  str = str.replace(/\s+/g, ' ')
162
170
 
163
- while (match = HTML_ATTRS.exec(str)) {
171
+ while ((match = HTML_ATTRS.exec(str))) {
164
172
  var
165
173
  k = match[1].toLowerCase(),
166
174
  v = match[2]
@@ -176,11 +184,10 @@ function parseAttribs (str, pcex) {
176
184
  if (k === 'type' && SPEC_TYPES.test(v)) {
177
185
  type = v
178
186
  } else {
179
- if (/\u0001\d/.test(v)) {
187
+ if (RE_HASEXPR.test(v)) {
180
188
 
181
189
  if (k === 'value') vexp = 1
182
- else if (BOOL_ATTRS.test(k)) k = '__' + k
183
- else if (~RIOT_ATTRS.indexOf(k)) k = 'riot-' + k
190
+ if (~RIOT_ATTRS.indexOf(k)) k = 'riot-' + k
184
191
  }
185
192
 
186
193
  list.push(k + '=' + v)
@@ -213,19 +220,17 @@ function splitHtml (html, opts, pcex) {
213
220
  var
214
221
  jsfn = opts.expr && (opts.parser || opts.type) ? _compileJS : 0,
215
222
  list = brackets.split(html, 0, _bp),
216
- expr, israw
223
+ expr
217
224
 
218
225
  for (var i = 1; i < list.length; i += 2) {
219
226
  expr = list[i]
220
227
  if (expr[0] === '^') {
221
228
  expr = expr.slice(1)
222
229
  } else if (jsfn) {
223
- israw = expr[0] === '='
224
- expr = jsfn(israw ? expr.slice(1) : expr, opts).trim()
230
+ expr = jsfn(expr, opts).trim()
225
231
  if (expr.slice(-1) === ';') expr = expr.slice(0, -1)
226
- if (israw) expr = '=' + expr
227
232
  }
228
- list[i] = '\u0001' + (pcex.push(expr) - 1) + _bp[1]
233
+ list[i] = CH_IDEXPR + (pcex.push(expr) - 1) + _bp[1]
229
234
  }
230
235
  html = list.join('')
231
236
  }
@@ -242,20 +247,10 @@ function splitHtml (html, opts, pcex) {
242
247
  */
243
248
  function restoreExpr (html, pcex) {
244
249
  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, '&lt;')
253
- .replace(/>/g, '&gt;')
254
- })
255
- }
250
+ html = html.replace(RE_REPEXPR, function (_, d) {
256
251
 
257
- return pcex._bp[0] + expr.trim().replace(/[\r\n]+/g, ' ').replace(/"/g, '\u2057')
258
- })
252
+ return pcex._bp[0] + pcex[d].trim().replace(/[\r\n]+/g, ' ').replace(/"/g, CH_DQCODE)
253
+ })
259
254
  }
260
255
  return html
261
256
  }
@@ -271,6 +266,7 @@ function restoreExpr (html, pcex) {
271
266
  * @see {@link http://www.w3.org/TR/html5/syntax.html}
272
267
  */
273
268
  function _compileHTML (html, opts, pcex) {
269
+ if (!/\S/.test(html)) return ''
274
270
 
275
271
  html = splitHtml(html, opts, pcex)
276
272
  .replace(HTML_TAGS, function (_, name, attr, ends) {
@@ -299,7 +295,7 @@ function _compileHTML (html, opts, pcex) {
299
295
  if (p.length) html = html.replace(/\u0002/g, function () { return p.shift() })
300
296
  }
301
297
 
302
- if (opts.compact) html = html.replace(/>[ \t]+<([-\w\/])/g, '><$1')
298
+ if (opts.compact) html = html.replace(HTML_PACK, '><$1')
303
299
 
304
300
  return restoreExpr(html, pcex).replace(TRIM_TRAIL, '')
305
301
  }
@@ -377,7 +373,7 @@ function riotjs (js) {
377
373
 
378
374
  if (~js.indexOf('/')) js = rmComms(js, JS_COMMS)
379
375
 
380
- while (match = js.match(JS_ES6SIGN)) {
376
+ while ((match = js.match(JS_ES6SIGN))) {
381
377
 
382
378
  parts.push(RE.leftContext)
383
379
  js = RE.rightContext
@@ -396,7 +392,7 @@ function riotjs (js) {
396
392
 
397
393
  function rmComms (s, r, m) {
398
394
  r.lastIndex = 0
399
- while (m = r.exec(s)) {
395
+ while ((m = r.exec(s))) {
400
396
  if (m[0][0] === '/' && !m[1] && !m[2]) {
401
397
  s = RE.leftContext + ' ' + RE.rightContext
402
398
  r.lastIndex = m[3] + 1
@@ -434,11 +430,8 @@ function _compileJS (js, opts, type, parserOpts, url) {
434
430
  if (!/\S/.test(js)) return ''
435
431
  if (!type) type = opts.type
436
432
 
437
- var parser = opts.parser || (type ? parsers.js[type] : riotjs)
433
+ var parser = opts.parser || type && parsers._req('js.' + type, true) || riotjs
438
434
 
439
- if (!parser) {
440
- throw new Error('JS parser not found: "' + type + '"')
441
- }
442
435
  return parser(js, parserOpts, url).replace(/\r\n?/g, '\n').replace(TRIM_TRAIL, '')
443
436
  }
444
437
 
@@ -478,7 +471,7 @@ function compileJS (js, opts, type, userOpts) {
478
471
  return _compileJS(js, opts || {}, type, userOpts.parserOptions, userOpts.url)
479
472
  }
480
473
 
481
- var CSS_SELECTOR = RegExp('([{}]|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + S_LINESTR, 'g')
474
+ var CSS_SELECTOR = RegExp('([{}]|^)[; ]*((?:[^@ ;{}][^{}]*)?[^@ ;{}:] ?)(?={)|' + S_LINESTR, 'g')
482
475
 
483
476
  /**
484
477
  * Parses styles enclosed in a "scoped" tag (`scoped` was removed from HTML5).
@@ -498,17 +491,21 @@ function scopedCSS (tag, css) {
498
491
  p2 = p2.replace(/[^,]+/g, function (sel) {
499
492
  var s = sel.trim()
500
493
 
494
+ if (s.indexOf(tag) === 0) {
495
+ return sel
496
+ }
497
+
501
498
  if (!s || s === 'from' || s === 'to' || s.slice(-1) === '%') {
502
499
  return sel
503
500
  }
504
501
 
505
502
  if (s.indexOf(scope) < 0) {
506
- s = tag + ' ' + s + ',[riot-tag="' + tag + '"] ' + s
503
+ s = tag + ' ' + s + ',[data-is="' + tag + '"] ' + s
507
504
  } else {
508
505
  s = s.replace(scope, tag) + ',' +
509
- s.replace(scope, '[riot-tag="' + tag + '"]')
506
+ s.replace(scope, '[data-is="' + tag + '"]')
510
507
  }
511
- return sel.slice(-1) === ' ' ? s + ' ' : s
508
+ return s
512
509
  })
513
510
 
514
511
  return p1 ? p1 + ' ' + p2 : p2
@@ -530,26 +527,19 @@ function scopedCSS (tag, css) {
530
527
  * @see {@link module:compiler.compileCSS|compileCSS}
531
528
  */
532
529
  function _compileCSS (css, tag, type, opts) {
533
- var scoped = (opts || (opts = {})).scoped
530
+ opts = opts || {}
534
531
 
535
532
  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 + '"')
533
+ if (type !== 'css') {
534
+
535
+ var parser = parsers._req('css.' + type, true)
536
+ css = parser(tag, css, opts.parserOpts || {}, opts.url)
542
537
  }
543
538
  }
544
539
 
545
540
  css = css.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim()
541
+ if (tag) css = scopedCSS(tag, css)
546
542
 
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
543
  return css
554
544
  }
555
545
 
@@ -624,7 +614,7 @@ var MISC_ATTR = '\\s*=\\s*(' + S_STRINGS + '|{[^}]+}|\\S+)'
624
614
  * ```
625
615
  * @const {RegExp}
626
616
  */
627
- var END_TAGS = /\/>\n|^<(?:\/?[-\w]+\s*|[-\w]+\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/
617
+ var END_TAGS = /\/>\n|^<(?:\/?-?[A-Za-z][-\w\xA0-\xFF]*\s*|-?[A-Za-z][-\w\xA0-\xFF]*\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/
628
618
 
629
619
  /**
630
620
  * Encloses the given string in single quotes.
@@ -646,25 +636,26 @@ function _q (s, r) {
646
636
  /**
647
637
  * Generates code to call the `riot.tag2` function with the processed parts.
648
638
  *
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
639
+ * @param {string} name - The tag name
640
+ * @param {string} html - HTML (can contain embeded eols)
641
+ * @param {string} css - Styles
642
+ * @param {string} attr - Root attributes
643
+ * @param {string} js - JavaScript "constructor"
644
+ * @param {string} imports - Code containing 'import' statements
645
+ * @param {object} opts - Compiler options
655
646
  * @returns {string} Code to call `riot.tag2`
656
647
  */
657
- function mktag (name, html, css, attribs, js, pcex) {
648
+ function mktag (name, html, css, attr, js, imports, opts) {
658
649
  var
659
- c = ', ',
660
- s = '}' + (pcex.length ? ', ' + _q(pcex._bp[8]) : '') + ');'
650
+ c = opts.debug ? ',\n ' : ', ',
651
+ s = '});'
661
652
 
662
653
  if (js && js.slice(-1) !== '\n') s = '\n' + s
663
654
 
664
- return 'riot.tag2(\'' + name + SQ +
655
+ return imports + 'riot.tag2(\'' + name + SQ +
665
656
  c + _q(html, 1) +
666
657
  c + _q(css) +
667
- c + _q(attribs) + ', function(opts) {\n' + js + s
658
+ c + _q(attr) + ', function(opts) {\n' + js + s
668
659
  }
669
660
 
670
661
  /**
@@ -686,7 +677,9 @@ function splitBlocks (str) {
686
677
  m = str.slice(k, n).match(END_TAGS)
687
678
  if (m) {
688
679
  k += m.index + m[0].length
689
- return [str.slice(0, k), str.slice(k)]
680
+ m = str.slice(0, k)
681
+ if (m.slice(-5) === '<-/>\n') m = m.slice(0, -5)
682
+ return [m, str.slice(k)]
690
683
  }
691
684
  n = k
692
685
  k = str.lastIndexOf('<', k - 1)
@@ -732,6 +725,20 @@ function getAttrib (attribs, name) {
732
725
  return ''
733
726
  }
734
727
 
728
+ /**
729
+ * Unescape any html string
730
+ * @param {string} str escaped html string
731
+ * @returns {string} unescaped html string
732
+ */
733
+ function unescapeHTML (str) {
734
+ return str
735
+ .replace(/&amp;/g, '&')
736
+ .replace(/&lt;/g, '<')
737
+ .replace(/&gt;/g, '>')
738
+ .replace(/&quot;/g, '"')
739
+ .replace(/&#039;/g, '\'')
740
+ }
741
+
735
742
  /**
736
743
  * Gets the parser options from the "options" attribute.
737
744
  *
@@ -739,7 +746,7 @@ function getAttrib (attribs, name) {
739
746
  * @returns {object} Parsed options, or null if no options
740
747
  */
741
748
  function getParserOptions (attribs) {
742
- var opts = getAttrib(attribs, 'options')
749
+ var opts = unescapeHTML(getAttrib(attribs, 'options'))
743
750
 
744
751
  return opts ? JSON.parse(opts) : null
745
752
  }
@@ -756,9 +763,10 @@ function getParserOptions (attribs) {
756
763
  * @returns {string} Parsed code
757
764
  */
758
765
  function getCode (code, opts, attribs, base) {
759
- var type = getType(attribs)
760
-
761
- var src = getAttrib(attribs, 'src')
766
+ var
767
+ type = getType(attribs),
768
+ src = getAttrib(attribs, 'src'),
769
+ jsParserOptions = extend({}, opts.parserOptions.js)
762
770
 
763
771
  if (src) {
764
772
  if (DEFER_ATTR.test(attribs)) return false
@@ -768,7 +776,14 @@ function getCode (code, opts, attribs, base) {
768
776
 
769
777
  code = require('fs').readFileSync(file, charset || 'utf8')
770
778
  }
771
- return _compileJS(code, opts, type, getParserOptions(attribs), base)
779
+
780
+ return _compileJS(
781
+ code,
782
+ opts,
783
+ type,
784
+ extend(jsParserOptions, getParserOptions(attribs)),
785
+ base
786
+ )
772
787
  }
773
788
 
774
789
  /**
@@ -782,11 +797,12 @@ function getCode (code, opts, attribs, base) {
782
797
  * @returns {string} Parsed styles
783
798
  */
784
799
  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
- }
800
+ var
801
+ parserStyleOptions = extend({}, opts.parserOptions.style),
802
+ extraOpts = {
803
+ parserOpts: extend(parserStyleOptions, getParserOptions(attribs)),
804
+ url: url
805
+ }
790
806
 
791
807
  return _compileCSS(code, tag, getType(attribs) || opts.style, extraOpts)
792
808
  }
@@ -803,11 +819,8 @@ function cssCode (code, opts, attribs, url, tag) {
803
819
  * @throws Will throw "Template parser not found" if the HTML parser cannot be loaded.
804
820
  */
805
821
  function compileTemplate (html, url, lang, opts) {
806
- var parser = parsers.html[lang]
807
822
 
808
- if (!parser) {
809
- throw new Error('Template parser not found: "' + lang + '"')
810
- }
823
+ var parser = parsers._req('html.' + lang, true)
811
824
  return parser(html, opts, url)
812
825
  }
813
826
 
@@ -820,7 +833,7 @@ var
820
833
  * unquoted expressions, but disallows the character '>' within unquoted attribute values.
821
834
  * @const {RegExp}
822
835
  */
823
- CUST_TAG = RegExp(/^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:@|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/
836
+ CUST_TAG = RegExp(/^([ \t]*)<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^'"\/>]+(?:(?:@|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/
824
837
  .source.replace('@', S_STRINGS), 'gim'),
825
838
  /**
826
839
  * Matches `script` elements, capturing its attributes in $1 and its content in $2.
@@ -865,10 +878,18 @@ var
865
878
  function compile (src, opts, url) {
866
879
  var
867
880
  parts = [],
868
- included
881
+ included,
882
+ defaultParserptions = {
883
+
884
+ template: {},
885
+ js: {},
886
+ style: {}
887
+ }
869
888
 
870
889
  if (!opts) opts = {}
871
890
 
891
+ opts.parserOptions = extend(defaultParserptions, opts.parserOptions || {})
892
+
872
893
  included = opts.exclude
873
894
  ? function (s) { return opts.exclude.indexOf(s) < 0 } : function () { return 1 }
874
895
 
@@ -877,7 +898,7 @@ function compile (src, opts, url) {
877
898
  var _bp = brackets.array(opts.brackets)
878
899
 
879
900
  if (opts.template) {
880
- src = compileTemplate(src, url, opts.template, opts.templateOptions)
901
+ src = compileTemplate(src, url, opts.template, opts.parserOptions.template)
881
902
  }
882
903
 
883
904
  src = cleanSource(src)
@@ -886,6 +907,7 @@ function compile (src, opts, url) {
886
907
  jscode = '',
887
908
  styles = '',
888
909
  html = '',
910
+ imports = '',
889
911
  pcex = []
890
912
 
891
913
  pcex._bp = _bp
@@ -908,13 +930,6 @@ function compile (src, opts, url) {
908
930
 
909
931
  body = body.replace(RegExp('^' + indent, 'gm'), '')
910
932
 
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
933
  body = body.replace(SCRIPTS, function (_m, _attrs, _script) {
919
934
  if (included('js')) {
920
935
  var code = getCode(_script, opts, _attrs, url)
@@ -925,6 +940,13 @@ function compile (src, opts, url) {
925
940
  return ''
926
941
  })
927
942
 
943
+ body = body.replace(STYLES, function (_m, _attrs, _style) {
944
+ if (included('css')) {
945
+ styles += (styles ? ' ' : '') + cssCode(_style, opts, _attrs, url, tagName)
946
+ }
947
+ return ''
948
+ })
949
+
928
950
  var blocks = splitBlocks(body.replace(TRIM_TRAIL, ''))
929
951
 
930
952
  if (included('html')) {
@@ -934,6 +956,10 @@ function compile (src, opts, url) {
934
956
  if (included('js')) {
935
957
  body = _compileJS(blocks[1], opts, null, null, url)
936
958
  if (body) jscode += (jscode ? '\n' : '') + body
959
+ jscode = jscode.replace(IMPORT_STATEMENT, function (s) {
960
+ imports += s.trim() + '\n'
961
+ return ''
962
+ })
937
963
  }
938
964
  }
939
965
  }
@@ -946,12 +972,13 @@ function compile (src, opts, url) {
946
972
  html: html,
947
973
  css: styles,
948
974
  attribs: attribs,
949
- js: jscode
975
+ js: jscode,
976
+ imports: imports
950
977
  })
951
978
  return ''
952
979
  }
953
980
 
954
- return mktag(tagName, html, styles, attribs, jscode, pcex)
981
+ return mktag(tagName, html, styles, attribs, jscode, imports, opts)
955
982
  })
956
983
 
957
984
  if (opts.entities) return parts