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 |