Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(672)

Side by Side Diff: pkg/polymer/lib/src/build/import_inliner.dart

Issue 427623002: Polymer transformer logs now show on the frontend for pub serve. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: added tests for the log widget Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
11 import 'package:analyzer/analyzer.dart'; 11 import 'package:analyzer/analyzer.dart';
12 import 'package:analyzer/src/generated/ast.dart'; 12 import 'package:analyzer/src/generated/ast.dart';
13 import 'package:barback/barback.dart'; 13 import 'package:barback/barback.dart';
14 import 'package:code_transformers/assets.dart'; 14 import 'package:code_transformers/assets.dart';
15 import 'package:path/path.dart' as path; 15 import 'package:path/path.dart' as path;
16 import 'package:html5lib/dom.dart' show 16 import 'package:html5lib/dom.dart' show
17 Document, DocumentFragment, Element, Node; 17 Document, DocumentFragment, Element, Node;
18 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; 18 import 'package:html5lib/dom_parsing.dart' show TreeVisitor;
19 import 'package:source_maps/refactor.dart' show TextEditTransaction; 19 import 'package:source_maps/refactor.dart' show TextEditTransaction;
20 import 'package:source_span/source_span.dart'; 20 import 'package:source_span/source_span.dart';
21 21
22 import 'common.dart'; 22 import 'common.dart';
23 import 'wrapped_logger.dart';
23 24
24 // TODO(sigmund): move to web_components package (dartbug.com/18037). 25 // TODO(sigmund): move to web_components package (dartbug.com/18037).
25 class _HtmlInliner extends PolymerTransformer { 26 class _HtmlInliner extends PolymerTransformer {
26 final TransformOptions options; 27 final TransformOptions options;
27 final Transform transform; 28 final Transform transform;
28 final TransformLogger logger; 29 final TransformLogger logger;
29 final AssetId docId; 30 final AssetId docId;
30 final seen = new Set<AssetId>(); 31 final seen = new Set<AssetId>();
31 final scriptIds = <AssetId>[]; 32 final scriptIds = <AssetId>[];
32 final extractedFiles = new Set<AssetId>(); 33 final extractedFiles = new Set<AssetId>();
33 bool experimentalBootstrap = false; 34 bool experimentalBootstrap = false;
34 35
35 /// The number of extracted inline Dart scripts. Used as a counter to give 36 /// The number of extracted inline Dart scripts. Used as a counter to give
36 /// unique-ish filenames. 37 /// unique-ish filenames.
37 int inlineScriptCounter = 0; 38 int inlineScriptCounter = 0;
38 39
39 _HtmlInliner(this.options, Transform transform) 40 _HtmlInliner(TransformOptions options, Transform transform)
40 : transform = transform, 41 : options = options,
41 logger = transform.logger, 42 transform = transform,
43 logger = new WrappedLogger(transform, convertErrorsToWarnings: true),
42 docId = transform.primaryInput.id; 44 docId = transform.primaryInput.id;
43 45
44 Future apply() { 46 Future apply() {
45 seen.add(docId); 47 seen.add(docId);
46 48
47 Document document; 49 Document document;
48 bool changed = false; 50 bool changed = false;
49 51
50 return readPrimaryAsHtml(transform).then((doc) { 52 return readPrimaryAsHtml(transform).then((doc) {
51 document = doc; 53 document = doc;
52 changed = new _UrlNormalizer(transform, docId).visit(document) || changed; 54 changed = new _UrlNormalizer(transform, docId, logger).visit(document)
55 || changed;
53 56
54 experimentalBootstrap = document.querySelectorAll('link').any((link) => 57 experimentalBootstrap = document.querySelectorAll('link').any((link) =>
55 link.attributes['rel'] == 'import' && 58 link.attributes['rel'] == 'import' &&
56 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); 59 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML);
57 changed = _extractScripts(document) || changed; 60 changed = _extractScripts(document) || changed;
58 return _visitImports(document); 61 return _visitImports(document);
59 }).then((importsFound) { 62 }).then((importsFound) {
60 changed = changed || importsFound; 63 changed = changed || importsFound;
61 return _removeScripts(document); 64 return _removeScripts(document);
62 }).then((scriptsRemoved) { 65 }).then((scriptsRemoved) {
63 changed = changed || scriptsRemoved; 66 changed = changed || scriptsRemoved;
64 67
65 var output = transform.primaryInput; 68 var output = transform.primaryInput;
66 if (changed) output = new Asset.fromString(docId, document.outerHtml); 69 if (changed) output = new Asset.fromString(docId, document.outerHtml);
67 transform.addOutput(output); 70 transform.addOutput(output);
68 71
69 // We produce a secondary asset with extra information for later phases. 72 // We produce a secondary asset with extra information for later phases.
70 transform.addOutput(new Asset.fromString( 73 transform.addOutput(new Asset.fromString(
71 docId.addExtension('._data'), 74 docId.addExtension('._data'),
72 JSON.encode({ 75 JSON.encode({
73 'experimental_bootstrap': experimentalBootstrap, 76 'experimental_bootstrap': experimentalBootstrap,
74 'script_ids': scriptIds, 77 'script_ids': scriptIds,
75 }, toEncodable: (id) => id.serialize()))); 78 }, toEncodable: (id) => id.serialize())));
79
80 // Write out the logs collected by our [WrappedLogger].
81 if (options.injectBuildLogsInOutput) return logger.writeOutput();
76 }); 82 });
77 } 83 }
78 84
79 /// Visits imports in [document] and add the imported documents to documents. 85 /// Visits imports in [document] and add the imported documents to documents.
80 /// Documents are added in the order they appear, transitive imports are added 86 /// Documents are added in the order they appear, transitive imports are added
81 /// first. 87 /// first.
82 /// 88 ///
83 /// Returns `true` if and only if the document was changed and should be 89 /// Returns `true` if and only if the document was changed and should be
84 /// written out. 90 /// written out.
85 Future<bool> _visitImports(Document document) { 91 Future<bool> _visitImports(Document document) {
86 bool changed = false; 92 bool changed = false;
87 93
88 _moveHeadToBody(document); 94 _moveHeadToBody(document);
89 95
90 // Note: we need to preserve the import order in the generated output. 96 // Note: we need to preserve the import order in the generated output.
91 return Future.forEach(document.querySelectorAll('link'), (Element tag) { 97 return Future.forEach(document.querySelectorAll('link'), (Element tag) {
92 var rel = tag.attributes['rel']; 98 var rel = tag.attributes['rel'];
93 if (rel != 'import' && rel != 'stylesheet') return null; 99 if (rel != 'import' && rel != 'stylesheet') return null;
94 100
95 // Note: URL has already been normalized so use docId. 101 // Note: URL has already been normalized so use docId.
96 var href = tag.attributes['href']; 102 var href = tag.attributes['href'];
97 var id = uriToAssetId(docId, href, transform.logger, tag.sourceSpan, 103 var id = uriToAssetId(docId, href, logger, tag.sourceSpan,
98 errorOnAbsolute: rel != 'stylesheet'); 104 errorOnAbsolute: rel != 'stylesheet');
99 105
100 if (rel == 'import') { 106 if (rel == 'import') {
101 changed = true; 107 changed = true;
102 if (id == null || !seen.add(id)) { 108 if (id == null || !seen.add(id)) {
103 tag.remove(); 109 tag.remove();
104 return null; 110 return null;
105 } 111 }
106 return _inlineImport(id, tag); 112 return _inlineImport(id, tag);
107 113
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
139 // Move the node into the body, where its contents will be placed. 145 // Move the node into the body, where its contents will be placed.
140 doc.body.insertBefore(node, insertionPoint); 146 doc.body.insertBefore(node, insertionPoint);
141 } 147 }
142 } 148 }
143 } 149 }
144 150
145 /// Loads an asset identified by [id], visits its imports and collects its 151 /// Loads an asset identified by [id], visits its imports and collects its
146 /// html imports. Then inlines it into the main document. 152 /// html imports. Then inlines it into the main document.
147 Future _inlineImport(AssetId id, Element link) { 153 Future _inlineImport(AssetId id, Element link) {
148 return readAsHtml(id, transform).catchError((error) { 154 return readAsHtml(id, transform).catchError((error) {
149 transform.logger.error( 155 logger.error(
150 "Failed to inline html import: $error", asset: id, 156 "Failed to inline html import: $error", asset: id,
151 span: link.sourceSpan); 157 span: link.sourceSpan);
152 }).then((doc) { 158 }).then((doc) {
153 if (doc == null) return false; 159 if (doc == null) return false;
154 new _UrlNormalizer(transform, id).visit(doc); 160 new _UrlNormalizer(transform, id, logger).visit(doc);
155 return _visitImports(doc).then((_) { 161 return _visitImports(doc).then((_) {
156 // _UrlNormalizer already ensures there is a library name. 162 // _UrlNormalizer already ensures there is a library name.
157 _extractScripts(doc, injectLibraryName: false); 163 _extractScripts(doc, injectLibraryName: false);
158 164
159 // TODO(jmesserly): figure out how this is working in vulcanizer. 165 // TODO(jmesserly): figure out how this is working in vulcanizer.
160 // Do they produce a <body> tag with a <head> and <body> inside? 166 // Do they produce a <body> tag with a <head> and <body> inside?
161 var imported = new DocumentFragment(); 167 var imported = new DocumentFragment();
162 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); 168 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes);
163 link.replaceWith(imported); 169 link.replaceWith(imported);
164 }); 170 });
165 }); 171 });
166 } 172 }
167 173
168 Future _inlineStylesheet(AssetId id, Element link) { 174 Future _inlineStylesheet(AssetId id, Element link) {
169 return transform.readInputAsString(id).catchError((error) { 175 return transform.readInputAsString(id).catchError((error) {
170 // TODO(jakemac): Move this warning to the linter once we can make it run 176 // TODO(jakemac): Move this warning to the linter once we can make it run
171 // always (see http://dartbug.com/17199). Then hide this error and replace 177 // always (see http://dartbug.com/17199). Then hide this error and replace
172 // with a comment pointing to the linter error (so we don't double warn). 178 // with a comment pointing to the linter error (so we don't double warn).
173 transform.logger.warning( 179 logger.warning(
174 "Failed to inline stylesheet: $error", asset: id, 180 "Failed to inline stylesheet: $error", asset: id,
175 span: link.sourceSpan); 181 span: link.sourceSpan);
176 }).then((css) { 182 }).then((css) {
177 if (css == null) return; 183 if (css == null) return;
178 css = new _UrlNormalizer(transform, id).visitCss(css); 184 css = new _UrlNormalizer(transform, id, logger).visitCss(css);
179 var styleElement = new Element.tag('style')..text = css; 185 var styleElement = new Element.tag('style')..text = css;
180 // Copy over the extra attributes from the link tag to the style tag. 186 // Copy over the extra attributes from the link tag to the style tag.
181 // This adds support for no-shim, shim-shadowdom, etc. 187 // This adds support for no-shim, shim-shadowdom, etc.
182 link.attributes.forEach((key, value) { 188 link.attributes.forEach((key, value) {
183 if (!IGNORED_LINKED_STYLE_ATTRS.contains(key)) { 189 if (!IGNORED_LINKED_STYLE_ATTRS.contains(key)) {
184 styleElement.attributes[key] = value; 190 styleElement.attributes[key] = value;
185 } 191 }
186 }); 192 });
187 link.replaceWith(styleElement); 193 link.replaceWith(styleElement);
188 }); 194 });
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after
320 /// Counter used to ensure that every library name we inject is unique. 326 /// Counter used to ensure that every library name we inject is unique.
321 int _count = 0; 327 int _count = 0;
322 328
323 /// Path to the top level folder relative to the transform primaryInput. 329 /// Path to the top level folder relative to the transform primaryInput.
324 /// This should just be some arbitrary # of ../'s. 330 /// This should just be some arbitrary # of ../'s.
325 final String topLevelPath; 331 final String topLevelPath;
326 332
327 /// Whether or not the normalizer has changed something in the tree. 333 /// Whether or not the normalizer has changed something in the tree.
328 bool changed = false; 334 bool changed = false;
329 335
330 _UrlNormalizer(transform, this.sourceId) 336 final TransformLogger logger;
337
338 _UrlNormalizer(transform, this.sourceId, this.logger)
331 : transform = transform, 339 : transform = transform,
332 topLevelPath = 340 topLevelPath =
333 '../' * (transform.primaryInput.id.path.split('/').length - 2); 341 '../' * (transform.primaryInput.id.path.split('/').length - 2);
334 342
335 visit(Node node) { 343 visit(Node node) {
336 super.visit(node); 344 super.visit(node);
337 return changed; 345 return changed;
338 } 346 }
339 347
340 visitElement(Element node) { 348 visitElement(Element node) {
341 // TODO(jakemac): Support custom elements that extend html elements which 349 // TODO(jakemac): Support custom elements that extend html elements which
342 // have url-like attributes. This probably means keeping a list of which 350 // have url-like attributes. This probably means keeping a list of which
343 // html elements support each url-like attribute. 351 // html elements support each url-like attribute.
344 if (!isCustomTagName(node.localName)) { 352 if (!isCustomTagName(node.localName)) {
345 node.attributes.forEach((name, value) { 353 node.attributes.forEach((name, value) {
346 if (_urlAttributes.contains(name)) { 354 if (_urlAttributes.contains(name)) {
347 if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) { 355 if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) {
348 transform.logger.warning( 356 logger.warning(
349 'When using bindings with the "$name" attribute you may ' 357 'When using bindings with the "$name" attribute you may '
350 'experience errors in certain browsers. Please use the ' 358 'experience errors in certain browsers. Please use the '
351 '"_$name" attribute instead. For more information, see ' 359 '"_$name" attribute instead. For more information, see '
352 'http://goo.gl/5av8cU', span: node.sourceSpan, asset: sourceId); 360 'http://goo.gl/5av8cU', span: node.sourceSpan, asset: sourceId);
353 } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) { 361 } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) {
354 transform.logger.warning( 362 logger.warning(
355 'The "$name" attribute is only supported when using bindings. ' 363 'The "$name" attribute is only supported when using bindings. '
356 'Please change to the "${name.substring(1)}" attribute.', 364 'Please change to the "${name.substring(1)}" attribute.',
357 span: node.sourceSpan, asset: sourceId); 365 span: node.sourceSpan, asset: sourceId);
358 } 366 }
359 if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) { 367 if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) {
360 node.attributes[name] = _newUrl(value, node.sourceSpan); 368 node.attributes[name] = _newUrl(value, node.sourceSpan);
361 changed = changed || value != node.attributes[name]; 369 changed = changed || value != node.attributes[name];
362 } 370 }
363 } 371 }
364 }); 372 });
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
400 String visitInlineDart(String code) { 408 String visitInlineDart(String code) {
401 var unit = parseDirectives(code, suppressErrors: true); 409 var unit = parseDirectives(code, suppressErrors: true);
402 var file = new SourceFile(code, url: spanUrlFor(sourceId, transform)); 410 var file = new SourceFile(code, url: spanUrlFor(sourceId, transform));
403 var output = new TextEditTransaction(code, file); 411 var output = new TextEditTransaction(code, file);
404 var foundLibraryDirective = false; 412 var foundLibraryDirective = false;
405 for (Directive directive in unit.directives) { 413 for (Directive directive in unit.directives) {
406 if (directive is UriBasedDirective) { 414 if (directive is UriBasedDirective) {
407 var uri = directive.uri.stringValue; 415 var uri = directive.uri.stringValue;
408 var span = _getSpan(file, directive.uri); 416 var span = _getSpan(file, directive.uri);
409 417
410 var id = uriToAssetId(sourceId, uri, transform.logger, span, 418 var id = uriToAssetId(sourceId, uri, logger, span,
411 errorOnAbsolute: false); 419 errorOnAbsolute: false);
412 if (id == null) continue; 420 if (id == null) continue;
413 421
414 var primaryId = transform.primaryInput.id; 422 var primaryId = transform.primaryInput.id;
415 var newUri = assetUrlFor(id, primaryId, transform.logger); 423 var newUri = assetUrlFor(id, primaryId, logger);
416 if (newUri != uri) { 424 if (newUri != uri) {
417 output.edit(span.start.offset, span.end.offset, "'$newUri'"); 425 output.edit(span.start.offset, span.end.offset, "'$newUri'");
418 } 426 }
419 } else if (directive is LibraryDirective) { 427 } else if (directive is LibraryDirective) {
420 foundLibraryDirective = true; 428 foundLibraryDirective = true;
421 } 429 }
422 } 430 }
423 431
424 if (!foundLibraryDirective) { 432 if (!foundLibraryDirective) {
425 // Ensure all inline scripts also have a library name. 433 // Ensure all inline scripts also have a library name.
(...skipping 23 matching lines...) Expand all
449 hrefToParse = '${href.substring(0, firstBinding)}$placeholder'; 457 hrefToParse = '${href.substring(0, firstBinding)}$placeholder';
450 } 458 }
451 459
452 var uri = Uri.parse(hrefToParse); 460 var uri = Uri.parse(hrefToParse);
453 if (uri.isAbsolute) return href; 461 if (uri.isAbsolute) return href;
454 if (!uri.scheme.isEmpty) return href; 462 if (!uri.scheme.isEmpty) return href;
455 if (!uri.host.isEmpty) return href; 463 if (!uri.host.isEmpty) return href;
456 if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI. 464 if (uri.path.isEmpty) return href; // Implies standalone ? or # in URI.
457 if (path.isAbsolute(href)) return href; 465 if (path.isAbsolute(href)) return href;
458 466
459 var id = uriToAssetId(sourceId, hrefToParse, transform.logger, span); 467 var id = uriToAssetId(sourceId, hrefToParse, logger, span);
460 if (id == null) return href; 468 if (id == null) return href;
461 var primaryId = transform.primaryInput.id; 469 var primaryId = transform.primaryInput.id;
462 470
463 // Build the new path, placing back any suffixes that we stripped earlier. 471 // Build the new path, placing back any suffixes that we stripped earlier.
464 var prefix = (firstBinding == -1) ? id.path 472 var prefix = (firstBinding == -1) ? id.path
465 : id.path.substring(0, id.path.length - placeholder.length); 473 : id.path.substring(0, id.path.length - placeholder.length);
466 var suffix = (firstBinding == -1) ? '' : href.substring(firstBinding); 474 var suffix = (firstBinding == -1) ? '' : href.substring(firstBinding);
467 var newPath = '$prefix$suffix'; 475 var newPath = '$prefix$suffix';
468 476
469 if (newPath.startsWith('lib/')) { 477 if (newPath.startsWith('lib/')) {
470 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}'; 478 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}';
471 } 479 }
472 480
473 if (newPath.startsWith('asset/')) { 481 if (newPath.startsWith('asset/')) {
474 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}'; 482 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}';
475 } 483 }
476 484
477 if (primaryId.package != id.package) { 485 if (primaryId.package != id.package) {
478 // Techincally we shouldn't get there 486 // Techincally we shouldn't get there
479 transform.logger.error("don't know how to include $id from $primaryId", 487 logger.error("don't know how to include $id from $primaryId", span: span);
480 span: span);
481 return href; 488 return href;
482 } 489 }
483 490
484 var builder = path.url; 491 var builder = path.url;
485 return builder.relative(builder.join('/', newPath), 492 return builder.relative(builder.join('/', newPath),
486 from: builder.join('/', builder.dirname(primaryId.path))); 493 from: builder.join('/', builder.dirname(primaryId.path)));
487 } 494 }
488 } 495 }
489 496
490 /// HTML attributes that expect a URL value. 497 /// HTML attributes that expect a URL value.
(...skipping 21 matching lines...) Expand all
512 /// style tag except these ones. 519 /// style tag except these ones.
513 const IGNORED_LINKED_STYLE_ATTRS = 520 const IGNORED_LINKED_STYLE_ATTRS =
514 const ['charset', 'href', 'href-lang', 'rel', 'rev']; 521 const ['charset', 'href', 'href-lang', 'rel', 'rev'];
515 522
516 /// Global RegExp objects. 523 /// Global RegExp objects.
517 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); 524 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]');
518 final _NUM_REGEX = new RegExp('[0-9]'); 525 final _NUM_REGEX = new RegExp('[0-9]');
519 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); 526 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))');
520 527
521 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); 528 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698