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 |