OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /// Transfomer that inlines polymer-element definitions from html imports. | 5 /// Transfomer that inlines polymer-element definitions from html imports. |
6 library polymer.src.build.import_inliner; | 6 library polymer.src.build.import_inliner; |
7 | 7 |
8 import 'dart:async'; | 8 import 'dart:async'; |
9 import 'dart:convert'; | 9 import 'dart:convert'; |
10 | 10 |
(...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
255 /// | 255 /// |
256 /// myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html | 256 /// myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html |
257 /// myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html | 257 /// myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html |
258 /// | 258 /// |
259 /// This should roughly match the recommended library name conventions. | 259 /// This should roughly match the recommended library name conventions. |
260 String _libraryNameFor(AssetId id, int suffix) { | 260 String _libraryNameFor(AssetId id, int suffix) { |
261 var name = '${path.withoutExtension(id.path)}_' | 261 var name = '${path.withoutExtension(id.path)}_' |
262 '${path.extension(id.path).substring(1)}'; | 262 '${path.extension(id.path).substring(1)}'; |
263 if (name.startsWith('lib/')) name = name.substring(4); | 263 if (name.startsWith('lib/')) name = name.substring(4); |
264 name = name.split('/').map((part) { | 264 name = name.split('/').map((part) { |
265 part = part.replaceAll(INVALID_LIB_CHARS_REGEX, '_'); | 265 part = part.replaceAll(_INVALID_LIB_CHARS_REGEX, '_'); |
266 if (part.startsWith(NUM_REGEX)) part = '_${part}'; | 266 if (part.startsWith(_NUM_REGEX)) part = '_${part}'; |
267 return part; | 267 return part; |
268 }).join("."); | 268 }).join("."); |
269 return '${id.package}.${name}_$suffix'; | 269 return '${id.package}.${name}_$suffix'; |
270 } | 270 } |
271 | 271 |
272 /// Parse [code] and determine whether it has a library directive. | 272 /// Parse [code] and determine whether it has a library directive. |
273 bool _hasLibraryDirective(String code) => | 273 bool _hasLibraryDirective(String code) => |
274 parseDirectives(code, suppressErrors: true) | 274 parseDirectives(code, suppressErrors: true) |
275 .directives.any((d) => d is LibraryDirective); | 275 .directives.any((d) => d is LibraryDirective); |
276 | 276 |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
330 return changed; | 330 return changed; |
331 } | 331 } |
332 | 332 |
333 visitElement(Element node) { | 333 visitElement(Element node) { |
334 // TODO(jakemac): Support custom elements that extend html elements which | 334 // TODO(jakemac): Support custom elements that extend html elements which |
335 // have url-like attributes. This probably means keeping a list of which | 335 // have url-like attributes. This probably means keeping a list of which |
336 // html elements support each url-like attribute. | 336 // html elements support each url-like attribute. |
337 if (!isCustomTagName(node.localName)) { | 337 if (!isCustomTagName(node.localName)) { |
338 node.attributes.forEach((name, value) { | 338 node.attributes.forEach((name, value) { |
339 if (_urlAttributes.contains(name)) { | 339 if (_urlAttributes.contains(name)) { |
340 if (value != '' && !value.trim().startsWith(_BINDINGS)) { | 340 if (value != '' && !value.trim().startsWith(_BINDINGS_REGEX)) { |
341 node.attributes[name] = _newUrl(value, node.sourceSpan); | 341 node.attributes[name] = _newUrl(value, node.sourceSpan); |
342 changed = changed || value != node.attributes[name]; | 342 changed = changed || value != node.attributes[name]; |
343 } | 343 } |
344 } | 344 } |
345 }); | 345 }); |
346 } | 346 } |
347 if (node.localName == 'style') { | 347 if (node.localName == 'style') { |
348 node.text = visitCss(node.text); | 348 node.text = visitCss(node.text); |
349 changed = true; | 349 changed = true; |
350 } else if (node.localName == 'script' && | 350 } else if (node.localName == 'script' && |
351 node.attributes['type'] == TYPE_DART && | 351 node.attributes['type'] == TYPE_DART && |
352 !node.attributes.containsKey('src')) { | 352 !node.attributes.containsKey('src')) { |
353 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony | 353 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony |
354 // modules. | 354 // modules. |
355 node.text = visitInlineDart(node.text); | 355 node.text = visitInlineDart(node.text); |
356 changed = true; | 356 changed = true; |
357 } | 357 } |
358 return super.visitElement(node); | 358 return super.visitElement(node); |
359 } | 359 } |
360 | 360 |
361 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); | 361 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); |
362 static final _QUOTE = new RegExp('["\']', multiLine: true); | 362 static final _QUOTE = new RegExp('["\']', multiLine: true); |
363 static final _BINDINGS = new RegExp(r'({{)|(\[\[)'); | |
364 | 363 |
365 /// Visit the CSS text and replace any relative URLs so we can inline it. | 364 /// Visit the CSS text and replace any relative URLs so we can inline it. |
366 // Ported from: | 365 // Ported from: |
367 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149 | 366 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149 |
368 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. | 367 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. |
369 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. | 368 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. |
370 String visitCss(String cssText) { | 369 String visitCss(String cssText) { |
371 var url = spanUrlFor(sourceId, transform); | 370 var url = spanUrlFor(sourceId, transform); |
372 var src = new SourceFile.text(url, cssText); | 371 var src = new SourceFile.text(url, cssText); |
373 return cssText.replaceAllMapped(_URL, (match) { | 372 return cssText.replaceAllMapped(_URL, (match) { |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
410 } | 409 } |
411 | 410 |
412 if (!output.hasEdits) return code; | 411 if (!output.hasEdits) return code; |
413 | 412 |
414 // TODO(sigmund): emit source maps when barback supports it (see | 413 // TODO(sigmund): emit source maps when barback supports it (see |
415 // dartbug.com/12340) | 414 // dartbug.com/12340) |
416 return (output.commit()..build(file.url)).text; | 415 return (output.commit()..build(file.url)).text; |
417 } | 416 } |
418 | 417 |
419 String _newUrl(String href, Span span) { | 418 String _newUrl(String href, Span span) { |
420 // Uri.parse blows up on invalid characters (like {{). Encoding the uri | 419 // Take all bindings out of the path and replace with [_BINDING_PLACEHOLDER] |
421 // allows it to be parsed, which does the correct thing in the general case. | 420 // so [Uri.parse] will not fail. |
422 // This uri not used to build the new uri, so it never needs to be decoded. | 421 var bindingMatches = _BINDING_REGEX.allMatches(href).map((m) => m.group(0)); |
423 var uri = Uri.parse(Uri.encodeFull(href)); | 422 href = href.replaceAll(_BINDING_REGEX, _BINDING_PLACEHOLDER); |
Siggi Cherem (dart-lang)
2014/07/31 21:54:22
I wonder if we need to replace all, or if we shoul
jakemac
2014/08/01 15:20:47
This seems like a good idea, but I tried it out an
Siggi Cherem (dart-lang)
2014/08/01 16:02:51
Interesting, here are a couple other ideas... it m
jakemac
2014/08/01 22:12:35
Done.
| |
423 | |
424 var uri = Uri.parse(href); | |
424 if (uri.isAbsolute) return href; | 425 if (uri.isAbsolute) return href; |
Siggi Cherem (dart-lang)
2014/07/31 21:54:22
shouldn't this return the original href without th
jakemac
2014/08/01 15:20:47
Good catch, I have updated it so I don't write ove
| |
425 if (!uri.scheme.isEmpty) return href; | 426 if (!uri.scheme.isEmpty) return href; |
426 if (!uri.host.isEmpty) return href; | 427 if (!uri.host.isEmpty) return href; |
427 if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI. | 428 if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI. |
428 if (path.isAbsolute(href)) return href; | 429 if (path.isAbsolute(href)) return href; |
429 | 430 |
430 var id = uriToAssetId(sourceId, href, transform.logger, span); | 431 var id = uriToAssetId(sourceId, href, transform.logger, span); |
431 if (id == null) return href; | 432 if (id == null) return href; |
432 var primaryId = transform.primaryInput.id; | 433 var primaryId = transform.primaryInput.id; |
433 | 434 |
434 if (id.path.startsWith('lib/')) { | 435 // Put the original bindings back into the path now that we have parsed it. |
435 return '${topLevelPath}packages/${id.package}/${id.path.substring(4)}'; | 436 var newPath = id.path; |
437 for (var bindingMatch in bindingMatches) { | |
438 newPath = newPath.replaceFirst(_BINDING_PLACEHOLDER, bindingMatch); | |
436 } | 439 } |
437 | 440 |
438 if (id.path.startsWith('asset/')) { | 441 if (newPath.startsWith('lib/')) { |
439 return '${topLevelPath}assets/${id.package}/${id.path.substring(6)}'; | 442 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}'; |
443 } | |
444 | |
445 if (newPath.startsWith('asset/')) { | |
446 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}'; | |
440 } | 447 } |
441 | 448 |
442 if (primaryId.package != id.package) { | 449 if (primaryId.package != id.package) { |
443 // Techincally we shouldn't get there | 450 // Techincally we shouldn't get there |
444 transform.logger.error("don't know how to include $id from $primaryId", | 451 transform.logger.error("don't know how to include $id from $primaryId", |
445 span: span); | 452 span: span); |
446 return href; | 453 return href; |
447 } | 454 } |
448 | 455 |
449 var builder = path.url; | 456 var builder = path.url; |
450 return builder.relative(builder.join('/', id.path), | 457 return builder.relative(builder.join('/', newPath), |
451 from: builder.join('/', builder.dirname(primaryId.path))); | 458 from: builder.join('/', builder.dirname(primaryId.path))); |
452 } | 459 } |
453 } | 460 } |
454 | 461 |
455 /// HTML attributes that expect a URL value. | 462 /// HTML attributes that expect a URL value. |
456 /// <http://dev.w3.org/html5/spec/section-index.html#attributes-1> | 463 /// <http://dev.w3.org/html5/spec/section-index.html#attributes-1> |
457 /// | 464 /// |
458 /// Every one of these attributes is a URL in every context where it is used in | 465 /// Every one of these attributes is a URL in every context where it is used in |
459 /// the DOM. The comments show every DOM element where an attribute can be used. | 466 /// the DOM. The comments show every DOM element where an attribute can be used. |
460 const _urlAttributes = const [ | 467 const _urlAttributes = const [ |
461 'action', // in form | 468 'action', // in form |
462 'background', // in body | 469 'background', // in body |
463 'cite', // in blockquote, del, ins, q | 470 'cite', // in blockquote, del, ins, q |
464 'data', // in object | 471 'data', // in object |
465 'formaction', // in button, input | 472 'formaction', // in button, input |
466 'href', // in a, area, link, base, command | 473 'href', // in a, area, link, base, command |
467 'icon', // in command | 474 'icon', // in command |
468 'manifest', // in html | 475 'manifest', // in html |
469 'poster', // in video | 476 'poster', // in video |
470 'src', // in audio, embed, iframe, img, input, script, source, track, | 477 'src', // in audio, embed, iframe, img, input, script, source, track, |
471 // video | 478 // video |
472 ]; | 479 ]; |
473 | 480 |
474 /// When inlining <link rel="stylesheet"> tags copy over all attributes to the | 481 /// When inlining <link rel="stylesheet"> tags copy over all attributes to the |
475 /// style tag except these ones. | 482 /// style tag except these ones. |
476 const IGNORED_LINKED_STYLE_ATTRS = | 483 const IGNORED_LINKED_STYLE_ATTRS = |
477 const ['charset', 'href', 'href-lang', 'rel', 'rev']; | 484 const ['charset', 'href', 'href-lang', 'rel', 'rev']; |
478 | 485 |
479 /// Global RegExp objects for validating generated library names. | 486 /// Global RegExp objects. |
480 final INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); | 487 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); |
481 final NUM_REGEX = new RegExp('[0-9]'); | 488 final _NUM_REGEX = new RegExp('[0-9]'); |
489 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); | |
490 | |
491 /// Placeholder for bindings in urls for [Uri.parse]. | |
492 final _BINDING_PLACEHOLDER = '%BINDING%'; | |
482 | 493 |
483 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); | 494 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); |
OLD | NEW |