| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 import 'package:html/dom.dart'; | 5 import 'package:html/dom.dart'; |
| 6 import 'package:html/parser.dart' show parseFragment; | 6 import 'package:html/parser.dart' show parseFragment; |
| 7 import 'package:logging/logging.dart' show Logger; | 7 import 'package:logging/logging.dart' show Logger; |
| 8 import 'package:path/path.dart' as path; | |
| 9 | |
| 10 import '../compiler.dart' show AbstractCompiler; | |
| 11 import '../server/dependency_graph.dart'; | |
| 12 import '../utils.dart' show colorOf, resourceOutputPath; | |
| 13 | |
| 14 /// Emits an entry point HTML file corresponding to [inputFile] that can load | |
| 15 /// the code generated by the dev compiler. | |
| 16 /// | |
| 17 /// This internally transforms the given HTML [document]. When compiling to | |
| 18 /// JavaScript, we remove any Dart script tags, add new script tags to load our | |
| 19 /// runtime and the compiled code, and to execute the main method of the | |
| 20 /// application. When compiling to Dart, we ensure that the document contains a | |
| 21 /// single Dart script tag, but otherwise emit the original document | |
| 22 /// unmodified. | |
| 23 String generateEntryHtml(HtmlSourceNode root, AbstractCompiler compiler) { | |
| 24 var options = compiler.options; | |
| 25 var document = root.document.clone(true); | |
| 26 var scripts = document.querySelectorAll('script[type="application/dart"]'); | |
| 27 if (scripts.isEmpty) { | |
| 28 _log.warning('No <script type="application/dart"> found in ${root.uri}'); | |
| 29 // TODO(jacobr): we would rather return document.outerHtml to avoid future | |
| 30 // bugs that would only show up with html files include references to Dart | |
| 31 // scripts. Passing through the input content is needed to avoid breaking | |
| 32 // Angular ecause the spec-compliant HTML parser used modifies the HTML | |
| 33 // in a way that breaks Angular templates. An alternate fix would be to | |
| 34 // write an HTML emitter that preserves the structure of the input HTML as | |
| 35 // much as possible. | |
| 36 return root.contents; | |
| 37 } | |
| 38 scripts.skip(1).forEach((s) { | |
| 39 // TODO(sigmund): allow more than one Dart script tags? | |
| 40 _log.warning(s.sourceSpan.message( | |
| 41 'unexpected script. Only one Dart script tag allowed ' | |
| 42 '(see https://github.com/dart-lang/dart-dev-compiler/issues/53).', | |
| 43 color: options.useColors ? colorOf('warning') : false)); | |
| 44 s.remove(); | |
| 45 }); | |
| 46 | |
| 47 var libraries = []; | |
| 48 var resources = new Set(); | |
| 49 visitInPostOrder(root, (n) { | |
| 50 if (n is DartSourceNode) libraries.add(n); | |
| 51 if (n is ResourceSourceNode) resources.add(n); | |
| 52 }, includeParts: false); | |
| 53 | |
| 54 root.htmlResourceNodes.forEach((element, resource) { | |
| 55 // Make sure we don't try and add this node again. | |
| 56 resources.remove(resource); | |
| 57 | |
| 58 var resourcePath = | |
| 59 resourceOutputPath(resource.uri, root.uri, options.runtimeDir); | |
| 60 if (resource.cachingHash != null) { | |
| 61 resourcePath = _addHash(resourcePath, resource.cachingHash); | |
| 62 } | |
| 63 var attrs = element.attributes; | |
| 64 if (attrs.containsKey('href')) { | |
| 65 attrs['href'] = resourcePath; | |
| 66 } else if (attrs.containsKey('src')) { | |
| 67 attrs['src'] = resourcePath; | |
| 68 } | |
| 69 }); | |
| 70 | |
| 71 var rootDir = path.dirname(root.uri.path); | |
| 72 String rootRelative(String fullPath) { | |
| 73 return path.relative(path.join(compiler.inputBaseDir, fullPath), | |
| 74 from: rootDir); | |
| 75 } | |
| 76 | |
| 77 var fragment = new DocumentFragment(); | |
| 78 for (var resource in resources) { | |
| 79 var resourcePath = rootRelative( | |
| 80 resourceOutputPath(resource.uri, root.uri, options.runtimeDir)); | |
| 81 var ext = path.extension(resourcePath); | |
| 82 if (resource.cachingHash != null) { | |
| 83 resourcePath = _addHash(resourcePath, resource.cachingHash); | |
| 84 } | |
| 85 if (ext == '.js') { | |
| 86 fragment.nodes.add(libraryInclude(resourcePath)); | |
| 87 } else if (ext == '.css') { | |
| 88 var stylesheetLink = '<link rel="stylesheet" href="$resourcePath">\n'; | |
| 89 fragment.nodes.add(parseFragment(stylesheetLink)); | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 String mainLibraryName; | |
| 94 var src = scripts[0].attributes["src"]; | |
| 95 var scriptUri = root.source.resolveRelativeUri(Uri.parse(src)); | |
| 96 | |
| 97 for (var lib in libraries) { | |
| 98 var info = lib.info; | |
| 99 if (info == null) continue; | |
| 100 var uri = info.library.source.uri; | |
| 101 var jsPath = rootRelative(compiler.getModulePath(uri)); | |
| 102 if (uri == scriptUri) mainLibraryName = compiler.getModuleName(uri); | |
| 103 if (lib.cachingHash != null) { | |
| 104 jsPath = _addHash(jsPath, lib.cachingHash); | |
| 105 } | |
| 106 fragment.nodes.add(libraryInclude(jsPath)); | |
| 107 } | |
| 108 fragment.nodes.add(invokeMain(mainLibraryName)); | |
| 109 scripts[0].replaceWith(fragment); | |
| 110 return '${document.outerHtml}\n'; | |
| 111 } | |
| 112 | 8 |
| 113 // TODO(jmesserly): the string interpolation in these could lead to injection | 9 // TODO(jmesserly): the string interpolation in these could lead to injection |
| 114 // bugs. Not really a security issue since input is trusted, but the resulting | 10 // bugs. Not really a security issue since input is trusted, but the resulting |
| 115 // parse tree may not match expectations if interpolated strings contain quotes. | 11 // parse tree may not match expectations if interpolated strings contain quotes. |
| 116 | 12 |
| 117 /// A script tag that loads the .js code for a compiled library. | 13 /// A script tag that loads the .js code for a compiled library. |
| 118 Node libraryInclude(String jsUrl) => | 14 Node libraryInclude(String jsUrl) => |
| 119 parseFragment('<script src="$jsUrl"></script>\n'); | 15 parseFragment('<script src="$jsUrl"></script>\n'); |
| 120 | 16 |
| 121 /// A script tag that invokes the main function on the entry point library. | 17 /// A script tag that invokes the main function on the entry point library. |
| 122 Node invokeMain(String mainLibraryName) { | 18 Node invokeMain(String mainLibraryName) { |
| 123 var code = mainLibraryName == null | 19 var code = mainLibraryName == null |
| 124 ? 'console.error("dev_compiler error: main was not generated");' | 20 ? 'console.error("dev_compiler error: main was not generated");' |
| 125 // TODO(vsm): Can we simplify this? | 21 // TODO(vsm): Can we simplify this? |
| 126 // See: https://github.com/dart-lang/dev_compiler/issues/164 | 22 // See: https://github.com/dart-lang/dev_compiler/issues/164 |
| 127 : "dart_library.start('$mainLibraryName');"; | 23 : "dart_library.start('$mainLibraryName');"; |
| 128 return parseFragment('<script>$code</script>\n'); | 24 return parseFragment('<script>$code</script>\n'); |
| 129 } | 25 } |
| 130 | 26 |
| 131 /// Convert the outputPath to include the hash in it. This function is the | |
| 132 /// reverse of what the server does to determine whether a request needs to have | |
| 133 /// cache headers added to it. | |
| 134 _addHash(String outPath, String hash) { | |
| 135 // (the ____ prefix makes it look better in the web inspector) | |
| 136 return '$outPath?____cached=$hash'; | |
| 137 } | |
| 138 | |
| 139 final _log = new Logger('dev_compiler.src.codegen.html_codegen'); | 27 final _log = new Logger('dev_compiler.src.codegen.html_codegen'); |
| OLD | NEW |