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 250 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
261 /// | 261 /// |
262 /// myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html | 262 /// myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html |
263 /// myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html | 263 /// myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html |
264 /// | 264 /// |
265 /// This should roughly match the recommended library name conventions. | 265 /// This should roughly match the recommended library name conventions. |
266 String _libraryNameFor(AssetId id, int suffix) { | 266 String _libraryNameFor(AssetId id, int suffix) { |
267 var name = '${path.withoutExtension(id.path)}_' | 267 var name = '${path.withoutExtension(id.path)}_' |
268 '${path.extension(id.path).substring(1)}'; | 268 '${path.extension(id.path).substring(1)}'; |
269 if (name.startsWith('lib/')) name = name.substring(4); | 269 if (name.startsWith('lib/')) name = name.substring(4); |
270 name = name.split('/').map((part) { | 270 name = name.split('/').map((part) { |
271 part = part.replaceAll(INVALID_LIB_CHARS_REGEX, '_'); | 271 part = part.replaceAll(_INVALID_LIB_CHARS_REGEX, '_'); |
272 if (part.startsWith(NUM_REGEX)) part = '_${part}'; | 272 if (part.startsWith(_NUM_REGEX)) part = '_${part}'; |
273 return part; | 273 return part; |
274 }).join("."); | 274 }).join("."); |
275 return '${id.package}.${name}_$suffix'; | 275 return '${id.package}.${name}_$suffix'; |
276 } | 276 } |
277 | 277 |
278 /// Parse [code] and determine whether it has a library directive. | 278 /// Parse [code] and determine whether it has a library directive. |
279 bool _hasLibraryDirective(String code) => | 279 bool _hasLibraryDirective(String code) => |
280 parseDirectives(code, suppressErrors: true) | 280 parseDirectives(code, suppressErrors: true) |
281 .directives.any((d) => d is LibraryDirective); | 281 .directives.any((d) => d is LibraryDirective); |
282 | 282 |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
336 return changed; | 336 return changed; |
337 } | 337 } |
338 | 338 |
339 visitElement(Element node) { | 339 visitElement(Element node) { |
340 // TODO(jakemac): Support custom elements that extend html elements which | 340 // TODO(jakemac): Support custom elements that extend html elements which |
341 // have url-like attributes. This probably means keeping a list of which | 341 // have url-like attributes. This probably means keeping a list of which |
342 // html elements support each url-like attribute. | 342 // html elements support each url-like attribute. |
343 if (!isCustomTagName(node.localName)) { | 343 if (!isCustomTagName(node.localName)) { |
344 node.attributes.forEach((name, value) { | 344 node.attributes.forEach((name, value) { |
345 if (_urlAttributes.contains(name)) { | 345 if (_urlAttributes.contains(name)) { |
346 if (!name.startsWith('_') && value.contains(_BINDINGS)) { | 346 if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) { |
347 transform.logger.warning( | 347 transform.logger.warning( |
348 'When using bindings with the "$name" attribute you may ' | 348 'When using bindings with the "$name" attribute you may ' |
349 'experience errors in certain browsers. Please use the ' | 349 'experience errors in certain browsers. Please use the ' |
350 '"_$name" attribute instead. For more information, see ' | 350 '"_$name" attribute instead. For more information, see ' |
351 'http://goo.gl/5av8cU', span: node.sourceSpan, asset: sourceId); | 351 'http://goo.gl/5av8cU', span: node.sourceSpan, asset: sourceId); |
352 } else if (name.startsWith('_') && !value.contains(_BINDINGS)) { | 352 } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) { |
353 transform.logger.warning( | 353 transform.logger.warning( |
354 'The "$name" attribute is only supported when using bindings. ' | 354 'The "$name" attribute is only supported when using bindings. ' |
355 'Please change to the "${name.substring(1)}" attribute.', | 355 'Please change to the "${name.substring(1)}" attribute.', |
356 span: node.sourceSpan, asset: sourceId); | 356 span: node.sourceSpan, asset: sourceId); |
357 } | 357 } |
358 if (value != '' && !value.trim().startsWith(_BINDINGS)) { | 358 if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) { |
359 node.attributes[name] = _newUrl(value, node.sourceSpan); | 359 node.attributes[name] = _newUrl(value, node.sourceSpan); |
360 changed = changed || value != node.attributes[name]; | 360 changed = changed || value != node.attributes[name]; |
361 } | 361 } |
362 } | 362 } |
363 }); | 363 }); |
364 } | 364 } |
365 if (node.localName == 'style') { | 365 if (node.localName == 'style') { |
366 node.text = visitCss(node.text); | 366 node.text = visitCss(node.text); |
367 changed = true; | 367 changed = true; |
368 } else if (node.localName == 'script' && | 368 } else if (node.localName == 'script' && |
369 node.attributes['type'] == TYPE_DART && | 369 node.attributes['type'] == TYPE_DART && |
370 !node.attributes.containsKey('src')) { | 370 !node.attributes.containsKey('src')) { |
371 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony | 371 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony |
372 // modules. | 372 // modules. |
373 node.text = visitInlineDart(node.text); | 373 node.text = visitInlineDart(node.text); |
374 changed = true; | 374 changed = true; |
375 } | 375 } |
376 return super.visitElement(node); | 376 return super.visitElement(node); |
377 } | 377 } |
378 | 378 |
379 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); | 379 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); |
380 static final _QUOTE = new RegExp('["\']', multiLine: true); | 380 static final _QUOTE = new RegExp('["\']', multiLine: true); |
381 static final _BINDINGS = new RegExp(r'({{)|(\[\[)'); | |
382 | 381 |
383 /// Visit the CSS text and replace any relative URLs so we can inline it. | 382 /// Visit the CSS text and replace any relative URLs so we can inline it. |
384 // Ported from: | 383 // Ported from: |
385 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149 | 384 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378 acc691f/lib/vulcan.js#L149 |
386 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. | 385 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. |
387 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. | 386 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. |
388 String visitCss(String cssText) { | 387 String visitCss(String cssText) { |
389 var url = spanUrlFor(sourceId, transform); | 388 var url = spanUrlFor(sourceId, transform); |
390 var src = new SourceFile(cssText, url: url); | 389 var src = new SourceFile(cssText, url: url); |
391 return cssText.replaceAllMapped(_URL, (match) { | 390 return cssText.replaceAllMapped(_URL, (match) { |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
428 } | 427 } |
429 | 428 |
430 if (!output.hasEdits) return code; | 429 if (!output.hasEdits) return code; |
431 | 430 |
432 // TODO(sigmund): emit source maps when barback supports it (see | 431 // TODO(sigmund): emit source maps when barback supports it (see |
433 // dartbug.com/12340) | 432 // dartbug.com/12340) |
434 return (output.commit()..build(file.url.toString())).text; | 433 return (output.commit()..build(file.url.toString())).text; |
435 } | 434 } |
436 | 435 |
437 String _newUrl(String href, SourceSpan span) { | 436 String _newUrl(String href, SourceSpan span) { |
438 // Uri.parse blows up on invalid characters (like {{). Encoding the uri | 437 // We only want to parse the part of the href leading up to the first |
439 // allows it to be parsed, which does the correct thing in the general case. | 438 // binding, anything after that is not informative. |
440 // This uri not used to build the new uri, so it never needs to be decoded. | 439 var hrefToParse; |
441 var uri = Uri.parse(Uri.encodeFull(href)); | 440 var firstBinding = href.indexOf(_BINDING_REGEX); |
441 if (firstBinding == -1) { | |
442 hrefToParse = href; | |
443 } else if (firstBinding == 0) { | |
444 return href; | |
445 } else { | |
446 hrefToParse = '${href.substring(0, firstBinding)}$_BINDING_PLACEHOLDER'; | |
447 } | |
448 | |
449 var uri = Uri.parse(hrefToParse); | |
442 if (uri.isAbsolute) return href; | 450 if (uri.isAbsolute) return href; |
443 if (!uri.scheme.isEmpty) return href; | 451 if (!uri.scheme.isEmpty) return href; |
444 if (!uri.host.isEmpty) return href; | 452 if (!uri.host.isEmpty) return href; |
445 if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI. | 453 if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI. |
446 if (path.isAbsolute(href)) return href; | 454 if (path.isAbsolute(href)) return href; |
447 | 455 |
448 var id = uriToAssetId(sourceId, href, transform.logger, span); | 456 var id = uriToAssetId(sourceId, hrefToParse, transform.logger, span); |
449 if (id == null) return href; | 457 if (id == null) return href; |
450 var primaryId = transform.primaryInput.id; | 458 var primaryId = transform.primaryInput.id; |
451 | 459 |
452 if (id.path.startsWith('lib/')) { | 460 // Build the new path, placing back any suffixes that we stripped earlier. |
453 return '${topLevelPath}packages/${id.package}/${id.path.substring(4)}'; | 461 var prefix = (firstBinding < 1) ? id.path |
Siggi Cherem (dart-lang)
2014/08/04 19:39:38
nit: since we return automatically with == 0 upfro
jakemac
2014/08/04 19:55:01
Done, I think it is a bit better
| |
462 : id.path.substring(0, id.path.length - _BINDING_PLACEHOLDER.length); | |
463 var suffix = (firstBinding < 1) ? '' : href.substring(firstBinding); | |
464 var newPath = '$prefix$suffix'; | |
465 | |
466 if (newPath.startsWith('lib/')) { | |
467 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}'; | |
454 } | 468 } |
455 | 469 |
456 if (id.path.startsWith('asset/')) { | 470 if (newPath.startsWith('asset/')) { |
457 return '${topLevelPath}assets/${id.package}/${id.path.substring(6)}'; | 471 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}'; |
458 } | 472 } |
459 | 473 |
460 if (primaryId.package != id.package) { | 474 if (primaryId.package != id.package) { |
461 // Techincally we shouldn't get there | 475 // Techincally we shouldn't get there |
462 transform.logger.error("don't know how to include $id from $primaryId", | 476 transform.logger.error("don't know how to include $id from $primaryId", |
463 span: span); | 477 span: span); |
464 return href; | 478 return href; |
465 } | 479 } |
466 | 480 |
467 var builder = path.url; | 481 var builder = path.url; |
468 return builder.relative(builder.join('/', id.path), | 482 return builder.relative(builder.join('/', newPath), |
469 from: builder.join('/', builder.dirname(primaryId.path))); | 483 from: builder.join('/', builder.dirname(primaryId.path))); |
470 } | 484 } |
471 } | 485 } |
472 | 486 |
473 /// HTML attributes that expect a URL value. | 487 /// HTML attributes that expect a URL value. |
474 /// <http://dev.w3.org/html5/spec/section-index.html#attributes-1> | 488 /// <http://dev.w3.org/html5/spec/section-index.html#attributes-1> |
475 /// | 489 /// |
476 /// Every one of these attributes is a URL in every context where it is used in | 490 /// Every one of these attributes is a URL in every context where it is used in |
477 /// the DOM. The comments show every DOM element where an attribute can be used. | 491 /// the DOM. The comments show every DOM element where an attribute can be used. |
478 /// | 492 /// |
(...skipping 10 matching lines...) Expand all Loading... | |
489 'poster', '_poster', // in video | 503 'poster', '_poster', // in video |
490 'src', '_src', // in audio, embed, iframe, img, input, script, | 504 'src', '_src', // in audio, embed, iframe, img, input, script, |
491 // source, track,video | 505 // source, track,video |
492 ]; | 506 ]; |
493 | 507 |
494 /// When inlining <link rel="stylesheet"> tags copy over all attributes to the | 508 /// When inlining <link rel="stylesheet"> tags copy over all attributes to the |
495 /// style tag except these ones. | 509 /// style tag except these ones. |
496 const IGNORED_LINKED_STYLE_ATTRS = | 510 const IGNORED_LINKED_STYLE_ATTRS = |
497 const ['charset', 'href', 'href-lang', 'rel', 'rev']; | 511 const ['charset', 'href', 'href-lang', 'rel', 'rev']; |
498 | 512 |
499 /// Global RegExp objects for validating generated library names. | 513 /// Global RegExp objects. |
500 final INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); | 514 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); |
501 final NUM_REGEX = new RegExp('[0-9]'); | 515 final _NUM_REGEX = new RegExp('[0-9]'); |
516 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); | |
517 | |
518 /// Placeholder for bindings in urls for [Uri.parse]. | |
519 final _BINDING_PLACEHOLDER = '_'; | |
Siggi Cherem (dart-lang)
2014/08/04 19:39:38
nit: maybe rename or rephrase the comment a big ("
jakemac
2014/08/04 19:55:01
Done, went with the const local
| |
502 | 520 |
503 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); | 521 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); |
OLD | NEW |