| 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 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 | 22 |
| 23 // TODO(sigmund): move to web_components package (dartbug.com/18037). | 23 // TODO(sigmund): move to web_components package (dartbug.com/18037). |
| 24 class _HtmlInliner extends PolymerTransformer { | 24 class _HtmlInliner extends PolymerTransformer { |
| 25 final TransformOptions options; | 25 final TransformOptions options; |
| 26 final Transform transform; | 26 final Transform transform; |
| 27 final TransformLogger logger; | 27 final TransformLogger logger; |
| 28 final AssetId docId; | 28 final AssetId docId; |
| 29 final seen = new Set<AssetId>(); | 29 final seen = new Set<AssetId>(); |
| 30 final scriptIds = <AssetId>[]; | 30 final scriptIds = <AssetId>[]; |
| 31 final extractedFiles = new Set<AssetId>(); | 31 final extractedFiles = new Set<AssetId>(); |
| 32 bool experimentalBootstrap = false; |
| 32 | 33 |
| 33 /// The number of extracted inline Dart scripts. Used as a counter to give | 34 /// The number of extracted inline Dart scripts. Used as a counter to give |
| 34 /// unique-ish filenames. | 35 /// unique-ish filenames. |
| 35 int inlineScriptCounter = 0; | 36 int inlineScriptCounter = 0; |
| 36 | 37 |
| 37 _HtmlInliner(this.options, Transform transform) | 38 _HtmlInliner(this.options, Transform transform) |
| 38 : transform = transform, | 39 : transform = transform, |
| 39 logger = transform.logger, | 40 logger = transform.logger, |
| 40 docId = transform.primaryInput.id; | 41 docId = transform.primaryInput.id; |
| 41 | 42 |
| 42 Future apply() { | 43 Future apply() { |
| 43 seen.add(docId); | 44 seen.add(docId); |
| 44 | 45 |
| 45 Document document; | 46 Document document; |
| 46 bool changed; | 47 bool changed; |
| 47 | 48 |
| 48 return readPrimaryAsHtml(transform).then((doc) { | 49 return readPrimaryAsHtml(transform).then((doc) { |
| 49 document = doc; | 50 document = doc; |
| 50 // Add the main script's ID, or null if none is present. | 51 experimentalBootstrap = document.querySelectorAll('link').any((link) => |
| 51 // This will be used by ScriptCompactor. | 52 link.attributes['rel'] == 'import' && |
| 53 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); |
| 52 changed = _extractScripts(document, docId); | 54 changed = _extractScripts(document, docId); |
| 53 return _visitImports(document); | 55 return _visitImports(document); |
| 54 }).then((importsFound) { | 56 }).then((importsFound) { |
| 55 changed = changed || importsFound; | 57 changed = changed || importsFound; |
| 56 return _removeScripts(document); | 58 return _removeScripts(document); |
| 57 }).then((scriptsRemoved) { | 59 }).then((scriptsRemoved) { |
| 58 changed = changed || scriptsRemoved; | 60 changed = changed || scriptsRemoved; |
| 59 | 61 |
| 60 var output = transform.primaryInput; | 62 var output = transform.primaryInput; |
| 61 if (changed) output = new Asset.fromString(docId, document.outerHtml); | 63 if (changed) output = new Asset.fromString(docId, document.outerHtml); |
| 62 transform.addOutput(output); | 64 transform.addOutput(output); |
| 63 | 65 |
| 64 // We produce a secondary asset with extra information for later phases. | 66 // We produce a secondary asset with extra information for later phases. |
| 65 transform.addOutput(new Asset.fromString( | 67 transform.addOutput(new Asset.fromString( |
| 66 docId.addExtension('.scriptUrls'), | 68 docId.addExtension('._data'), |
| 67 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); | 69 JSON.encode({ |
| 70 'experimental_bootstrap': experimentalBootstrap, |
| 71 'script_ids': scriptIds, |
| 72 }, toEncodable: (id) => id.serialize()))); |
| 68 }); | 73 }); |
| 69 } | 74 } |
| 70 | 75 |
| 71 /// Visits imports in [document] and add the imported documents to documents. | 76 /// Visits imports in [document] and add the imported documents to documents. |
| 72 /// Documents are added in the order they appear, transitive imports are added | 77 /// Documents are added in the order they appear, transitive imports are added |
| 73 /// first. | 78 /// first. |
| 74 /// | 79 /// |
| 75 /// Returns `true` if and only if the document was changed and should be | 80 /// Returns `true` if and only if the document was changed and should be |
| 76 /// written out. | 81 /// written out. |
| 77 Future<bool> _visitImports(Document document) { | 82 Future<bool> _visitImports(Document document) { |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 118 // Should we do the same? Alternatively could we inline head into head and | 123 // Should we do the same? Alternatively could we inline head into head and |
| 119 // body into body and avoid this whole thing? | 124 // body into body and avoid this whole thing? |
| 120 void _moveHeadToBody(Document doc) { | 125 void _moveHeadToBody(Document doc) { |
| 121 var insertionPoint = doc.body.firstChild; | 126 var insertionPoint = doc.body.firstChild; |
| 122 for (var node in doc.head.nodes.toList(growable: false)) { | 127 for (var node in doc.head.nodes.toList(growable: false)) { |
| 123 if (node is! Element) continue; | 128 if (node is! Element) continue; |
| 124 var tag = node.localName; | 129 var tag = node.localName; |
| 125 var type = node.attributes['type']; | 130 var type = node.attributes['type']; |
| 126 var rel = node.attributes['rel']; | 131 var rel = node.attributes['rel']; |
| 127 if (tag == 'style' || tag == 'script' && | 132 if (tag == 'style' || tag == 'script' && |
| 128 (type == null || type == TYPE_JS || type == TYPE_DART_APP || | 133 (type == null || type == TYPE_JS || type == TYPE_DART) || |
| 129 type == TYPE_DART_COMPONENT) || | |
| 130 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { | 134 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { |
| 131 // Move the node into the body, where its contents will be placed. | 135 // Move the node into the body, where its contents will be placed. |
| 132 doc.body.insertBefore(node, insertionPoint); | 136 doc.body.insertBefore(node, insertionPoint); |
| 133 } | 137 } |
| 134 } | 138 } |
| 135 } | 139 } |
| 136 | 140 |
| 137 /// Loads an asset identified by [id], visits its imports and collects its | 141 /// Loads an asset identified by [id], visits its imports and collects its |
| 138 /// html imports. Then inlines it into the main document. | 142 /// html imports. Then inlines it into the main document. |
| 139 Future _inlineImport(AssetId id, Element link) { | 143 Future _inlineImport(AssetId id, Element link) { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 151 }); | 155 }); |
| 152 } | 156 } |
| 153 | 157 |
| 154 Future _inlineStylesheet(AssetId id, Element link) { | 158 Future _inlineStylesheet(AssetId id, Element link) { |
| 155 return transform.readInputAsString(id).then((css) { | 159 return transform.readInputAsString(id).then((css) { |
| 156 css = new _UrlNormalizer(transform, id).visitCss(css); | 160 css = new _UrlNormalizer(transform, id).visitCss(css); |
| 157 link.replaceWith(new Element.tag('style')..text = css); | 161 link.replaceWith(new Element.tag('style')..text = css); |
| 158 }); | 162 }); |
| 159 } | 163 } |
| 160 | 164 |
| 161 /// Remove "application/dart;component=1" scripts and remember their | 165 /// Remove all Dart scripts and remember their [AssetId]s for later use. |
| 162 /// [AssetId]s for later use. | |
| 163 /// | 166 /// |
| 164 /// Dartium only allows a single script tag per page, so we can't inline | 167 /// Dartium only allows a single script tag per page, so we can't inline |
| 165 /// the script tags. Instead we remove them entirely. | 168 /// the script tags. Instead we remove them entirely. |
| 166 Future<bool> _removeScripts(Document doc) { | 169 Future<bool> _removeScripts(Document doc) { |
| 167 bool changed = false; | 170 bool changed = false; |
| 168 return Future.forEach(doc.querySelectorAll('script'), (script) { | 171 return Future.forEach(doc.querySelectorAll('script'), (script) { |
| 169 if (script.attributes['type'] == TYPE_DART_COMPONENT) { | 172 if (script.attributes['type'] == TYPE_DART) { |
| 170 changed = true; | 173 changed = true; |
| 171 script.remove(); | 174 script.remove(); |
| 172 var src = script.attributes['src']; | 175 var src = script.attributes['src']; |
| 173 var srcId = uriToAssetId(docId, src, logger, script.sourceSpan); | 176 var srcId = uriToAssetId(docId, src, logger, script.sourceSpan); |
| 174 | 177 |
| 175 // We check for extractedFiles because 'hasInput' below is only true for | 178 // We check for extractedFiles because 'hasInput' below is only true for |
| 176 // assets that existed before this transformer runs (hasInput is false | 179 // assets that existed before this transformer runs (hasInput is false |
| 177 // for files created by [_extractScripts]). | 180 // for files created by [_extractScripts]). |
| 178 if (extractedFiles.contains(srcId)) { | 181 if (extractedFiles.contains(srcId)) { |
| 179 scriptIds.add(srcId); | 182 scriptIds.add(srcId); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 190 } | 193 } |
| 191 }).then((_) => changed); | 194 }).then((_) => changed); |
| 192 } | 195 } |
| 193 | 196 |
| 194 /// Split inline scripts into their own files. We need to do this for dart2js | 197 /// Split inline scripts into their own files. We need to do this for dart2js |
| 195 /// to be able to compile them. | 198 /// to be able to compile them. |
| 196 /// | 199 /// |
| 197 /// This also validates that there weren't any duplicate scripts. | 200 /// This also validates that there weren't any duplicate scripts. |
| 198 bool _extractScripts(Document doc, AssetId sourceId) { | 201 bool _extractScripts(Document doc, AssetId sourceId) { |
| 199 bool changed = false; | 202 bool changed = false; |
| 200 bool first = true; | |
| 201 for (var script in doc.querySelectorAll('script')) { | 203 for (var script in doc.querySelectorAll('script')) { |
| 202 var type = script.attributes['type']; | 204 if (script.attributes['type'] != TYPE_DART) continue; |
| 203 if (type != TYPE_DART_COMPONENT && type != TYPE_DART_APP) continue; | |
| 204 | |
| 205 // only one Dart script per document is supported in Dartium. | |
| 206 if (type == TYPE_DART_APP) { | |
| 207 if (!first) logger.warning(COMPONENT_WARNING, span: script.sourceSpan); | |
| 208 first = false; | |
| 209 } | |
| 210 | 205 |
| 211 var src = script.attributes['src']; | 206 var src = script.attributes['src']; |
| 212 if (src != null) continue; | 207 if (src != null) continue; |
| 213 | 208 |
| 214 final filename = path.url.basename(docId.path); | 209 final filename = path.url.basename(docId.path); |
| 215 final count = inlineScriptCounter++; | 210 final count = inlineScriptCounter++; |
| 216 var code = script.text; | 211 var code = script.text; |
| 217 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). | 212 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). |
| 218 script.attributes['src'] = src = '$filename.$count.dart'; | 213 script.attributes['src'] = src = '$filename.$count.dart'; |
| 219 script.text = ''; | 214 script.text = ''; |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 267 // is dropped. | 262 // is dropped. |
| 268 Future<bool> isPrimary(idOrAsset) { | 263 Future<bool> isPrimary(idOrAsset) { |
| 269 var id = idOrAsset is AssetId ? idOrAsset : idOrAsset.id; | 264 var id = idOrAsset is AssetId ? idOrAsset : idOrAsset.id; |
| 270 return new Future.value(options.isHtmlEntryPoint(id)); | 265 return new Future.value(options.isHtmlEntryPoint(id)); |
| 271 } | 266 } |
| 272 | 267 |
| 273 Future apply(Transform transform) => | 268 Future apply(Transform transform) => |
| 274 new _HtmlInliner(options, transform).apply(); | 269 new _HtmlInliner(options, transform).apply(); |
| 275 } | 270 } |
| 276 | 271 |
| 277 const TYPE_DART_APP = 'application/dart'; | 272 const TYPE_DART = 'application/dart'; |
| 278 const TYPE_DART_COMPONENT = 'application/dart;component=1'; | |
| 279 const TYPE_JS = 'text/javascript'; | 273 const TYPE_JS = 'text/javascript'; |
| 280 | 274 |
| 281 /// Internally adjusts urls in the html that we are about to inline. | 275 /// Internally adjusts urls in the html that we are about to inline. |
| 282 class _UrlNormalizer extends TreeVisitor { | 276 class _UrlNormalizer extends TreeVisitor { |
| 283 final Transform transform; | 277 final Transform transform; |
| 284 | 278 |
| 285 /// Asset where the original content (and original url) was found. | 279 /// Asset where the original content (and original url) was found. |
| 286 final AssetId sourceId; | 280 final AssetId sourceId; |
| 287 | 281 |
| 288 _UrlNormalizer(this.transform, this.sourceId); | 282 _UrlNormalizer(this.transform, this.sourceId); |
| 289 | 283 |
| 290 visitElement(Element node) { | 284 visitElement(Element node) { |
| 291 node.attributes.forEach((name, value) { | 285 node.attributes.forEach((name, value) { |
| 292 if (_urlAttributes.contains(name)) { | 286 if (_urlAttributes.contains(name)) { |
| 293 if (value != '' && !value.trim().startsWith('{{')) { | 287 if (value != '' && !value.trim().startsWith('{{')) { |
| 294 node.attributes[name] = _newUrl(value, node.sourceSpan); | 288 node.attributes[name] = _newUrl(value, node.sourceSpan); |
| 295 } | 289 } |
| 296 } | 290 } |
| 297 }); | 291 }); |
| 298 if (node.localName == 'style') { | 292 if (node.localName == 'style') { |
| 299 node.text = visitCss(node.text); | 293 node.text = visitCss(node.text); |
| 300 } else if (node.localName == 'script') { | 294 } else if (node.localName == 'script' && |
| 301 var type = node.attributes['type']; | 295 node.attributes['type'] == TYPE_DART) { |
| 302 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony | 296 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony |
| 303 // modules. | 297 // modules. |
| 304 if (type == TYPE_DART_APP || type == TYPE_DART_COMPONENT) { | 298 node.text = visitInlineDart(node.text); |
| 305 node.text = visitInlineDart(node.text); | |
| 306 } | |
| 307 } | 299 } |
| 308 super.visitElement(node); | 300 super.visitElement(node); |
| 309 } | 301 } |
| 310 | 302 |
| 311 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); | 303 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); |
| 312 static final _QUOTE = new RegExp('["\']', multiLine: true); | 304 static final _QUOTE = new RegExp('["\']', multiLine: true); |
| 313 | 305 |
| 314 /// Visit the CSS text and replace any relative URLs so we can inline it. | 306 /// Visit the CSS text and replace any relative URLs so we can inline it. |
| 315 // Ported from: | 307 // Ported from: |
| 316 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 | 308 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 403 'formaction', // in button, input | 395 'formaction', // in button, input |
| 404 'href', // in a, area, link, base, command | 396 'href', // in a, area, link, base, command |
| 405 'icon', // in command | 397 'icon', // in command |
| 406 'manifest', // in html | 398 'manifest', // in html |
| 407 'poster', // in video | 399 'poster', // in video |
| 408 'src', // in audio, embed, iframe, img, input, script, source, track, | 400 'src', // in audio, embed, iframe, img, input, script, source, track, |
| 409 // video | 401 // video |
| 410 ]; | 402 ]; |
| 411 | 403 |
| 412 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); | 404 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); |
| 413 | |
| 414 const COMPONENT_WARNING = | |
| 415 'More than one Dart script per HTML document is not supported, but in the ' | |
| 416 'near future Dartium will execute each tag as a separate isolate. If this ' | |
| 417 'code is meant to load definitions that are part of the same application ' | |
| 418 'you should switch it to use the "application/dart;component=1" mime-type ' | |
| 419 'instead.'; | |
| OLD | NEW |