| 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 |