| 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:analyzer/analyzer.dart'; |
| 11 import 'package:analyzer/src/generated/ast.dart'; | 12 import 'package:analyzer/src/generated/ast.dart'; |
| 12 import 'package:barback/barback.dart'; | 13 import 'package:barback/barback.dart'; |
| 13 import 'package:code_transformers/assets.dart'; | 14 import 'package:code_transformers/assets.dart'; |
| 14 import 'package:path/path.dart' as path; | 15 import 'package:path/path.dart' as path; |
| 15 import 'package:html5lib/dom.dart' show | 16 import 'package:html5lib/dom.dart' show |
| 16 Document, DocumentFragment, Element, Node; | 17 Document, DocumentFragment, Element, Node; |
| 17 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; | 18 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; |
| 18 import 'package:source_maps/refactor.dart' show TextEditTransaction; | 19 import 'package:source_maps/refactor.dart' show TextEditTransaction; |
| 19 import 'package:source_maps/span.dart'; | 20 import 'package:source_maps/span.dart'; |
| 20 | 21 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 44 seen.add(docId); | 45 seen.add(docId); |
| 45 | 46 |
| 46 Document document; | 47 Document document; |
| 47 bool changed; | 48 bool changed; |
| 48 | 49 |
| 49 return readPrimaryAsHtml(transform).then((doc) { | 50 return readPrimaryAsHtml(transform).then((doc) { |
| 50 document = doc; | 51 document = doc; |
| 51 experimentalBootstrap = document.querySelectorAll('link').any((link) => | 52 experimentalBootstrap = document.querySelectorAll('link').any((link) => |
| 52 link.attributes['rel'] == 'import' && | 53 link.attributes['rel'] == 'import' && |
| 53 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); | 54 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); |
| 54 changed = _extractScripts(document, docId); | 55 changed = _extractScripts(document); |
| 55 return _visitImports(document); | 56 return _visitImports(document); |
| 56 }).then((importsFound) { | 57 }).then((importsFound) { |
| 57 changed = changed || importsFound; | 58 changed = changed || importsFound; |
| 58 return _removeScripts(document); | 59 return _removeScripts(document); |
| 59 }).then((scriptsRemoved) { | 60 }).then((scriptsRemoved) { |
| 60 changed = changed || scriptsRemoved; | 61 changed = changed || scriptsRemoved; |
| 61 | 62 |
| 62 var output = transform.primaryInput; | 63 var output = transform.primaryInput; |
| 63 if (changed) output = new Asset.fromString(docId, document.outerHtml); | 64 if (changed) output = new Asset.fromString(docId, document.outerHtml); |
| 64 transform.addOutput(output); | 65 transform.addOutput(output); |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 137 } | 138 } |
| 138 } | 139 } |
| 139 } | 140 } |
| 140 | 141 |
| 141 /// Loads an asset identified by [id], visits its imports and collects its | 142 /// Loads an asset identified by [id], visits its imports and collects its |
| 142 /// html imports. Then inlines it into the main document. | 143 /// html imports. Then inlines it into the main document. |
| 143 Future _inlineImport(AssetId id, Element link) { | 144 Future _inlineImport(AssetId id, Element link) { |
| 144 return readAsHtml(id, transform).then((doc) { | 145 return readAsHtml(id, transform).then((doc) { |
| 145 new _UrlNormalizer(transform, id).visit(doc); | 146 new _UrlNormalizer(transform, id).visit(doc); |
| 146 return _visitImports(doc).then((_) { | 147 return _visitImports(doc).then((_) { |
| 147 _extractScripts(doc, id); | 148 // _UrlNormalizer already ensures there is a library name. |
| 149 _extractScripts(doc, injectLibraryName: false); |
| 148 | 150 |
| 149 // TODO(jmesserly): figure out how this is working in vulcanizer. | 151 // TODO(jmesserly): figure out how this is working in vulcanizer. |
| 150 // Do they produce a <body> tag with a <head> and <body> inside? | 152 // Do they produce a <body> tag with a <head> and <body> inside? |
| 151 var imported = new DocumentFragment(); | 153 var imported = new DocumentFragment(); |
| 152 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); | 154 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); |
| 153 link.replaceWith(imported); | 155 link.replaceWith(imported); |
| 154 }); | 156 }); |
| 155 }); | 157 }); |
| 156 } | 158 } |
| 157 | 159 |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 191 } | 193 } |
| 192 }); | 194 }); |
| 193 } | 195 } |
| 194 }).then((_) => changed); | 196 }).then((_) => changed); |
| 195 } | 197 } |
| 196 | 198 |
| 197 /// Split inline scripts into their own files. We need to do this for dart2js | 199 /// Split inline scripts into their own files. We need to do this for dart2js |
| 198 /// to be able to compile them. | 200 /// to be able to compile them. |
| 199 /// | 201 /// |
| 200 /// This also validates that there weren't any duplicate scripts. | 202 /// This also validates that there weren't any duplicate scripts. |
| 201 bool _extractScripts(Document doc, AssetId sourceId) { | 203 bool _extractScripts(Document doc, {bool injectLibraryName: true}) { |
| 202 bool changed = false; | 204 bool changed = false; |
| 203 for (var script in doc.querySelectorAll('script')) { | 205 for (var script in doc.querySelectorAll('script')) { |
| 204 if (script.attributes['type'] != TYPE_DART) continue; | 206 if (script.attributes['type'] != TYPE_DART) continue; |
| 205 | 207 |
| 206 var src = script.attributes['src']; | 208 var src = script.attributes['src']; |
| 207 if (src != null) continue; | 209 if (src != null) continue; |
| 208 | 210 |
| 209 final filename = path.url.basename(docId.path); | 211 final filename = path.url.basename(docId.path); |
| 210 final count = inlineScriptCounter++; | 212 final count = inlineScriptCounter++; |
| 211 var code = script.text; | 213 var code = script.text; |
| 212 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). | 214 // TODO(sigmund): ensure this path is unique (dartbug.com/12618). |
| 213 script.attributes['src'] = src = '$filename.$count.dart'; | 215 script.attributes['src'] = src = '$filename.$count.dart'; |
| 214 script.text = ''; | 216 script.text = ''; |
| 215 changed = true; | 217 changed = true; |
| 216 | 218 |
| 217 var newId = docId.addExtension('.$count.dart'); | 219 var newId = docId.addExtension('.$count.dart'); |
| 218 // TODO(jmesserly): consolidate this check with our other parsing of the | 220 if (injectLibraryName && !_hasLibraryDirective(code)) { |
| 219 // Dart code, so we only parse it once. | 221 var libName = _libraryNameFor(docId, count); |
| 220 if (!_hasLibraryDirective(code)) { | |
| 221 // Inject a library tag with an appropriate library name. | |
| 222 | |
| 223 // Transform AssetId into a package name. For example: | |
| 224 // myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html | |
| 225 // myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html | |
| 226 // This should roughly match the recommended library name conventions. | |
| 227 var libName = '${path.withoutExtension(sourceId.path)}_' | |
| 228 '${path.extension(sourceId.path).substring(1)}'; | |
| 229 if (libName.startsWith('lib/')) libName = libName.substring(4); | |
| 230 libName = libName.replaceAll('/', '.').replaceAll('-', '_'); | |
| 231 libName = '${sourceId.package}.${libName}_$count'; | |
| 232 | |
| 233 code = "library $libName;\n$code"; | 222 code = "library $libName;\n$code"; |
| 234 } | 223 } |
| 235 extractedFiles.add(newId); | 224 extractedFiles.add(newId); |
| 236 transform.addOutput(new Asset.fromString(newId, code)); | 225 transform.addOutput(new Asset.fromString(newId, code)); |
| 237 } | 226 } |
| 238 return changed; | 227 return changed; |
| 239 } | 228 } |
| 240 } | 229 } |
| 241 | 230 |
| 231 /// Transform AssetId into a library name. For example: |
| 232 /// |
| 233 /// myPkgName|lib/foo/bar.html -> myPkgName.foo.bar_html |
| 234 /// myPkgName|web/foo/bar.html -> myPkgName.web.foo.bar_html |
| 235 /// |
| 236 /// This should roughly match the recommended library name conventions. |
| 237 String _libraryNameFor(AssetId id, int suffix) { |
| 238 var name = '${path.withoutExtension(id.path)}_' |
| 239 '${path.extension(id.path).substring(1)}'; |
| 240 if (name.startsWith('lib/')) name = name.substring(4); |
| 241 name = name.replaceAll('/', '.').replaceAll('-', '_'); |
| 242 return '${id.package}.${name}_$suffix'; |
| 243 } |
| 244 |
| 242 /// Parse [code] and determine whether it has a library directive. | 245 /// Parse [code] and determine whether it has a library directive. |
| 243 bool _hasLibraryDirective(String code) => | 246 bool _hasLibraryDirective(String code) => |
| 244 parseCompilationUnit(code).directives.any((d) => d is LibraryDirective); | 247 parseDirectives(code, suppressErrors: true) |
| 248 .directives.any((d) => d is LibraryDirective); |
| 245 | 249 |
| 246 | 250 |
| 247 /// Recursively inlines the contents of HTML imports. Produces as output a | 251 /// Recursively inlines the contents of HTML imports. Produces as output a |
| 248 /// single HTML file that inlines the polymer-element definitions, and a text | 252 /// single HTML file that inlines the polymer-element definitions, and a text |
| 249 /// file that contains, in order, the URIs to each library that sourced in a | 253 /// file that contains, in order, the URIs to each library that sourced in a |
| 250 /// script tag. | 254 /// script tag. |
| 251 /// | 255 /// |
| 252 /// This transformer assumes that all script tags point to external files. To | 256 /// This transformer assumes that all script tags point to external files. To |
| 253 /// support script tags with inlined code, use this transformer after running | 257 /// support script tags with inlined code, use this transformer after running |
| 254 /// [InlineCodeExtractor] on an earlier phase. | 258 /// [InlineCodeExtractor] on an earlier phase. |
| (...skipping 17 matching lines...) Expand all Loading... |
| 272 const TYPE_DART = 'application/dart'; | 276 const TYPE_DART = 'application/dart'; |
| 273 const TYPE_JS = 'text/javascript'; | 277 const TYPE_JS = 'text/javascript'; |
| 274 | 278 |
| 275 /// Internally adjusts urls in the html that we are about to inline. | 279 /// Internally adjusts urls in the html that we are about to inline. |
| 276 class _UrlNormalizer extends TreeVisitor { | 280 class _UrlNormalizer extends TreeVisitor { |
| 277 final Transform transform; | 281 final Transform transform; |
| 278 | 282 |
| 279 /// Asset where the original content (and original url) was found. | 283 /// Asset where the original content (and original url) was found. |
| 280 final AssetId sourceId; | 284 final AssetId sourceId; |
| 281 | 285 |
| 286 /// Counter used to ensure that every library name we inject is unique. |
| 287 int _count = 0; |
| 288 |
| 282 _UrlNormalizer(this.transform, this.sourceId); | 289 _UrlNormalizer(this.transform, this.sourceId); |
| 283 | 290 |
| 284 visitElement(Element node) { | 291 visitElement(Element node) { |
| 285 node.attributes.forEach((name, value) { | 292 node.attributes.forEach((name, value) { |
| 286 if (_urlAttributes.contains(name)) { | 293 if (_urlAttributes.contains(name)) { |
| 287 if (value != '' && !value.trim().startsWith('{{')) { | 294 if (value != '' && !value.trim().startsWith('{{')) { |
| 288 node.attributes[name] = _newUrl(value, node.sourceSpan); | 295 node.attributes[name] = _newUrl(value, node.sourceSpan); |
| 289 } | 296 } |
| 290 } | 297 } |
| 291 }); | 298 }); |
| 292 if (node.localName == 'style') { | 299 if (node.localName == 'style') { |
| 293 node.text = visitCss(node.text); | 300 node.text = visitCss(node.text); |
| 294 } else if (node.localName == 'script' && | 301 } else if (node.localName == 'script' && |
| 295 node.attributes['type'] == TYPE_DART) { | 302 node.attributes['type'] == TYPE_DART && |
| 303 !node.attributes.containsKey('src')) { |
| 296 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony | 304 // TODO(jmesserly): we might need to visit JS too to handle ES Harmony |
| 297 // modules. | 305 // modules. |
| 298 node.text = visitInlineDart(node.text); | 306 node.text = visitInlineDart(node.text); |
| 299 } | 307 } |
| 300 super.visitElement(node); | 308 super.visitElement(node); |
| 301 } | 309 } |
| 302 | 310 |
| 303 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); | 311 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); |
| 304 static final _QUOTE = new RegExp('["\']', multiLine: true); | 312 static final _QUOTE = new RegExp('["\']', multiLine: true); |
| 305 | 313 |
| 306 /// Visit the CSS text and replace any relative URLs so we can inline it. | 314 /// Visit the CSS text and replace any relative URLs so we can inline it. |
| 307 // Ported from: | 315 // Ported from: |
| 308 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 | 316 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 |
| 309 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. | 317 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. |
| 310 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. | 318 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. |
| 311 String visitCss(String cssText) { | 319 String visitCss(String cssText) { |
| 312 var url = spanUrlFor(sourceId, transform); | 320 var url = spanUrlFor(sourceId, transform); |
| 313 var src = new SourceFile.text(url, cssText); | 321 var src = new SourceFile.text(url, cssText); |
| 314 return cssText.replaceAllMapped(_URL, (match) { | 322 return cssText.replaceAllMapped(_URL, (match) { |
| 315 // Extract the URL, without any surrounding quotes. | 323 // Extract the URL, without any surrounding quotes. |
| 316 var span = src.span(match.start, match.end); | 324 var span = src.span(match.start, match.end); |
| 317 var href = match[1].replaceAll(_QUOTE, ''); | 325 var href = match[1].replaceAll(_QUOTE, ''); |
| 318 href = _newUrl(href, span); | 326 href = _newUrl(href, span); |
| 319 return 'url($href)'; | 327 return 'url($href)'; |
| 320 }); | 328 }); |
| 321 } | 329 } |
| 322 | 330 |
| 323 String visitInlineDart(String code) { | 331 String visitInlineDart(String code) { |
| 324 var unit = parseCompilationUnit(code); | 332 var unit = parseDirectives(code, suppressErrors: true); |
| 325 var file = new SourceFile.text(spanUrlFor(sourceId, transform), code); | 333 var file = new SourceFile.text(spanUrlFor(sourceId, transform), code); |
| 326 var output = new TextEditTransaction(code, file); | 334 var output = new TextEditTransaction(code, file); |
| 327 | 335 var foundLibraryDirective = false; |
| 328 for (Directive directive in unit.directives) { | 336 for (Directive directive in unit.directives) { |
| 329 if (directive is UriBasedDirective) { | 337 if (directive is UriBasedDirective) { |
| 330 var uri = directive.uri.stringValue; | 338 var uri = directive.uri.stringValue; |
| 331 var span = _getSpan(file, directive.uri); | 339 var span = _getSpan(file, directive.uri); |
| 332 | 340 |
| 333 var id = uriToAssetId(sourceId, uri, transform.logger, span, | 341 var id = uriToAssetId(sourceId, uri, transform.logger, span, |
| 334 errorOnAbsolute: false); | 342 errorOnAbsolute: false); |
| 335 if (id == null) continue; | 343 if (id == null) continue; |
| 336 | 344 |
| 337 var primaryId = transform.primaryInput.id; | 345 var primaryId = transform.primaryInput.id; |
| 338 var newUri = assetUrlFor(id, primaryId, transform.logger); | 346 var newUri = assetUrlFor(id, primaryId, transform.logger); |
| 339 if (newUri != uri) { | 347 if (newUri != uri) { |
| 340 output.edit(span.start.offset, span.end.offset, "'$newUri'"); | 348 output.edit(span.start.offset, span.end.offset, "'$newUri'"); |
| 341 } | 349 } |
| 350 } else if (directive is LibraryDirective) { |
| 351 foundLibraryDirective = true; |
| 342 } | 352 } |
| 343 } | 353 } |
| 344 | 354 |
| 355 if (!foundLibraryDirective) { |
| 356 // Ensure all inline scripts also have a library name. |
| 357 var libName = _libraryNameFor(sourceId, _count++); |
| 358 output.edit(0, 0, "library $libName;\n"); |
| 359 } |
| 360 |
| 345 if (!output.hasEdits) return code; | 361 if (!output.hasEdits) return code; |
| 346 | 362 |
| 347 // TODO(sigmund): emit source maps when barback supports it (see | 363 // TODO(sigmund): emit source maps when barback supports it (see |
| 348 // dartbug.com/12340) | 364 // dartbug.com/12340) |
| 349 return (output.commit()..build(file.url)).text; | 365 return (output.commit()..build(file.url)).text; |
| 350 } | 366 } |
| 351 | 367 |
| 352 String _newUrl(String href, Span span) { | 368 String _newUrl(String href, Span span) { |
| 353 var uri = Uri.parse(href); | 369 var uri = Uri.parse(href); |
| 354 if (uri.isAbsolute) return href; | 370 if (uri.isAbsolute) return href; |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 395 'formaction', // in button, input | 411 'formaction', // in button, input |
| 396 'href', // in a, area, link, base, command | 412 'href', // in a, area, link, base, command |
| 397 'icon', // in command | 413 'icon', // in command |
| 398 'manifest', // in html | 414 'manifest', // in html |
| 399 'poster', // in video | 415 'poster', // in video |
| 400 'src', // in audio, embed, iframe, img, input, script, source, track, | 416 'src', // in audio, embed, iframe, img, input, script, source, track, |
| 401 // video | 417 // video |
| 402 ]; | 418 ]; |
| 403 | 419 |
| 404 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); | 420 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); |
| OLD | NEW |