| 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 |
| 11 import 'package:barback/barback.dart'; | 11 import 'package:barback/barback.dart'; |
| 12 import 'package:path/path.dart' as path; | 12 import 'package:path/path.dart' as path; |
| 13 import 'package:html5lib/dom.dart' show | 13 import 'package:html5lib/dom.dart' show |
| 14 Document, DocumentFragment, Element, Node; | 14 Document, DocumentFragment, Element, Node; |
| 15 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; | 15 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; |
| 16 import 'package:source_maps/span.dart'; | 16 import 'package:source_maps/span.dart'; |
| 17 | 17 |
| 18 import 'code_extractor.dart'; // import just for documentation. | 18 import 'code_extractor.dart'; // import just for documentation. |
| 19 import 'common.dart'; | 19 import 'common.dart'; |
| 20 | 20 |
| 21 class _HtmlInliner extends PolymerTransformer { | 21 class _HtmlInliner extends PolymerTransformer { |
| 22 final TransformOptions options; | 22 final TransformOptions options; |
| 23 final Transform transform; | 23 final Transform transform; |
| 24 final TransformLogger logger; | 24 final TransformLogger logger; |
| 25 final AssetId docId; | 25 final AssetId docId; |
| 26 final seen = new Set<AssetId>(); | 26 final seen = new Set<AssetId>(); |
| 27 final imported = new DocumentFragment(); | |
| 28 final scriptIds = <AssetId>[]; | 27 final scriptIds = <AssetId>[]; |
| 29 | 28 |
| 29 static const TYPE_DART = 'application/dart'; |
| 30 static const TYPE_JS = 'text/javascript'; |
| 31 |
| 30 _HtmlInliner(this.options, Transform transform) | 32 _HtmlInliner(this.options, Transform transform) |
| 31 : transform = transform, | 33 : transform = transform, |
| 32 logger = transform.logger, | 34 logger = transform.logger, |
| 33 docId = transform.primaryInput.id; | 35 docId = transform.primaryInput.id; |
| 34 | 36 |
| 35 Future apply() { | 37 Future apply() { |
| 36 seen.add(docId); | 38 seen.add(docId); |
| 37 | 39 |
| 38 Document document; | 40 Document document; |
| 39 | 41 |
| 40 return readPrimaryAsHtml(transform).then((document) => | 42 return readPrimaryAsHtml(transform).then((document) => |
| 41 _visitImports(document, docId).then((importsFound) { | 43 _visitImports(document, docId).then((importsFound) { |
| 42 | 44 |
| 45 var output = transform.primaryInput; |
| 43 if (importsFound) { | 46 if (importsFound) { |
| 44 document.body.insertBefore(imported, document.body.firstChild); | 47 output = new Asset.fromString(docId, document.outerHtml); |
| 45 transform.addOutput(new Asset.fromString(docId, document.outerHtml)); | |
| 46 } else { | |
| 47 transform.addOutput(transform.primaryInput); | |
| 48 } | 48 } |
| 49 transform.addOutput(output); |
| 49 | 50 |
| 50 // We produce a secondary asset with extra information for later phases. | 51 // We produce a secondary asset with extra information for later phases. |
| 51 transform.addOutput(new Asset.fromString( | 52 transform.addOutput(new Asset.fromString( |
| 52 docId.addExtension('.scriptUrls'), | 53 docId.addExtension('.scriptUrls'), |
| 53 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); | 54 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); |
| 54 })); | 55 })); |
| 55 } | 56 } |
| 56 | 57 |
| 57 /** | 58 /** |
| 58 * Visits imports in [document] and add the imported documents to documents. | 59 * Visits imports in [document] and add the imported documents to documents. |
| 59 * Documents are added in the order they appear, transitive imports are added | 60 * Documents are added in the order they appear, transitive imports are added |
| 60 * first. | 61 * first. |
| 61 * | 62 * |
| 62 * Returns `true` if and only if the document was changed and should be | 63 * Returns `true` if and only if the document was changed and should be |
| 63 * written out. | 64 * written out. |
| 64 */ | 65 */ |
| 65 Future<bool> _visitImports(Document document, AssetId sourceId) { | 66 Future<bool> _visitImports(Document document, AssetId sourceId) { |
| 66 bool changed = false; | 67 bool changed = false; |
| 67 | 68 |
| 69 _moveHeadToBody(document); |
| 70 |
| 68 // Note: we need to preserve the import order in the generated output. | 71 // Note: we need to preserve the import order in the generated output. |
| 69 return Future.forEach(document.querySelectorAll('link'), (Element tag) { | 72 return Future.forEach(document.querySelectorAll('link'), (Element tag) { |
| 70 var rel = tag.attributes['rel']; | 73 var rel = tag.attributes['rel']; |
| 71 if (rel != 'import' && rel != 'stylesheet') return null; | 74 if (rel != 'import' && rel != 'stylesheet') return null; |
| 72 | 75 |
| 73 var href = tag.attributes['href']; | 76 var href = tag.attributes['href']; |
| 74 var id = resolve(sourceId, href, transform.logger, tag.sourceSpan, | 77 var id = resolve(sourceId, href, transform.logger, tag.sourceSpan, |
| 75 allowAbsolute: rel == 'stylesheet'); | 78 allowAbsolute: rel == 'stylesheet'); |
| 76 | 79 |
| 77 if (rel == 'import') { | 80 if (rel == 'import') { |
| 78 changed = true; | 81 changed = true; |
| 79 tag.remove(); | 82 if (id == null || !seen.add(id)) { |
| 80 if (id == null || !seen.add(id)) return null; | 83 tag.remove(); |
| 81 return _inlineImport(id); | 84 return null; |
| 85 } |
| 86 return _inlineImport(id, tag); |
| 82 | 87 |
| 83 } else if (rel == 'stylesheet') { | 88 } else if (rel == 'stylesheet') { |
| 84 if (id == null) return null; | 89 if (id == null) return null; |
| 85 changed = true; | 90 changed = true; |
| 91 |
| 86 return _inlineStylesheet(id, tag); | 92 return _inlineStylesheet(id, tag); |
| 87 } | 93 } |
| 88 }).then((_) => changed); | 94 }).then((_) => changed); |
| 89 } | 95 } |
| 90 | 96 |
| 97 /** |
| 98 * To preserve the order of scripts with respect to inlined |
| 99 * link rel=import, we move both of those into the body before we do any |
| 100 * inlining. |
| 101 * |
| 102 * Note: we do this for stylesheets as well to preserve ordering with |
| 103 * respect to eachother, because stylesheets can be pulled in transitively |
| 104 * from imports. |
| 105 */ |
| 106 // TODO(jmesserly): vulcanizer doesn't need this because they inline JS |
| 107 // scripts, causing them to be naturally moved as part of the inlining. |
| 108 // Should we do the same? Alternatively could we inline head into head and |
| 109 // body into body and avoid this whole thing? |
| 110 void _moveHeadToBody(Document doc) { |
| 111 var insertionPoint = doc.body.firstChild; |
| 112 for (var node in doc.head.nodes.toList(growable: false)) { |
| 113 if (node is! Element) continue; |
| 114 var tag = node.tagName; |
| 115 var type = node.attributes['type']; |
| 116 var rel = node.attributes['rel']; |
| 117 if (tag == 'style' || tag == 'script' && |
| 118 (type == null || type == TYPE_JS || type == TYPE_DART) || |
| 119 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { |
| 120 // Move the node into the body, where its contents will be placed. |
| 121 doc.body.insertBefore(node, insertionPoint); |
| 122 } |
| 123 } |
| 124 } |
| 125 |
| 91 // Loads an asset identified by [id], visits its imports and collects its | 126 // Loads an asset identified by [id], visits its imports and collects its |
| 92 // html imports. Then inlines it into the main document. | 127 // html imports. Then inlines it into the main document. |
| 93 Future _inlineImport(AssetId id) => | 128 Future _inlineImport(AssetId id, Element link) => |
| 94 readAsHtml(id, transform).then((doc) => _visitImports(doc, id).then((_) { | 129 readAsHtml(id, transform).then((doc) => _visitImports(doc, id).then((_) { |
| 95 | 130 |
| 96 new _UrlNormalizer(transform, id).visit(doc); | 131 new _UrlNormalizer(transform, id).visit(doc); |
| 97 _extractScripts(doc); | 132 _extractScripts(doc); |
| 98 | 133 |
| 99 // TODO(jmesserly): figure out how this is working in vulcanizer. | 134 // TODO(jmesserly): figure out how this is working in vulcanizer. |
| 100 // Do they produce a <body> tag with a <head> and <body> inside? | 135 // Do they produce a <body> tag with a <head> and <body> inside? |
| 136 var imported = new DocumentFragment(); |
| 101 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); | 137 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); |
| 138 link.replaceWith(imported); |
| 102 })); | 139 })); |
| 103 | 140 |
| 104 Future _inlineStylesheet(AssetId id, Element link) { | 141 Future _inlineStylesheet(AssetId id, Element link) { |
| 105 return transform.readInputAsString(id).then((css) { | 142 return transform.readInputAsString(id).then((css) { |
| 106 var url = spanUrlFor(id, transform); | 143 var url = spanUrlFor(id, transform); |
| 107 css = new _UrlNormalizer(transform, id).visitCss(css, url); | 144 css = new _UrlNormalizer(transform, id).visitCss(css, url); |
| 108 link.replaceWith(new Element.tag('style')..text = css); | 145 link.replaceWith(new Element.tag('style')..text = css); |
| 109 }); | 146 }); |
| 110 } | 147 } |
| 111 | 148 |
| 112 /** | 149 /** |
| 113 * Split Dart script tags from all the other elements. Now that Dartium | 150 * Split Dart script tags from all the other elements. Now that Dartium |
| 114 * only allows a single script tag per page, we can't inline script | 151 * only allows a single script tag per page, we can't inline script |
| 115 * tags. Instead, we collect the urls of each script tag so we import | 152 * tags. Instead, we collect the urls of each script tag so we import |
| 116 * them directly from the Dart bootstrap code. | 153 * them directly from the Dart bootstrap code. |
| 117 */ | 154 */ |
| 118 void _extractScripts(Document document) { | 155 void _extractScripts(Document document) { |
| 119 bool first = true; | 156 bool first = true; |
| 120 for (var script in document.querySelectorAll('script')) { | 157 for (var script in document.querySelectorAll('script')) { |
| 121 if (script.attributes['type'] == 'application/dart') { | 158 if (script.attributes['type'] == TYPE_DART) { |
| 122 script.remove(); | 159 script.remove(); |
| 123 | 160 |
| 124 // only one Dart script per document is supported in Dartium. | 161 // only one Dart script per document is supported in Dartium. |
| 125 if (first) { | 162 if (first) { |
| 126 first = false; | 163 first = false; |
| 127 | 164 |
| 128 var src = script.attributes['src']; | 165 var src = script.attributes['src']; |
| 129 if (src == null) { | 166 if (src == null) { |
| 130 logger.warning('unexpected script without a src url. The ' | 167 logger.warning('unexpected script without a src url. The ' |
| 131 'ImportInliner transformer should run after running the ' | 168 'ImportInliner transformer should run after running the ' |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 254 'cite', // in blockquote, del, ins, q | 291 'cite', // in blockquote, del, ins, q |
| 255 'data', // in object | 292 'data', // in object |
| 256 'formaction', // in button, input | 293 'formaction', // in button, input |
| 257 'href', // in a, area, link, base, command | 294 'href', // in a, area, link, base, command |
| 258 'icon', // in command | 295 'icon', // in command |
| 259 'manifest', // in html | 296 'manifest', // in html |
| 260 'poster', // in video | 297 'poster', // in video |
| 261 'src', // in audio, embed, iframe, img, input, script, source, track, | 298 'src', // in audio, embed, iframe, img, input, script, source, track, |
| 262 // video | 299 // video |
| 263 ]; | 300 ]; |
| OLD | NEW |