Chromium Code Reviews| 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 |