| 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 /// Development server that compiles Dart to JS on the fly. | 5 /// Development server that compiles Dart to JS on the fly. |
| 6 library dev_compiler.src.server; | 6 library dev_compiler.src.server; |
| 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 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 239 } | 239 } |
| 240 | 240 |
| 241 DevServer._(ServerCompiler compiler, this.outDir, this.host, this.port, | 241 DevServer._(ServerCompiler compiler, this.outDir, this.host, this.port, |
| 242 String entryPath) | 242 String entryPath) |
| 243 : this.compiler = compiler, | 243 : this.compiler = compiler, |
| 244 // TODO(jmesserly): this logic is duplicated in a few places | 244 // TODO(jmesserly): this logic is duplicated in a few places |
| 245 this._entryPath = compiler.options.sourceOptions.useImplicitHtml | 245 this._entryPath = compiler.options.sourceOptions.useImplicitHtml |
| 246 ? SourceResolverOptions.implicitHtmlFile | 246 ? SourceResolverOptions.implicitHtmlFile |
| 247 : entryPath; | 247 : entryPath; |
| 248 | 248 |
| 249 Future<bool> start() async { | 249 Future start() async { |
| 250 // Create output directory if needed. shelf_static will fail otherwise. | 250 // Create output directory if needed. shelf_static will fail otherwise. |
| 251 var out = new Directory(outDir); | 251 var out = new Directory(outDir); |
| 252 if (!await out.exists()) await out.create(recursive: true); | 252 if (!await out.exists()) await out.create(recursive: true); |
| 253 | 253 |
| 254 var handler = const shelf.Pipeline() | 254 var handler = const shelf.Pipeline() |
| 255 .addMiddleware(rebuildAndCache) | 255 .addMiddleware(rebuildAndCache) |
| 256 .addHandler(shelf_static.createStaticHandler(outDir, | 256 .addHandler(shelf_static.createStaticHandler(outDir, |
| 257 defaultDocument: _entryPath)); | 257 defaultDocument: _entryPath)); |
| 258 await shelf.serve(handler, host, port); | 258 await shelf.serve(handler, host, port); |
| 259 print('Serving $_entryPath at http://$host:$port/'); | 259 print('Serving $_entryPath at http://$host:$port/'); |
| 260 CheckerResults results = compiler.run(); | 260 // Give the compiler a head start. This is not needed for correctness, |
| 261 return !results.failure; | 261 // but will likely speed up the first load. Regardless of whether compile |
| 262 // succeeds we should still start the server. |
| 263 compiler.run(); |
| 264 // Server has started so this future will complete. |
| 262 } | 265 } |
| 263 | 266 |
| 264 shelf.Handler rebuildAndCache(shelf.Handler handler) => (request) { | 267 shelf.Handler rebuildAndCache(shelf.Handler handler) => (request) { |
| 265 print('requested $GREEN_COLOR${request.url}$NO_COLOR'); | 268 print('requested $GREEN_COLOR${request.url}$NO_COLOR'); |
| 266 // Trigger recompile only when requesting the HTML page. | 269 // Trigger recompile only when requesting the HTML page. |
| 267 var segments = request.url.pathSegments; | 270 var segments = request.url.pathSegments; |
| 268 bool isEntryPage = segments.length == 0 || segments[0] == _entryPath; | 271 bool isEntryPage = segments.length == 0 || segments[0] == _entryPath; |
| 269 if (isEntryPage) compiler._runAgain(); | 272 if (isEntryPage) compiler._runAgain(); |
| 270 | 273 |
| 271 // To help browsers cache resources that don't change, we serve these | 274 // To help browsers cache resources that don't change, we serve these |
| 272 // resources by adding a query parameter containing their hash: | 275 // resources by adding a query parameter containing their hash: |
| 273 // /{path-to-file.js}?____cached={hash} | 276 // /{path-to-file.js}?____cached={hash} |
| 274 var hash = request.url.queryParameters['____cached']; | 277 var hash = request.url.queryParameters['____cached']; |
| 275 var response = handler(request); | 278 var response = handler(request); |
| 276 var policy = hash != null ? 'max-age=${24 * 60 * 60}' : 'no-cache'; | 279 var policy = hash != null ? 'max-age=${24 * 60 * 60}' : 'no-cache'; |
| 277 var headers = {'cache-control': policy}; | 280 var headers = {'cache-control': policy}; |
| 278 if (hash != null) { | 281 if (hash != null) { |
| 279 // Note: the cache-control header should be enough, but this doesn't | 282 // Note: the cache-control header should be enough, but this doesn't |
| 280 // hurt and can help renew the policy after it expires. | 283 // hurt and can help renew the policy after it expires. |
| 281 headers['ETag'] = hash; | 284 headers['ETag'] = hash; |
| 282 } | 285 } |
| 283 return response.change(headers: headers); | 286 return response.change(headers: headers); |
| 284 }; | 287 }; |
| 285 } | 288 } |
| 286 | 289 |
| 287 UriResolver _createImplicitEntryResolver(String entryPath) { | 290 UriResolver _createImplicitEntryResolver(String entryPath) { |
| 288 var entry = path.absolute(SourceResolverOptions.implicitHtmlFile); | 291 var entry = path.toUri(path.absolute(SourceResolverOptions.implicitHtmlFile)); |
| 289 var src = path.absolute(entryPath); | 292 var src = path.toUri(path.absolute(entryPath)); |
| 290 var provider = new MemoryResourceProvider(); | 293 var provider = new MemoryResourceProvider(); |
| 291 provider.newFile( | 294 provider.newFile( |
| 292 entry, '<body><script type="application/dart" src="$src"></script>'); | 295 entry.path, '<body><script type="application/dart" src="$src"></script>'); |
| 293 return new _ExistingSourceUriResolver(new ResourceUriResolver(provider)); | 296 return new _ExistingSourceUriResolver(new ResourceUriResolver(provider)); |
| 294 } | 297 } |
| 295 | 298 |
| 296 /// A UriResolver that continues to the next one if it fails to find an existing | 299 /// A UriResolver that continues to the next one if it fails to find an existing |
| 297 /// source file. This is unlike normal URI resolvers, that always return | 300 /// source file. This is unlike normal URI resolvers, that always return |
| 298 /// something, even if it is a non-existing file. | 301 /// something, even if it is a non-existing file. |
| 299 class _ExistingSourceUriResolver implements UriResolver { | 302 class _ExistingSourceUriResolver implements UriResolver { |
| 300 final UriResolver resolver; | 303 final UriResolver resolver; |
| 301 _ExistingSourceUriResolver(this.resolver); | 304 _ExistingSourceUriResolver(this.resolver); |
| 302 | 305 |
| 303 Source resolveAbsolute(Uri uri, [Uri actualUri]) { | 306 Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| 304 var src = resolver.resolveAbsolute(uri, actualUri); | 307 var src = resolver.resolveAbsolute(uri, actualUri); |
| 305 return src.exists() ? src : null; | 308 return src.exists() ? src : null; |
| 306 } | 309 } |
| 307 | 310 |
| 308 Uri restoreAbsolute(Source source) => resolver.restoreAbsolute(source); | 311 Uri restoreAbsolute(Source source) => resolver.restoreAbsolute(source); |
| 309 } | 312 } |
| 310 | 313 |
| 311 final _log = new Logger('dev_compiler.src.server'); | 314 final _log = new Logger('dev_compiler.src.server'); |
| 312 final _earlyErrorResult = new CheckerResults(const [], null, true); | 315 final _earlyErrorResult = new CheckerResults(const [], null, true); |
| OLD | NEW |