| Index: lib/src/server/server.dart
|
| diff --git a/lib/src/server/server.dart b/lib/src/server/server.dart
|
| deleted file mode 100644
|
| index 2a826a22f70cc963b210eba6be7277eccfb4dd90..0000000000000000000000000000000000000000
|
| --- a/lib/src/server/server.dart
|
| +++ /dev/null
|
| @@ -1,339 +0,0 @@
|
| -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| -// for details. All rights reserved. Use of this source code is governed by a
|
| -// BSD-style license that can be found in the LICENSE file.
|
| -
|
| -/// Development server that compiles Dart to JS on the fly.
|
| -
|
| -import 'dart:async';
|
| -import 'dart:convert';
|
| -import 'dart:io';
|
| -
|
| -import 'package:analyzer/file_system/file_system.dart' show ResourceUriResolver;
|
| -import 'package:analyzer/file_system/memory_file_system.dart';
|
| -import 'package:analyzer/src/generated/engine.dart'
|
| - show AnalysisContext, ChangeSet;
|
| -import 'package:analyzer/src/generated/error.dart';
|
| -import 'package:analyzer/src/generated/source.dart';
|
| -import 'package:logging/logging.dart' show Level, Logger, LogRecord;
|
| -import 'package:path/path.dart' as path;
|
| -import 'package:shelf/shelf.dart' as shelf;
|
| -import 'package:shelf/shelf_io.dart' as shelf;
|
| -import 'package:shelf_static/shelf_static.dart' as shelf_static;
|
| -
|
| -import '../codegen/code_generator.dart' show CodeGenerator;
|
| -import '../codegen/html_codegen.dart' show generateEntryHtml;
|
| -import '../codegen/js_codegen.dart';
|
| -import '../report/html_reporter.dart' show HtmlReporter;
|
| -import '../analysis_context.dart';
|
| -import '../compiler.dart' show AbstractCompiler, createErrorReporter;
|
| -import '../info.dart'
|
| - show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit;
|
| -import '../options.dart';
|
| -import '../report.dart';
|
| -import '../utils.dart';
|
| -
|
| -import 'dependency_graph.dart';
|
| -
|
| -/// Encapsulates the logic when the compiler is run as a development server.
|
| -class ServerCompiler extends AbstractCompiler {
|
| - SourceNode _entryNode;
|
| - List<LibraryInfo> _libraries = <LibraryInfo>[];
|
| - final _generators = <CodeGenerator>[];
|
| - bool _hashing;
|
| - bool _failure = false;
|
| -
|
| - factory ServerCompiler(AnalysisContext context, CompilerOptions options,
|
| - {AnalysisErrorListener reporter}) {
|
| - var srcOpts = options.sourceOptions;
|
| - var inputFiles = options.inputs;
|
| - var inputUris = inputFiles.map((String inputFile) =>
|
| - inputFile.startsWith('dart:') || inputFile.startsWith('package:')
|
| - ? Uri.parse(inputFile)
|
| - : new Uri.file(path.absolute(srcOpts.useImplicitHtml
|
| - ? SourceResolverOptions.implicitHtmlFile
|
| - : inputFile)));
|
| - var graph = new SourceGraph(context, reporter, options);
|
| - var entryNodes =
|
| - inputUris.map((inputUri) => graph.nodeFromUri(inputUri)).toList();
|
| -
|
| - return new ServerCompiler._(context, options, reporter, graph, entryNodes);
|
| - }
|
| -
|
| - ServerCompiler._(
|
| - AnalysisContext context,
|
| - CompilerOptions options,
|
| - AnalysisErrorListener reporter,
|
| - SourceGraph graph,
|
| - List<SourceNode> entryNodes)
|
| - : super(context, options, reporter) {
|
| - _entryNode = entryNodes.length == 1
|
| - ? entryNodes.first
|
| - : new EntryNode(graph, new Uri.file(inputBaseDir), entryNodes);
|
| -
|
| - if (outputDir != null) {
|
| - _generators.add(new JSGenerator(this));
|
| - }
|
| - // TODO(sigmund): refactor to support hashing of the dart output?
|
| - _hashing = options.enableHashing && _generators.length == 1;
|
| - }
|
| -
|
| - CheckerResults run() {
|
| - var clock = new Stopwatch()..start();
|
| -
|
| - // TODO(sigmund): we are missing a couple failures here. The
|
| - // dependency_graph now detects broken imports or unsupported features
|
| - // like more than one script tag (see .severe messages in
|
| - // dependency_graph.dart). Such failures should be reported back
|
| - // here so we can mark failure=true in the CheckerResults.
|
| - rebuild(_entryNode, _buildSource);
|
| - clock.stop();
|
| - var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
|
| - _log.fine('Compiled ${_libraries.length} libraries in ${time} s\n');
|
| - return new CheckerResults(
|
| - _libraries, _failure || options.codegenOptions.forceCompile);
|
| - }
|
| -
|
| - bool _buildSource(SourceNode node) {
|
| - node.clearSummary();
|
| - if (node is HtmlSourceNode) {
|
| - _buildHtmlFile(node);
|
| - } else if (node is DartSourceNode) {
|
| - _buildDartLibrary(node);
|
| - } else if (node is ResourceSourceNode) {
|
| - _buildResourceFile(node);
|
| - } else {
|
| - assert(false); // should not get a build request on PartSourceNode
|
| - }
|
| -
|
| - // TODO(sigmund): don't always return true.
|
| - // Use summaries to determine when rebuilding is needed.
|
| - return true;
|
| - }
|
| -
|
| - void _buildHtmlFile(HtmlSourceNode node) {
|
| - if (outputDir == null) return;
|
| - var output = generateEntryHtml(node, this);
|
| - if (output == null) {
|
| - _failure = true;
|
| - return;
|
| - }
|
| -
|
| - var filepath =
|
| - resourceOutputPath(node.uri, _entryNode.uri, options.runtimeDir);
|
| - String outputFile = path.join(outputDir, filepath);
|
| - new File(outputFile)
|
| - ..createSync(recursive: true)
|
| - ..writeAsStringSync(output);
|
| - }
|
| -
|
| - void _buildResourceFile(ResourceSourceNode node) {
|
| - // ResourceSourceNodes files that just need to be copied over to the output
|
| - // location. These can be external dependencies or pieces of the
|
| - // dev_compiler runtime.
|
| - if (outputDir == null) return;
|
| - var filepath =
|
| - resourceOutputPath(node.uri, _entryNode.uri, options.runtimeDir);
|
| - assert(filepath != null);
|
| - filepath = path.join(outputDir, filepath);
|
| - var dir = path.dirname(filepath);
|
| - new Directory(dir).createSync(recursive: true);
|
| - new File.fromUri(node.source.uri).copySync(filepath);
|
| - if (_hashing) node.cachingHash = computeHashFromFile(filepath);
|
| - }
|
| -
|
| - void _buildDartLibrary(DartSourceNode node) {
|
| - print('Compiling ${node.uri}');
|
| - var source = node.source;
|
| - // TODO(sigmund): find out from analyzer team if there is a better way
|
| - context.applyChanges(new ChangeSet()..changedSource(source));
|
| - var entryUnit = context.resolveCompilationUnit2(source, source);
|
| - var lib = entryUnit.element.enclosingElement;
|
| - if (!options.checkSdk && lib.source.isInSystemLibrary) return;
|
| - var current = node.info;
|
| - if (current != null) {
|
| - assert(current.library == lib);
|
| - } else {
|
| - node.info = current = new LibraryInfo(lib);
|
| - }
|
| - _libraries.add(current);
|
| -
|
| - var resolvedParts = node.parts
|
| - .map((p) => context.resolveCompilationUnit2(p.source, source))
|
| - .toList(growable: false);
|
| - var libraryUnit = new LibraryUnit(entryUnit, resolvedParts);
|
| - bool failureInLib = false;
|
| - for (var unit in libraryUnit.libraryThenParts) {
|
| - var unitSource = unit.element.source;
|
| - // TODO(sigmund): integrate analyzer errors with static-info (issue #6).
|
| - failureInLib = computeErrors(unitSource) || failureInLib;
|
| - }
|
| - if (failureInLib) {
|
| - _failure = true;
|
| - if (!options.codegenOptions.forceCompile) return;
|
| - }
|
| -
|
| - for (var cg in _generators) {
|
| - var hash = cg.generateLibrary(libraryUnit);
|
| - if (_hashing) node.cachingHash = hash;
|
| - }
|
| - }
|
| -
|
| - void _runAgain() {
|
| - var clock = new Stopwatch()..start();
|
| - _libraries = <LibraryInfo>[];
|
| - int changed = 0;
|
| -
|
| - // TODO(sigmund): propagate failures here (see TODO in run).
|
| - rebuild(_entryNode, (n) {
|
| - changed++;
|
| - return _buildSource(n);
|
| - });
|
| - clock.stop();
|
| - if (changed > 0) _dumpInfoIfRequested();
|
| - var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
|
| - _log.fine("Compiled ${changed} libraries in ${time} s\n");
|
| - }
|
| -
|
| - _dumpInfoIfRequested() {
|
| - var reporter = this.reporter;
|
| - if (reporter is HtmlReporter) {
|
| - reporter.finish(options);
|
| - } else if (reporter is SummaryReporter) {
|
| - var result = reporter.result;
|
| - if (outputDir != null) {
|
| - var filepath = path.join(outputDir, 'messages.json');
|
| - new File(filepath).writeAsStringSync(JSON.encode(result.toJsonMap()));
|
| - } else {
|
| - print(summaryToString(result));
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -class DevServer {
|
| - final ServerCompiler compiler;
|
| - final String outDir;
|
| - final String host;
|
| - final int port;
|
| - final String _entryPath;
|
| -
|
| - factory DevServer(CompilerOptions options) {
|
| - assert(options.inputs.isNotEmpty);
|
| -
|
| - var fileResolvers = createFileResolvers(options.sourceOptions);
|
| - if (options.sourceOptions.useImplicitHtml) {
|
| - fileResolvers.insert(0, _createImplicitEntryResolver(options.inputs[0]));
|
| - }
|
| -
|
| - var context = createAnalysisContextWithSources(options.sourceOptions,
|
| - fileResolvers: fileResolvers);
|
| -
|
| - var entryPath = path.basename(options.inputs[0]);
|
| - var extension = path.extension(entryPath);
|
| - if (extension != '.html' && !options.sourceOptions.useImplicitHtml) {
|
| - print('error: devc in server mode requires an HTML or Dart entry point.');
|
| - exit(1);
|
| - }
|
| -
|
| - // TODO(sigmund): allow running without a dir, but keep output in memory?
|
| - var outDir = options.codegenOptions.outputDir;
|
| - if (outDir == null) {
|
| - print('error: devc in server mode also requires specifying and '
|
| - 'output location for generated code.');
|
| - exit(1);
|
| - }
|
| - var port = options.port;
|
| - var host = options.host;
|
| - var reporter = createErrorReporter(context, options);
|
| - var compiler = new ServerCompiler(context, options, reporter: reporter);
|
| - return new DevServer._(compiler, outDir, host, port, entryPath);
|
| - }
|
| -
|
| - DevServer._(ServerCompiler compiler, this.outDir, this.host, this.port,
|
| - String entryPath)
|
| - : this.compiler = compiler,
|
| - // TODO(jmesserly): this logic is duplicated in a few places
|
| - this._entryPath = compiler.options.sourceOptions.useImplicitHtml
|
| - ? SourceResolverOptions.implicitHtmlFile
|
| - : entryPath;
|
| -
|
| - Future start() async {
|
| - // Create output directory if needed. shelf_static will fail otherwise.
|
| - var out = new Directory(outDir);
|
| - if (!await out.exists()) await out.create(recursive: true);
|
| -
|
| - var generatedHandler =
|
| - shelf_static.createStaticHandler(outDir, defaultDocument: _entryPath);
|
| - var sourceHandler = shelf_static.createStaticHandler(compiler.inputBaseDir,
|
| - serveFilesOutsidePath: true);
|
| - // TODO(vsm): Is there a better builtin way to compose these handlers?
|
| - var topLevelHandler = (shelf.Request request) {
|
| - // Prefer generated code
|
| - var response = generatedHandler(request);
|
| - if (response.statusCode == 404) {
|
| - // Fall back on original sources
|
| - response = sourceHandler(request);
|
| - }
|
| - return response;
|
| - };
|
| -
|
| - var handler = const shelf.Pipeline()
|
| - .addMiddleware(rebuildAndCache)
|
| - .addHandler(topLevelHandler);
|
| - await shelf.serve(handler, host, port);
|
| - print('Serving $_entryPath at http://$host:$port/');
|
| - // Give the compiler a head start. This is not needed for correctness,
|
| - // but will likely speed up the first load. Regardless of whether compile
|
| - // succeeds we should still start the server.
|
| - compiler.run();
|
| - // Server has started so this future will complete.
|
| - }
|
| -
|
| - shelf.Handler rebuildAndCache(shelf.Handler handler) => (request) {
|
| - // Trigger recompile only when requesting the HTML page.
|
| - var segments = request.url.pathSegments;
|
| - bool isEntryPage = segments.length == 0 || segments[0] == _entryPath;
|
| - if (isEntryPage) compiler._runAgain();
|
| -
|
| - // To help browsers cache resources that don't change, we serve these
|
| - // resources by adding a query parameter containing their hash:
|
| - // /{path-to-file.js}?____cached={hash}
|
| - var hash = request.url.queryParameters['____cached'];
|
| - var response = handler(request);
|
| - var policy = hash != null ? 'max-age=${24 * 60 * 60}' : 'no-cache';
|
| - var headers = {'cache-control': policy};
|
| - if (hash != null) {
|
| - // Note: the cache-control header should be enough, but this doesn't
|
| - // hurt and can help renew the policy after it expires.
|
| - headers['ETag'] = hash;
|
| - }
|
| - return response.change(headers: headers);
|
| - };
|
| -}
|
| -
|
| -UriResolver _createImplicitEntryResolver(String entryPath) {
|
| - var entry = path.toUri(path.absolute(SourceResolverOptions.implicitHtmlFile));
|
| - var src = path.toUri(path.absolute(entryPath));
|
| - var provider = new MemoryResourceProvider();
|
| - provider.newFile(
|
| - entry.path, '<body><script type="application/dart" src="$src"></script>');
|
| - return new _ExistingSourceUriResolver(new ResourceUriResolver(provider));
|
| -}
|
| -
|
| -/// A UriResolver that continues to the next one if it fails to find an existing
|
| -/// source file. This is unlike normal URI resolvers, that always return
|
| -/// something, even if it is a non-existing file.
|
| -class _ExistingSourceUriResolver implements UriResolver {
|
| - final UriResolver resolver;
|
| - _ExistingSourceUriResolver(this.resolver);
|
| -
|
| - Source resolveAbsolute(Uri uri, [Uri actualUri]) {
|
| - var src = resolver.resolveAbsolute(uri, actualUri);
|
| - return src.exists() ? src : null;
|
| - }
|
| -
|
| - Uri restoreAbsolute(Source source) => resolver.restoreAbsolute(source);
|
| -}
|
| -
|
| -final _log = new Logger('dev_compiler.src.server');
|
| -final _earlyErrorResult = new CheckerResults(const [], true);
|
|
|