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 |