| 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 15 matching lines...) Expand all Loading... |
| 26 // TODO(sigmund): move to web_components package (dartbug.com/18037). | 26 // TODO(sigmund): move to web_components package (dartbug.com/18037). |
| 27 class _HtmlInliner extends PolymerTransformer { | 27 class _HtmlInliner extends PolymerTransformer { |
| 28 final TransformOptions options; | 28 final TransformOptions options; |
| 29 final Transform transform; | 29 final Transform transform; |
| 30 final BuildLogger logger; | 30 final BuildLogger logger; |
| 31 final AssetId docId; | 31 final AssetId docId; |
| 32 final seen = new Set<AssetId>(); | 32 final seen = new Set<AssetId>(); |
| 33 final scriptIds = <AssetId>[]; | 33 final scriptIds = <AssetId>[]; |
| 34 final extractedFiles = new Set<AssetId>(); | 34 final extractedFiles = new Set<AssetId>(); |
| 35 bool experimentalBootstrap = false; | 35 bool experimentalBootstrap = false; |
| 36 final Element importsWrapper = new Element.html('<div hidden></div>'); |
| 36 | 37 |
| 37 /// The number of extracted inline Dart scripts. Used as a counter to give | 38 /// The number of extracted inline Dart scripts. Used as a counter to give |
| 38 /// unique-ish filenames. | 39 /// unique-ish filenames. |
| 39 int inlineScriptCounter = 0; | 40 int inlineScriptCounter = 0; |
| 40 | 41 |
| 41 _HtmlInliner(TransformOptions options, Transform transform) | 42 _HtmlInliner(TransformOptions options, Transform transform) |
| 42 : options = options, | 43 : options = options, |
| 43 transform = transform, | 44 transform = transform, |
| 44 logger = new BuildLogger(transform, | 45 logger = new BuildLogger(transform, |
| 45 convertErrorsToWarnings: !options.releaseMode, | 46 convertErrorsToWarnings: !options.releaseMode, |
| 46 detailsUri: 'http://goo.gl/5HPeuP'), | 47 detailsUri: 'http://goo.gl/5HPeuP'), |
| 47 docId = transform.primaryInput.id; | 48 docId = transform.primaryInput.id; |
| 48 | 49 |
| 49 Future apply() { | 50 Future apply() { |
| 50 seen.add(docId); | 51 seen.add(docId); |
| 51 | 52 |
| 52 Document document; | 53 Document document; |
| 53 bool changed = false; | 54 bool changed = false; |
| 54 | 55 |
| 55 return readPrimaryAsHtml(transform, logger).then((doc) { | 56 return readPrimaryAsHtml(transform, logger).then((doc) { |
| 56 document = doc; | 57 document = doc; |
| 58 |
| 59 // Insert our importsWrapper. This may be removed later if not needed, but |
| 60 // it makes the logic simpler to have it in the document. |
| 61 document.body.insertBefore(importsWrapper, document.body.firstChild); |
| 62 |
| 57 changed = new _UrlNormalizer(transform, docId, logger).visit(document) | 63 changed = new _UrlNormalizer(transform, docId, logger).visit(document) |
| 58 || changed; | 64 || changed; |
| 59 | 65 |
| 60 experimentalBootstrap = document.querySelectorAll('link').any((link) => | 66 experimentalBootstrap = document.querySelectorAll('link').any((link) => |
| 61 link.attributes['rel'] == 'import' && | 67 link.attributes['rel'] == 'import' && |
| 62 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); | 68 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); |
| 63 changed = _extractScripts(document) || changed; | 69 changed = _extractScripts(document) || changed; |
| 70 |
| 71 // We only need to move the head into the body for the entry point. |
| 72 _moveHeadToBody(document); |
| 73 |
| 64 return _visitImports(document); | 74 return _visitImports(document); |
| 65 }).then((importsFound) { | 75 }).then((importsFound) { |
| 66 changed = changed || importsFound; | 76 changed = changed || importsFound; |
| 77 |
| 67 return _removeScripts(document); | 78 return _removeScripts(document); |
| 68 }).then((scriptsRemoved) { | 79 }).then((scriptsRemoved) { |
| 69 changed = changed || scriptsRemoved; | 80 changed = changed || scriptsRemoved; |
| 70 | 81 |
| 82 // Remove the importsWrapper if it contains nothing. Wait until now to do |
| 83 // this since it might have a script that got removed, and thus no longer |
| 84 // have any children. |
| 85 if (importsWrapper.children.isEmpty) importsWrapper.remove(); |
| 86 |
| 71 var output = transform.primaryInput; | 87 var output = transform.primaryInput; |
| 72 if (changed) output = new Asset.fromString(docId, document.outerHtml); | 88 if (changed) output = new Asset.fromString(docId, document.outerHtml); |
| 73 transform.addOutput(output); | 89 transform.addOutput(output); |
| 74 | 90 |
| 75 // We produce a secondary asset with extra information for later phases. | 91 // We produce a secondary asset with extra information for later phases. |
| 76 transform.addOutput(new Asset.fromString( | 92 transform.addOutput(new Asset.fromString( |
| 77 docId.addExtension('._data'), | 93 docId.addExtension('._data'), |
| 78 JSON.encode({ | 94 JSON.encode({ |
| 79 'experimental_bootstrap': experimentalBootstrap, | 95 'experimental_bootstrap': experimentalBootstrap, |
| 80 'script_ids': scriptIds, | 96 'script_ids': scriptIds, |
| 81 }, toEncodable: (id) => id.serialize()))); | 97 }, toEncodable: (id) => id.serialize()))); |
| 82 | 98 |
| 83 // Write out the logs collected by our [BuildLogger]. | 99 // Write out the logs collected by our [BuildLogger]. |
| 84 if (options.injectBuildLogsInOutput) { | 100 if (options.injectBuildLogsInOutput) { |
| 85 return logger.writeOutput(); | 101 return logger.writeOutput(); |
| 86 } | 102 } |
| 87 }); | 103 }); |
| 88 } | 104 } |
| 89 | 105 |
| 90 /// Visits imports in [document] and add the imported documents to documents. | 106 /// Visits imports in [document] and add the imported documents to documents. |
| 91 /// Documents are added in the order they appear, transitive imports are added | 107 /// Documents are added in the order they appear, transitive imports are added |
| 92 /// first. | 108 /// first. |
| 93 /// | 109 /// |
| 94 /// Returns `true` if and only if the document was changed and should be | 110 /// Returns `true` if and only if the document was changed and should be |
| 95 /// written out. | 111 /// written out. |
| 96 Future<bool> _visitImports(Document document) { | 112 Future<bool> _visitImports(Document document) { |
| 97 bool changed = false; | 113 bool changed = false; |
| 98 | 114 |
| 99 _moveHeadToBody(document); | |
| 100 | |
| 101 // Note: we need to preserve the import order in the generated output. | 115 // Note: we need to preserve the import order in the generated output. |
| 102 return Future.forEach(document.querySelectorAll('link'), (Element tag) { | 116 return Future.forEach(document.querySelectorAll('link'), (Element tag) { |
| 103 var rel = tag.attributes['rel']; | 117 var rel = tag.attributes['rel']; |
| 104 if (rel != 'import' && rel != 'stylesheet') return null; | 118 if (rel != 'import' && rel != 'stylesheet') return null; |
| 105 | 119 |
| 106 // Note: URL has already been normalized so use docId. | 120 // Note: URL has already been normalized so use docId. |
| 107 var href = tag.attributes['href']; | 121 var href = tag.attributes['href']; |
| 108 var id = uriToAssetId(docId, href, logger, tag.sourceSpan, | 122 var id = uriToAssetId(docId, href, logger, tag.sourceSpan, |
| 109 errorOnAbsolute: rel != 'stylesheet'); | 123 errorOnAbsolute: rel != 'stylesheet'); |
| 110 | 124 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 131 /// inlining. | 145 /// inlining. |
| 132 /// | 146 /// |
| 133 /// Note: we do this for stylesheets as well to preserve ordering with | 147 /// Note: we do this for stylesheets as well to preserve ordering with |
| 134 /// respect to eachother, because stylesheets can be pulled in transitively | 148 /// respect to eachother, because stylesheets can be pulled in transitively |
| 135 /// from imports. | 149 /// from imports. |
| 136 // TODO(jmesserly): vulcanizer doesn't need this because they inline JS | 150 // TODO(jmesserly): vulcanizer doesn't need this because they inline JS |
| 137 // scripts, causing them to be naturally moved as part of the inlining. | 151 // scripts, causing them to be naturally moved as part of the inlining. |
| 138 // Should we do the same? Alternatively could we inline head into head and | 152 // Should we do the same? Alternatively could we inline head into head and |
| 139 // body into body and avoid this whole thing? | 153 // body into body and avoid this whole thing? |
| 140 void _moveHeadToBody(Document doc) { | 154 void _moveHeadToBody(Document doc) { |
| 141 var insertionPoint = doc.body.firstChild; | |
| 142 for (var node in doc.head.nodes.toList(growable: false)) { | 155 for (var node in doc.head.nodes.toList(growable: false)) { |
| 143 if (node is! Element) continue; | 156 if (node is! Element) continue; |
| 144 var tag = node.localName; | 157 var tag = node.localName; |
| 145 var type = node.attributes['type']; | 158 var type = node.attributes['type']; |
| 146 var rel = node.attributes['rel']; | 159 var rel = node.attributes['rel']; |
| 147 if (tag == 'style' || tag == 'script' && | 160 if (tag == 'style' || tag == 'script' && |
| 148 (type == null || type == TYPE_JS || type == TYPE_DART) || | 161 (type == null || type == TYPE_JS || type == TYPE_DART) || |
| 149 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { | 162 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { |
| 150 // Move the node into the body, where its contents will be placed. | 163 // Move the node into the importsWrapper, where its contents will be |
| 151 doc.body.insertBefore(node, insertionPoint); | 164 // placed. This wrapper is a hidden div to prevent inlined html from |
| 165 // causing a FOUC. |
| 166 importsWrapper.append(node); |
| 152 } | 167 } |
| 153 } | 168 } |
| 154 } | 169 } |
| 155 | 170 |
| 156 /// Loads an asset identified by [id], visits its imports and collects its | 171 /// Loads an asset identified by [id], visits its imports and collects its |
| 157 /// html imports. Then inlines it into the main document. | 172 /// html imports. Then inlines it into the main document. |
| 158 Future _inlineImport(AssetId id, Element link) { | 173 Future _inlineImport(AssetId id, Element link) { |
| 159 return readAsHtml(id, transform, logger).catchError((error) { | 174 return readAsHtml(id, transform, logger).catchError((error) { |
| 160 logger.error(INLINE_IMPORT_FAIL.create({'error': error}), | 175 logger.error(INLINE_IMPORT_FAIL.create({'error': error}), |
| 161 span: link.sourceSpan); | 176 span: link.sourceSpan); |
| (...skipping 362 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 524 /// style tag except these ones. | 539 /// style tag except these ones. |
| 525 const IGNORED_LINKED_STYLE_ATTRS = | 540 const IGNORED_LINKED_STYLE_ATTRS = |
| 526 const ['charset', 'href', 'href-lang', 'rel', 'rev']; | 541 const ['charset', 'href', 'href-lang', 'rel', 'rev']; |
| 527 | 542 |
| 528 /// Global RegExp objects. | 543 /// Global RegExp objects. |
| 529 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); | 544 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); |
| 530 final _NUM_REGEX = new RegExp('[0-9]'); | 545 final _NUM_REGEX = new RegExp('[0-9]'); |
| 531 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); | 546 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); |
| 532 | 547 |
| 533 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); | 548 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); |
| OLD | NEW |