Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(297)

Side by Side Diff: lib/devc.dart

Issue 993213003: Support browser caching using hashes in serverMode (fixes #93, fixes #92) (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « bin/devc.dart ('k') | lib/src/codegen/code_generator.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 /// Command line tool to run the checker on a Dart program. 5 /// Command line tool to run the checker on a Dart program.
6 library dev_compiler.devc; 6 library dev_compiler.devc;
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:convert'; 9 import 'dart:convert';
10 import 'dart:io'; 10 import 'dart:io';
(...skipping 10 matching lines...) Expand all
21 import 'src/checker/resolver.dart'; 21 import 'src/checker/resolver.dart';
22 import 'src/checker/rules.dart'; 22 import 'src/checker/rules.dart';
23 import 'src/codegen/code_generator.dart' show CodeGenerator; 23 import 'src/codegen/code_generator.dart' show CodeGenerator;
24 import 'src/codegen/dart_codegen.dart'; 24 import 'src/codegen/dart_codegen.dart';
25 import 'src/codegen/html_codegen.dart'; 25 import 'src/codegen/html_codegen.dart';
26 import 'src/codegen/js_codegen.dart'; 26 import 'src/codegen/js_codegen.dart';
27 import 'src/dependency_graph.dart'; 27 import 'src/dependency_graph.dart';
28 import 'src/info.dart' show LibraryInfo, CheckerResults; 28 import 'src/info.dart' show LibraryInfo, CheckerResults;
29 import 'src/options.dart'; 29 import 'src/options.dart';
30 import 'src/report.dart'; 30 import 'src/report.dart';
31 import 'src/utils.dart';
31 32
32 /// Sets up the type checker logger to print a span that highlights error 33 /// Sets up the type checker logger to print a span that highlights error
33 /// messages. 34 /// messages.
34 StreamSubscription setupLogger(Level level, printFn) { 35 StreamSubscription setupLogger(Level level, printFn) {
35 Logger.root.level = level; 36 Logger.root.level = level;
36 return Logger.root.onRecord.listen((LogRecord rec) { 37 return Logger.root.onRecord.listen((LogRecord rec) {
37 printFn('${rec.level.name.toLowerCase()}: ${rec.message}'); 38 printFn('${rec.level.name.toLowerCase()}: ${rec.message}');
38 }); 39 });
39 } 40 }
40 41
41 /// Encapsulates the logic to do a one-off compilation or a partial compilation 42 /// Encapsulates the logic to do a one-off compilation or a partial compilation
42 /// when the compiler is run as a development server. 43 /// when the compiler is run as a development server.
43 class Compiler { 44 class Compiler {
44 final CompilerOptions _options; 45 final CompilerOptions _options;
45 final TypeResolver _resolver; 46 final TypeResolver _resolver;
46 final CheckerReporter _reporter; 47 final CheckerReporter _reporter;
47 final TypeRules _rules; 48 final TypeRules _rules;
48 final CodeChecker _checker; 49 final CodeChecker _checker;
49 final SourceGraph _graph; 50 final SourceGraph _graph;
50 final SourceNode _entryNode; 51 final SourceNode _entryNode;
51 List<LibraryInfo> _libraries = <LibraryInfo>[]; 52 List<LibraryInfo> _libraries = <LibraryInfo>[];
52 final List<CodeGenerator> _generators; 53 final List<CodeGenerator> _generators;
54 final bool _hashing;
53 bool _failure = false; 55 bool _failure = false;
54 56
55 factory Compiler(CompilerOptions options, 57 factory Compiler(CompilerOptions options,
56 [TypeResolver resolver, CheckerReporter reporter]) { 58 [TypeResolver resolver, CheckerReporter reporter]) {
57 if (resolver == null) { 59 if (resolver == null) {
58 resolver = options.useMockSdk 60 resolver = options.useMockSdk
59 ? new TypeResolver.fromMock(mockSdkSources, options) 61 ? new TypeResolver.fromMock(mockSdkSources, options)
60 : new TypeResolver.fromDir(options.dartSdkPath, options); 62 : new TypeResolver.fromDir(options.dartSdkPath, options);
61 } 63 }
62 64
63 if (reporter == null) { 65 if (reporter == null) {
64 reporter = options.dumpInfo 66 reporter = options.dumpInfo
65 ? new SummaryReporter() 67 ? new SummaryReporter()
66 : new LogReporter(options.useColors); 68 : new LogReporter(options.useColors);
67 } 69 }
68 var graph = new SourceGraph(resolver.context, reporter); 70 var graph = new SourceGraph(resolver.context, reporter, options);
69 var rules = new RestrictedRules(resolver.context.typeProvider, reporter, 71 var rules = new RestrictedRules(resolver.context.typeProvider, reporter,
70 options: options); 72 options: options);
71 var checker = new CodeChecker(rules, reporter, options); 73 var checker = new CodeChecker(rules, reporter, options);
72 var inputFile = options.entryPointFile; 74 var inputFile = options.entryPointFile;
73 var uri = inputFile.startsWith('dart:') || inputFile.startsWith('package:') 75 var uri = inputFile.startsWith('dart:') || inputFile.startsWith('package:')
74 ? Uri.parse(inputFile) 76 ? Uri.parse(inputFile)
75 : new Uri.file(path.absolute(inputFile)); 77 : new Uri.file(path.absolute(inputFile));
76 var entryNode = graph.nodeFromUri(uri); 78 var entryNode = graph.nodeFromUri(uri);
77 79
78 var outputDir = options.outputDir; 80 var outputDir = options.outputDir;
79 var generators = <CodeGenerator>[]; 81 var generators = <CodeGenerator>[];
80 if (options.dumpSrcDir != null) { 82 if (options.dumpSrcDir != null) {
81 generators.add(new EmptyDartGenerator( 83 generators.add(new EmptyDartGenerator(
82 options.dumpSrcDir, entryNode.uri, rules, options)); 84 options.dumpSrcDir, entryNode.uri, rules, options));
83 } 85 }
84 if (outputDir != null) { 86 if (outputDir != null) {
85 generators.add(options.outputDart 87 generators.add(options.outputDart
86 ? new DartGenerator(outputDir, entryNode.uri, rules, options) 88 ? new DartGenerator(outputDir, entryNode.uri, rules, options)
87 : new JSGenerator(outputDir, entryNode.uri, rules, options)); 89 : new JSGenerator(outputDir, entryNode.uri, rules, options));
88 } 90 }
89 return new Compiler._(options, resolver, reporter, rules, checker, graph, 91 return new Compiler._(options, resolver, reporter, rules, checker, graph,
90 entryNode, generators); 92 entryNode, generators,
93 // TODO(sigmund): refactor to support hashing of the dart output?
94 options.serverMode && generators.length == 1 && !options.outputDart);
91 } 95 }
92 96
93 Compiler._(this._options, this._resolver, this._reporter, this._rules, 97 Compiler._(this._options, this._resolver, this._reporter, this._rules,
94 this._checker, this._graph, this._entryNode, this._generators); 98 this._checker, this._graph, this._entryNode, this._generators,
99 this._hashing);
95 100
96 bool _buildSource(SourceNode node) { 101 bool _buildSource(SourceNode node) {
97 if (node is HtmlSourceNode) { 102 if (node is HtmlSourceNode) {
98 _buildHtmlFile(node); 103 _buildHtmlFile(node);
99 } else if (node is DartSourceNode) { 104 } else if (node is DartSourceNode) {
100 _buildDartLibrary(node); 105 _buildDartLibrary(node);
101 } else if (node is ResourceSourceNode) { 106 } else if (node is ResourceSourceNode) {
102 _buildResourceFile(node); 107 _buildResourceFile(node);
103 } else { 108 } else {
104 assert(false); // should not get a build request on PartSourceNode 109 assert(false); // should not get a build request on PartSourceNode
(...skipping 20 matching lines...) Expand all
125 130
126 if (_options.outputDart) return; 131 if (_options.outputDart) return;
127 } 132 }
128 133
129 void _buildResourceFile(ResourceSourceNode node) { 134 void _buildResourceFile(ResourceSourceNode node) {
130 // ResourceSourceNodes files that just need to be copied over to the output 135 // ResourceSourceNodes files that just need to be copied over to the output
131 // location. These can be external dependencies or pieces of the 136 // location. These can be external dependencies or pieces of the
132 // dev_compiler runtime. 137 // dev_compiler runtime.
133 if (_options.outputDir == null || _options.outputDart) return; 138 if (_options.outputDir == null || _options.outputDart) return;
134 assert(node.uri.scheme == 'package'); 139 assert(node.uri.scheme == 'package');
135 var filepath = path.join(_options.outputDir, node.uri.path); 140 var filepath = path.join(_options.outputDir, resourceOutputPath(node.uri));
136 var dir = path.dirname(filepath); 141 var dir = path.dirname(filepath);
137 new Directory(dir).createSync(recursive: true); 142 new Directory(dir).createSync(recursive: true);
138 new File(filepath).writeAsStringSync(node.source.contents.data); 143 var text = node.source.contents.data;
144 new File(filepath).writeAsStringSync(text);
145 if (_hashing) node.cachingHash = computeHash(text);
139 } 146 }
140 147
141 bool _isEntry(DartSourceNode node) { 148 bool _isEntry(DartSourceNode node) {
142 if (_entryNode is DartSourceNode) return _entryNode == node; 149 if (_entryNode is DartSourceNode) return _entryNode == node;
143 return (_entryNode as HtmlSourceNode).scripts.contains(node); 150 return (_entryNode as HtmlSourceNode).scripts.contains(node);
144 } 151 }
145 152
146 void _buildDartLibrary(DartSourceNode node) { 153 void _buildDartLibrary(DartSourceNode node) {
147 var source = node.source; 154 var source = node.source;
148 // TODO(sigmund): find out from analyzer team if there is a better way 155 // TODO(sigmund): find out from analyzer team if there is a better way
(...skipping 21 matching lines...) Expand all
170 // TODO(sigmund): integrate analyzer errors with static-info (issue #6). 177 // TODO(sigmund): integrate analyzer errors with static-info (issue #6).
171 failureInLib = _resolver.logErrors(unitSource, _reporter) || failureInLib; 178 failureInLib = _resolver.logErrors(unitSource, _reporter) || failureInLib;
172 unit.visitChildren(_checker); 179 unit.visitChildren(_checker);
173 if (_checker.failure) failureInLib = true; 180 if (_checker.failure) failureInLib = true;
174 _reporter.leaveSource(); 181 _reporter.leaveSource();
175 } 182 }
176 if (failureInLib) { 183 if (failureInLib) {
177 _failure = true; 184 _failure = true;
178 if (!_options.forceCompile) return; 185 if (!_options.forceCompile) return;
179 } 186 }
187
180 for (var cg in _generators) { 188 for (var cg in _generators) {
181 cg.generateLibrary(units, current, _reporter); 189 var hash = cg.generateLibrary(units, current, _reporter);
190 if (_hashing) node.cachingHash = hash;
182 } 191 }
183 _reporter.leaveLibrary(); 192 _reporter.leaveLibrary();
184 } 193 }
185 194
186 CheckerResults run() { 195 CheckerResults run() {
187 var clock = new Stopwatch()..start(); 196 var clock = new Stopwatch()..start();
188 197
189 // TODO(sigmund): we are missing a couple failures here. The 198 // TODO(sigmund): we are missing a couple failures here. The
190 // dependendency_graph now detects broken imports or unsupported features 199 // dependendency_graph now detects broken imports or unsupported features
191 // like more than one script tag (see .severe messages in 200 // like more than one script tag (see .severe messages in
192 // dependency_graph.dart). Such failures should be reported back 201 // dependency_graph.dart). Such failures should be reported back
193 // here so we can mark failure=true in the CheckerResutls. 202 // here so we can mark failure=true in the CheckerResutls.
194 rebuild(_entryNode, _graph, _buildSource); 203 rebuild(_entryNode, _graph, _buildSource);
195 _dumpInfoIfRequested(); 204 _dumpInfoIfRequested();
196 clock.stop(); 205 clock.stop();
197 if (_options.serverMode) { 206 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
198 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2); 207 _log.fine('Compiled ${_libraries.length} libraries in ${time} s\n');
199 print('Compiled ${_libraries.length} libraries in ${time} s\n');
200 }
201 return new CheckerResults( 208 return new CheckerResults(
202 _libraries, _rules, _failure || _options.forceCompile); 209 _libraries, _rules, _failure || _options.forceCompile);
203 } 210 }
204 211
205 void _runAgain() { 212 void _runAgain() {
206 var clock = new Stopwatch()..start(); 213 var clock = new Stopwatch()..start();
207 _libraries = <LibraryInfo>[]; 214 _libraries = <LibraryInfo>[];
208 int changed = 0; 215 int changed = 0;
209 216
210 // TODO(sigmund): propagate failures here (see TODO in run). 217 // TODO(sigmund): propagate failures here (see TODO in run).
211 rebuild(_entryNode, _graph, (n) { 218 rebuild(_entryNode, _graph, (n) {
212 changed++; 219 changed++;
213 return _buildSource(n); 220 return _buildSource(n);
214 }); 221 });
215 clock.stop(); 222 clock.stop();
216 if (changed > 0) _dumpInfoIfRequested(); 223 if (changed > 0) _dumpInfoIfRequested();
217 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2); 224 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
218 print("Compiled ${changed} libraries in ${time} s\n"); 225 _log.fine("Compiled ${changed} libraries in ${time} s\n");
219 } 226 }
220 227
221 _dumpInfoIfRequested() { 228 _dumpInfoIfRequested() {
222 if (!_options.dumpInfo || _reporter is! SummaryReporter) return; 229 if (!_options.dumpInfo || _reporter is! SummaryReporter) return;
223 var result = (_reporter as SummaryReporter).result; 230 var result = (_reporter as SummaryReporter).result;
224 if (!_options.serverMode) print(summaryToString(result)); 231 if (!_options.serverMode) print(summaryToString(result));
225 var filepath = _options.serverMode 232 var filepath = _options.serverMode
226 ? path.join(_options.outputDir, 'messages.json') 233 ? path.join(_options.outputDir, 'messages.json')
227 : _options.dumpInfoFile; 234 : _options.dumpInfoFile;
228 if (filepath == null) return; 235 if (filepath == null) return;
(...skipping 15 matching lines...) Expand all
244 } 251 }
245 252
246 // TODO(sigmund): allow running without a dir, but keep output in memory? 253 // TODO(sigmund): allow running without a dir, but keep output in memory?
247 var outDir = options.outputDir; 254 var outDir = options.outputDir;
248 if (outDir == null) { 255 if (outDir == null) {
249 print('error: devc in server mode also requires specifying and ' 256 print('error: devc in server mode also requires specifying and '
250 'output location for generated code.'); 257 'output location for generated code.');
251 exit(1); 258 exit(1);
252 } 259 }
253 var port = options.port; 260 var port = options.port;
254 print('[dev_compiler]: Serving $entryPath at http://0.0.0.0:$port/'); 261 _log.fine('Serving $entryPath at http://0.0.0.0:$port/');
255 var compiler = new Compiler(options); 262 var compiler = new Compiler(options);
256 return new CompilerServer._(compiler, outDir, port, entryPath); 263 return new CompilerServer._(compiler, outDir, port, entryPath);
257 } 264 }
258 265
259 CompilerServer._(this.compiler, this.outDir, this.port, this._entryPath); 266 CompilerServer._(this.compiler, this.outDir, this.port, this._entryPath);
260 267
261 Future start() async { 268 Future start() async {
262 var handler = const shelf.Pipeline() 269 var handler = const shelf.Pipeline()
263 .addMiddleware(shelf.createMiddleware(requestHandler: rebuildIfNeeded)) 270 .addMiddleware(rebuildAndCache)
264 .addHandler(shelf_static.createStaticHandler(outDir, 271 .addHandler(shelf_static.createStaticHandler(outDir,
265 defaultDocument: _entryPath)); 272 defaultDocument: _entryPath));
266 await shelf.serve(handler, '0.0.0.0', port); 273 await shelf.serve(handler, '0.0.0.0', port);
267 compiler.run(); 274 compiler.run();
268 } 275 }
269 276
270 rebuildIfNeeded(shelf.Request request) { 277 shelf.Handler rebuildAndCache(shelf.Handler handler) => (request) {
271 var filepath = request.url.path; 278 _log.fine('requested $GREEN_COLOR${request.url}$NO_COLOR');
272 if (filepath == '/$_entryPath' || filepath == '/') compiler._runAgain(); 279 // Trigger recompile only when requesting the HTML page.
273 } 280 var segments = request.url.pathSegments;
281 bool isEntryPage = segments.length == 0 || segments[0] == _entryPath;
282 if (isEntryPage) compiler._runAgain();
283
284 // To help browsers cache resources that don't change, we serve these
285 // resources under a path containing their hash:
286 // /cached/{hash}/{path-to-file.js}
287 bool isCached = segments.length > 1 && segments[0] == 'cached';
288 if (isCached) {
289 // Changing the request lets us record that the hash prefix is handled
290 // here, and that the underlying handler should use the rest of the url to
291 // determine where to find the resource in the file system.
292 request = request.change(path: path.join('cached', segments[1]));
293 }
294 var response = handler(request);
295 var policy = isCached ? 'max-age=${24 * 60 * 60}' : 'no-cache';
296 var headers = {'cache-control': policy};
297 if (isCached) {
298 // Note: the cache-control header should be enough, but this doesn't hurt
299 // and can help renew the policy after it expires.
300 headers['ETag'] = segments[1];
301 }
302 return response.change(headers: headers);
303 };
274 } 304 }
275 305
276 final _log = new Logger('dev_compiler'); 306 final _log = new Logger('dev_compiler');
277 final _earlyErrorResult = new CheckerResults(const [], null, true); 307 final _earlyErrorResult = new CheckerResults(const [], null, true);
OLDNEW
« no previous file with comments | « bin/devc.dart ('k') | lib/src/codegen/code_generator.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698