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; |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
48 } | 48 } |
49 transform.addOutput(output); | 49 transform.addOutput(output); |
50 | 50 |
51 // We produce a secondary asset with extra information for later phases. | 51 // We produce a secondary asset with extra information for later phases. |
52 transform.addOutput(new Asset.fromString( | 52 transform.addOutput(new Asset.fromString( |
53 docId.addExtension('.scriptUrls'), | 53 docId.addExtension('.scriptUrls'), |
54 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); | 54 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); |
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. | |
65 */ | |
66 Future<bool> _visitImports(Document document, AssetId sourceId) { | 64 Future<bool> _visitImports(Document document, AssetId sourceId) { |
67 bool changed = false; | 65 bool changed = false; |
68 | 66 |
69 _moveHeadToBody(document); | 67 _moveHeadToBody(document); |
70 | 68 |
71 // Note: we need to preserve the import order in the generated output. | 69 // Note: we need to preserve the import order in the generated output. |
72 return Future.forEach(document.querySelectorAll('link'), (Element tag) { | 70 return Future.forEach(document.querySelectorAll('link'), (Element tag) { |
73 var rel = tag.attributes['rel']; | 71 var rel = tag.attributes['rel']; |
74 if (rel != 'import' && rel != 'stylesheet') return null; | 72 if (rel != 'import' && rel != 'stylesheet') return null; |
75 | 73 |
(...skipping 11 matching lines...) Expand all Loading... |
87 | 85 |
88 } else if (rel == 'stylesheet') { | 86 } else if (rel == 'stylesheet') { |
89 if (id == null) return null; | 87 if (id == null) return null; |
90 changed = true; | 88 changed = true; |
91 | 89 |
92 return _inlineStylesheet(id, tag); | 90 return _inlineStylesheet(id, tag); |
93 } | 91 } |
94 }).then((_) => changed); | 92 }).then((_) => changed); |
95 } | 93 } |
96 | 94 |
97 /** | 95 /// To preserve the order of scripts with respect to inlined |
98 * To preserve the order of scripts with respect to inlined | 96 /// link rel=import, we move both of those into the body before we do any |
99 * link rel=import, we move both of those into the body before we do any | 97 /// inlining. |
100 * inlining. | 98 /// |
101 * | 99 /// Note: we do this for stylesheets as well to preserve ordering with |
102 * Note: we do this for stylesheets as well to preserve ordering with | 100 /// respect to eachother, because stylesheets can be pulled in transitively |
103 * respect to eachother, because stylesheets can be pulled in transitively | 101 /// from imports. |
104 * from imports. | |
105 */ | |
106 // TODO(jmesserly): vulcanizer doesn't need this because they inline JS | 102 // TODO(jmesserly): vulcanizer doesn't need this because they inline JS |
107 // scripts, causing them to be naturally moved as part of the inlining. | 103 // 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 | 104 // Should we do the same? Alternatively could we inline head into head and |
109 // body into body and avoid this whole thing? | 105 // body into body and avoid this whole thing? |
110 void _moveHeadToBody(Document doc) { | 106 void _moveHeadToBody(Document doc) { |
111 var insertionPoint = doc.body.firstChild; | 107 var insertionPoint = doc.body.firstChild; |
112 for (var node in doc.head.nodes.toList(growable: false)) { | 108 for (var node in doc.head.nodes.toList(growable: false)) { |
113 if (node is! Element) continue; | 109 if (node is! Element) continue; |
114 var tag = node.tagName; | 110 var tag = node.tagName; |
115 var type = node.attributes['type']; | 111 var type = node.attributes['type']; |
(...skipping 23 matching lines...) Expand all Loading... |
139 })); | 135 })); |
140 | 136 |
141 Future _inlineStylesheet(AssetId id, Element link) { | 137 Future _inlineStylesheet(AssetId id, Element link) { |
142 return transform.readInputAsString(id).then((css) { | 138 return transform.readInputAsString(id).then((css) { |
143 var url = spanUrlFor(id, transform); | 139 var url = spanUrlFor(id, transform); |
144 css = new _UrlNormalizer(transform, id).visitCss(css, url); | 140 css = new _UrlNormalizer(transform, id).visitCss(css, url); |
145 link.replaceWith(new Element.tag('style')..text = css); | 141 link.replaceWith(new Element.tag('style')..text = css); |
146 }); | 142 }); |
147 } | 143 } |
148 | 144 |
149 /** | 145 /// 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 | 146 /// 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 | 147 /// 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 | 148 /// them directly from the Dart bootstrap code. |
153 * them directly from the Dart bootstrap code. | |
154 */ | |
155 void _extractScripts(Document document) { | 149 void _extractScripts(Document document) { |
156 bool first = true; | 150 bool first = true; |
157 for (var script in document.querySelectorAll('script')) { | 151 for (var script in document.querySelectorAll('script')) { |
158 if (script.attributes['type'] == TYPE_DART) { | 152 if (script.attributes['type'] == TYPE_DART) { |
159 script.remove(); | 153 script.remove(); |
160 | 154 |
161 // only one Dart script per document is supported in Dartium. | 155 // only one Dart script per document is supported in Dartium. |
162 if (first) { | 156 if (first) { |
163 first = false; | 157 first = false; |
164 | 158 |
(...skipping 10 matching lines...) Expand all Loading... |
175 // TODO(jmesserly): remove this when we are running linter. | 169 // TODO(jmesserly): remove this when we are running linter. |
176 logger.warning('more than one Dart script per HTML ' | 170 logger.warning('more than one Dart script per HTML ' |
177 'document is not supported. Script will be ignored.', | 171 'document is not supported. Script will be ignored.', |
178 span: script.sourceSpan); | 172 span: script.sourceSpan); |
179 } | 173 } |
180 } | 174 } |
181 } | 175 } |
182 } | 176 } |
183 } | 177 } |
184 | 178 |
185 /** | 179 /// Recursively inlines the contents of HTML imports. Produces as output a |
186 * Recursively inlines the contents of HTML imports. Produces as output a single | 180 /// single HTML file that inlines the polymer-element definitions, and a text |
187 * HTML file that inlines the polymer-element definitions, and a text file that | 181 /// file that contains, in order, the URIs to each library that sourced in a |
188 * contains, in order, the URIs to each library that sourced in a script tag. | 182 /// script tag. |
189 * | 183 /// |
190 * This transformer assumes that all script tags point to external files. To | 184 /// This transformer assumes that all script tags point to external files. To |
191 * support script tags with inlined code, use this transformer after running | 185 /// support script tags with inlined code, use this transformer after running |
192 * [InlineCodeExtractor] on an earlier phase. | 186 /// [InlineCodeExtractor] on an earlier phase. |
193 */ | |
194 class ImportInliner extends Transformer { | 187 class ImportInliner extends Transformer { |
195 final TransformOptions options; | 188 final TransformOptions options; |
196 | 189 |
197 ImportInliner(this.options); | 190 ImportInliner(this.options); |
198 | 191 |
199 /** Only run on entry point .html files. */ | 192 /// Only run on entry point .html files. |
200 Future<bool> isPrimary(Asset input) => | 193 Future<bool> isPrimary(Asset input) => |
201 new Future.value(options.isHtmlEntryPoint(input.id)); | 194 new Future.value(options.isHtmlEntryPoint(input.id)); |
202 | 195 |
203 Future apply(Transform transform) => | 196 Future apply(Transform transform) => |
204 new _HtmlInliner(options, transform).apply(); | 197 new _HtmlInliner(options, transform).apply(); |
205 } | 198 } |
206 | 199 |
207 | 200 |
208 /** Internally adjusts urls in the html that we are about to inline. */ | 201 /// Internally adjusts urls in the html that we are about to inline. |
209 class _UrlNormalizer extends TreeVisitor { | 202 class _UrlNormalizer extends TreeVisitor { |
210 final Transform transform; | 203 final Transform transform; |
211 | 204 |
212 /** Asset where the original content (and original url) was found. */ | 205 /// Asset where the original content (and original url) was found. |
213 final AssetId sourceId; | 206 final AssetId sourceId; |
214 | 207 |
215 _UrlNormalizer(this.transform, this.sourceId); | 208 _UrlNormalizer(this.transform, this.sourceId); |
216 | 209 |
217 visitElement(Element node) { | 210 visitElement(Element node) { |
218 for (var key in node.attributes.keys) { | 211 for (var key in node.attributes.keys) { |
219 if (_urlAttributes.contains(key)) { | 212 if (_urlAttributes.contains(key)) { |
220 var url = node.attributes[key]; | 213 var url = node.attributes[key]; |
221 if (url != null && url != '' && !url.startsWith('{{')) { | 214 if (url != null && url != '' && !url.startsWith('{{')) { |
222 node.attributes[key] = _newUrl(url, node.sourceSpan); | 215 node.attributes[key] = _newUrl(url, node.sourceSpan); |
223 } | 216 } |
224 } | 217 } |
225 } | 218 } |
226 super.visitElement(node); | 219 super.visitElement(node); |
227 } | 220 } |
228 | 221 |
229 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); | 222 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); |
230 static final _QUOTE = new RegExp('["\']', multiLine: true); | 223 static final _QUOTE = new RegExp('["\']', multiLine: true); |
231 | 224 |
232 /** Visit the CSS text and replace any relative URLs so we can inline it. */ | 225 /// Visit the CSS text and replace any relative URLs so we can inline it. |
233 // Ported from: | 226 // Ported from: |
234 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 | 227 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 |
235 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. | 228 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. |
236 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. | 229 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. |
237 String visitCss(String cssText, String url) { | 230 String visitCss(String cssText, String url) { |
238 var src = new SourceFile.text(url, cssText); | 231 var src = new SourceFile.text(url, cssText); |
239 return cssText.replaceAllMapped(_URL, (match) { | 232 return cssText.replaceAllMapped(_URL, (match) { |
240 // Extract the URL, without any surrounding quotes. | 233 // Extract the URL, without any surrounding quotes. |
241 var span = src.span(match.start, match.end); | 234 var span = src.span(match.start, match.end); |
242 var href = match[1].replaceAll(_QUOTE, ''); | 235 var href = match[1].replaceAll(_QUOTE, ''); |
(...skipping 28 matching lines...) Expand all Loading... |
271 span: span); | 264 span: span); |
272 return href; | 265 return href; |
273 } | 266 } |
274 | 267 |
275 var builder = path.url; | 268 var builder = path.url; |
276 return builder.relative(builder.join('/', id.path), | 269 return builder.relative(builder.join('/', id.path), |
277 from: builder.join('/', builder.dirname(primaryId.path))); | 270 from: builder.join('/', builder.dirname(primaryId.path))); |
278 } | 271 } |
279 } | 272 } |
280 | 273 |
281 /** | 274 /// HTML attributes that expect a URL value. |
282 * HTML attributes that expect a URL value. | 275 /// <http://dev.w3.org/html5/spec/section-index.html#attributes-1> |
283 * <http://dev.w3.org/html5/spec/section-index.html#attributes-1> | 276 /// |
284 * | 277 /// Every one of these attributes is a URL in every context where it is used in |
285 * Every one of these attributes is a URL in every context where it is used in | 278 /// the DOM. The comments show every DOM element where an attribute can be used. |
286 * the DOM. The comments show every DOM element where an attribute can be used. | |
287 */ | |
288 const _urlAttributes = const [ | 279 const _urlAttributes = const [ |
289 'action', // in form | 280 'action', // in form |
290 'background', // in body | 281 'background', // in body |
291 'cite', // in blockquote, del, ins, q | 282 'cite', // in blockquote, del, ins, q |
292 'data', // in object | 283 'data', // in object |
293 'formaction', // in button, input | 284 'formaction', // in button, input |
294 'href', // in a, area, link, base, command | 285 'href', // in a, area, link, base, command |
295 'icon', // in command | 286 'icon', // in command |
296 'manifest', // in html | 287 'manifest', // in html |
297 'poster', // in video | 288 'poster', // in video |
298 'src', // in audio, embed, iframe, img, input, script, source, track, | 289 'src', // in audio, embed, iframe, img, input, script, source, track, |
299 // video | 290 // video |
300 ]; | 291 ]; |
OLD | NEW |