| 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' show Span; | 16 import 'package:source_maps/span.dart' show Span; |
| 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 /** | 21 class _HtmlInliner extends PolymerTransformer { |
| 22 * Recursively inlines the contents of HTML imports. Produces as output a single | |
| 23 * HTML file that inlines the polymer-element definitions, and a text file that | |
| 24 * contains, in order, the URIs to each library that sourced in a script tag. | |
| 25 * | |
| 26 * This transformer assumes that all script tags point to external files. To | |
| 27 * support script tags with inlined code, use this transformer after running | |
| 28 * [InlineCodeExtractor] on an earlier phase. | |
| 29 */ | |
| 30 class ImportInliner extends Transformer with PolymerTransformer { | |
| 31 final TransformOptions options; | 22 final TransformOptions options; |
| 23 final Transform transform; |
| 24 final TransformLogger logger; |
| 25 final AssetId docId; |
| 26 final seen = new Set<AssetId>(); |
| 27 final imported = new DocumentFragment(); |
| 28 final scriptIds = <AssetId>[]; |
| 32 | 29 |
| 33 ImportInliner(this.options); | 30 _HtmlInliner(this.options, Transform transform) |
| 31 : transform = transform, |
| 32 logger = transform.logger, |
| 33 docId = transform.primaryInput.id; |
| 34 | 34 |
| 35 /** Only run on entry point .html files. */ | 35 Future apply() { |
| 36 Future<bool> isPrimary(Asset input) => | 36 seen.add(docId); |
| 37 new Future.value(options.isHtmlEntryPoint(input.id)); | |
| 38 | 37 |
| 39 Future apply(Transform transform) { | 38 Document document; |
| 40 var logger = transform.logger; | |
| 41 var seen = new Set<AssetId>(); | |
| 42 var documents = []; | |
| 43 var id = transform.primaryInput.id; | |
| 44 seen.add(id); | |
| 45 return readPrimaryAsHtml(transform).then((document) { | |
| 46 var future = _visitImports(document, id, transform, seen, documents); | |
| 47 return future.then((importsFound) { | |
| 48 // We produce a secondary asset with extra information for later phases. | |
| 49 var secondaryId = id.addExtension('.scriptUrls'); | |
| 50 if (!importsFound) { | |
| 51 transform.addOutput(transform.primaryInput); | |
| 52 transform.addOutput(new Asset.fromString(secondaryId, '[]')); | |
| 53 return; | |
| 54 } | |
| 55 | 39 |
| 56 // Split Dart script tags from all the other elements. Now that Dartium | 40 return readPrimaryAsHtml(transform).then((document) => |
| 57 // only allows a single script tag per page, we can't inline script | 41 _visitImports(document, docId).then((importsFound) { |
| 58 // tags. Instead, we collect the urls of each script tag so we import | |
| 59 // them directly from the Dart bootstrap code. | |
| 60 var scripts = []; | |
| 61 | 42 |
| 62 var fragment = new DocumentFragment(); | 43 if (importsFound) { |
| 63 for (var importedDoc in documents) { | 44 document.body.insertBefore(imported, document.body.firstChild); |
| 64 bool first = true; | 45 transform.addOutput(new Asset.fromString(docId, document.outerHtml)); |
| 65 for (var e in importedDoc.queryAll('script')) { | 46 } else { |
| 66 if (e.attributes['type'] == 'application/dart') { | 47 transform.addOutput(transform.primaryInput); |
| 67 e.remove(); | 48 } |
| 68 | 49 |
| 69 // only one Dart script per document is supported in Dartium. | 50 // We produce a secondary asset with extra information for later phases. |
| 70 if (first) { | 51 transform.addOutput(new Asset.fromString( |
| 71 first = false; | 52 docId.addExtension('.scriptUrls'), |
| 72 scripts.add(e); | 53 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); |
| 73 } else { | 54 })); |
| 74 // TODO(jmesserly): remove this when we are running linter. | 55 } |
| 75 logger.warning('more than one Dart script per HTML document is ' | |
| 76 'not supported. Script will be ignored.', | |
| 77 span: e.sourceSpan); | |
| 78 } | |
| 79 } | |
| 80 } | |
| 81 | 56 |
| 82 // TODO(jmesserly): should we merge the head too? | 57 /** |
| 83 fragment.nodes.addAll(importedDoc.body.nodes); | 58 * Visits imports in [document] and add the imported documents to [documents]. |
| 84 } | 59 * Documents are added in the order they appear, transitive imports are added |
| 60 * first. |
| 61 */ |
| 62 Future<bool> _visitImports(Document document, AssetId sourceId) { |
| 63 bool hasImports = false; |
| 85 | 64 |
| 86 document.body.insertBefore(fragment, document.body.firstChild); | 65 // Note: we need to preserve the import order in the generated output. |
| 66 return Future.forEach(document.querySelectorAll('link'), (Element tag) { |
| 67 if (tag.attributes['rel'] != 'import') return null; |
| 68 var href = tag.attributes['href']; |
| 69 var id = resolve(sourceId, href, transform.logger, tag.sourceSpan); |
| 70 hasImports = true; |
| 87 | 71 |
| 88 for (var tag in document.queryAll('link')) { | 72 tag.remove(); |
| 89 if (tag.attributes['rel'] == 'import') tag.remove(); | 73 if (id == null || !seen.add(id) || |
| 90 } | 74 (id.package == 'polymer' && id.path == 'lib/init.html')) return null; |
| 91 | 75 |
| 92 transform.addOutput(new Asset.fromString(id, document.outerHtml)); | 76 return _inlineImport(id); |
| 77 }).then((_) => hasImports); |
| 78 } |
| 93 | 79 |
| 94 var scriptIds = []; | 80 // Loads an asset identified by [id], visits its imports and collects its |
| 95 for (var script in scripts) { | 81 // html imports. Then inlines it into the main document. |
| 82 Future _inlineImport(AssetId id) => |
| 83 readAsHtml(id, transform).then((doc) => _visitImports(doc, id).then((_) { |
| 84 |
| 85 new _UrlNormalizer(transform, id).visit(doc); |
| 86 _extractScripts(doc); |
| 87 |
| 88 // TODO(jmesserly): figure out how this is working in vulcanizer. |
| 89 // Do they produce a <body> tag with a <head> and <body> inside? |
| 90 imported.nodes |
| 91 ..addAll(doc.head.nodes) |
| 92 ..addAll(doc.body.nodes); |
| 93 })); |
| 94 |
| 95 /** |
| 96 * Split Dart script tags from all the other elements. Now that Dartium |
| 97 * only allows a single script tag per page, we can't inline script |
| 98 * tags. Instead, we collect the urls of each script tag so we import |
| 99 * them directly from the Dart bootstrap code. |
| 100 */ |
| 101 void _extractScripts(Document document) { |
| 102 bool first = true; |
| 103 for (var script in document.querySelectorAll('script')) { |
| 104 if (script.attributes['type'] == 'application/dart') { |
| 105 script.remove(); |
| 106 |
| 107 // only one Dart script per document is supported in Dartium. |
| 108 if (first) { |
| 109 first = false; |
| 110 |
| 96 var src = script.attributes['src']; | 111 var src = script.attributes['src']; |
| 97 if (src == null) { | 112 if (src == null) { |
| 98 logger.warning('unexpected script without a src url. The ' | 113 logger.warning('unexpected script without a src url. The ' |
| 99 'ImportInliner transformer should run after running the ' | 114 'ImportInliner transformer should run after running the ' |
| 100 'InlineCodeExtractor', span: script.sourceSpan); | 115 'InlineCodeExtractor', span: script.sourceSpan); |
| 101 continue; | 116 continue; |
| 102 } | 117 } |
| 103 scriptIds.add(resolve(id, src, logger, script.sourceSpan)); | 118 scriptIds.add(resolve(docId, src, logger, script.sourceSpan)); |
| 119 |
| 120 } else { |
| 121 // TODO(jmesserly): remove this when we are running linter. |
| 122 logger.warning('more than one Dart script per HTML ' |
| 123 'document is not supported. Script will be ignored.', |
| 124 span: script.sourceSpan); |
| 104 } | 125 } |
| 105 transform.addOutput(new Asset.fromString(secondaryId, | 126 } |
| 106 JSON.encode(scriptIds, toEncodable: (id) => id.serialize()))); | |
| 107 }); | |
| 108 }); | |
| 109 } | |
| 110 | |
| 111 /** | |
| 112 * Visits imports in [document] and add their polymer-element and script tags | |
| 113 * to [elements], unless they have already been [seen]. Elements are added in | |
| 114 * the order they appear, transitive imports are added first. | |
| 115 */ | |
| 116 Future<bool> _visitImports(Document document, AssetId sourceId, | |
| 117 Transform transform, Set<AssetId> seen, List<Document> documents) { | |
| 118 var importIds = []; | |
| 119 bool hasImports = false; | |
| 120 for (var tag in document.queryAll('link')) { | |
| 121 if (tag.attributes['rel'] != 'import') continue; | |
| 122 var href = tag.attributes['href']; | |
| 123 var id = resolve(sourceId, href, transform.logger, tag.sourceSpan); | |
| 124 hasImports = true; | |
| 125 if (id == null || seen.contains(id) || | |
| 126 (id.package == 'polymer' && id.path == 'lib/init.html')) continue; | |
| 127 importIds.add(id); | |
| 128 } | 127 } |
| 129 | |
| 130 if (importIds.isEmpty) return new Future.value(hasImports); | |
| 131 | |
| 132 // Note: we need to preserve the import order in the generated output. | |
| 133 return Future.forEach(importIds, (id) { | |
| 134 if (seen.contains(id)) return new Future.value(null); | |
| 135 seen.add(id); | |
| 136 return _collectImportedDocuments(id, transform, seen, documents); | |
| 137 }).then((_) => true); | |
| 138 } | |
| 139 | |
| 140 /** | |
| 141 * Loads an asset identified by [id], visits its imports and collects it's | |
| 142 * polymer-element definitions and script tags. | |
| 143 */ | |
| 144 Future _collectImportedDocuments(AssetId id, Transform transform, | |
| 145 Set<AssetId> seen, List documents) { | |
| 146 return readAsHtml(id, transform).then((document) { | |
| 147 return _visitImports(document, id, transform, seen, documents).then((_) { | |
| 148 new _UrlNormalizer(transform, id).visit(document); | |
| 149 documents.add(document); | |
| 150 }); | |
| 151 }); | |
| 152 } | 128 } |
| 153 } | 129 } |
| 154 | 130 |
| 131 /** |
| 132 * Recursively inlines the contents of HTML imports. Produces as output a single |
| 133 * HTML file that inlines the polymer-element definitions, and a text file that |
| 134 * contains, in order, the URIs to each library that sourced in a script tag. |
| 135 * |
| 136 * This transformer assumes that all script tags point to external files. To |
| 137 * support script tags with inlined code, use this transformer after running |
| 138 * [InlineCodeExtractor] on an earlier phase. |
| 139 */ |
| 140 class ImportInliner extends Transformer { |
| 141 final TransformOptions options; |
| 142 |
| 143 ImportInliner(this.options); |
| 144 |
| 145 /** Only run on entry point .html files. */ |
| 146 Future<bool> isPrimary(Asset input) => |
| 147 new Future.value(options.isHtmlEntryPoint(input.id)); |
| 148 |
| 149 Future apply(Transform transform) => |
| 150 new _HtmlInliner(options, transform).apply(); |
| 151 } |
| 152 |
| 153 |
| 155 /** Internally adjusts urls in the html that we are about to inline. */ | 154 /** Internally adjusts urls in the html that we are about to inline. */ |
| 156 class _UrlNormalizer extends TreeVisitor { | 155 class _UrlNormalizer extends TreeVisitor { |
| 157 final Transform transform; | 156 final Transform transform; |
| 158 | 157 |
| 159 /** Asset where the original content (and original url) was found. */ | 158 /** Asset where the original content (and original url) was found. */ |
| 160 final AssetId sourceId; | 159 final AssetId sourceId; |
| 161 | 160 |
| 162 _UrlNormalizer(this.transform, this.sourceId); | 161 _UrlNormalizer(this.transform, this.sourceId); |
| 163 | 162 |
| 164 visitElement(Element node) { | 163 visitElement(Element node) { |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 219 'cite', // in blockquote, del, ins, q | 218 'cite', // in blockquote, del, ins, q |
| 220 'data', // in object | 219 'data', // in object |
| 221 'formaction', // in button, input | 220 'formaction', // in button, input |
| 222 'href', // in a, area, link, base, command | 221 'href', // in a, area, link, base, command |
| 223 'icon', // in command | 222 'icon', // in command |
| 224 'manifest', // in html | 223 'manifest', // in html |
| 225 'poster', // in video | 224 'poster', // in video |
| 226 'src', // in audio, embed, iframe, img, input, script, source, track, | 225 'src', // in audio, embed, iframe, img, input, script, source, track, |
| 227 // video | 226 // video |
| 228 ]; | 227 ]; |
| OLD | NEW |