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 |