diff --git a/lib/src/barback/asset_environment.dart b/lib/src/barback/asset_environment.dart
index 935bb64117d11aa08eab2fc50e8ef5f0fe508224..6663f5b6bdf4a6751942945246b988c492a49de0 100644
--- a/lib/src/barback/asset_environment.dart
+++ b/lib/src/barback/asset_environment.dart
@@ -18,12 +18,7 @@ import '../package.dart';
 import '../package_graph.dart';
 import '../source/cached.dart';
 import '../utils.dart';
-import 'dartdevc/dartdevc_bootstrap_transformer.dart';
-import 'dartdevc/dartdevc_module_transformer.dart';
-import 'dartdevc/dartdevc_resource_transformer.dart';
-import 'dartdevc/linked_summary_transformer.dart';
-import 'dartdevc/module_config_transformer.dart';
-import 'dartdevc/unlinked_summary_transformer.dart';
+import 'dartdevc/dartdevc_environment.dart';
 import 'admin_server.dart';
 import 'barback_server.dart';
 import 'compiler.dart';
@@ -87,10 +82,16 @@ class AssetEnvironment {
     return log.progress("Loading asset environment", () async {
       var graph = _adjustPackageGraph(entrypoint.packageGraph, mode, packages);
       var barback = new Barback(new PubPackageProvider(graph, compiler));
+      DartDevcEnvironment dartDevcEnvironment;
+      if (compiler == Compiler.dartDevc) {
+        dartDevcEnvironment =
+            new DartDevcEnvironment(barback, mode, environmentConstants, graph);
+      }
       barback.log.listen(_log);
 
       var environment = new AssetEnvironment._(graph, barback, mode,
-          watcherType, hostname, basePort, environmentConstants, compiler);
+          watcherType, hostname, basePort, environmentConstants, compiler,
+          dartDevcEnvironment: dartDevcEnvironment);
 
       await environment._load(entrypoints: entrypoints);
       return environment;
@@ -126,6 +127,11 @@ class AssetEnvironment {
   /// The [Barback] instance used to process assets in this environment.
   final Barback barback;
 
+  /// Manages running the dartdevc compiler on top of [barback].
+  ///
+  /// This is `null` unless `compiler == Compiler.dartDevc`.
+  final DartDevcEnvironment dartDevcEnvironment;
+
   /// The root package being built.
   Package get rootPackage => graph.entrypoint.root;
 
@@ -175,35 +181,18 @@ class AssetEnvironment {
   final Compiler compiler;
 
   AssetEnvironment._(this.graph, this.barback, this.mode, this._watcherType,
-      this._hostname, this._basePort, this.environmentConstants, this.compiler);
+      this._hostname, this._basePort, this.environmentConstants, this.compiler,
+      {this.dartDevcEnvironment});
 
   /// Gets the built-in [Transformer]s or [AggregateTransformer]s that should be
   /// added to [package].
   ///
   /// Returns `null` if there are none.
-  Iterable<Set> getBuiltInTransformers(Package package) {
-    var transformers = <List>[];
+  Iterable<Set<Transformer>> getBuiltInTransformers(Package package) {
+    var transformers = <List<Transformer>>[];
 
     var isRootPackage = package.name == rootPackage.name;
     switch (compiler) {
-      case Compiler.dartDevc:
-        var firstPhase = <dynamic>[new ModuleConfigTransformer()];
-        var lastPhase = <dynamic>[
-          new DartDevcModuleTransformer(mode,
-              environmentConstants: environmentConstants)
-        ];
-        if (isRootPackage) {
-          firstPhase.add(new DartDevcResourceTransformer());
-          lastPhase.add(new DartDevcBootstrapTransformer(mode));
-        }
-
-        transformers.addAll([
-          firstPhase,
-          [new UnlinkedSummaryTransformer()],
-          [new LinkedSummaryTransformer()],
-          lastPhase,
-        ]);
-        break;
       case Compiler.dart2JS:
         // the dart2js transformer only runs on the root package.
         if (isRootPackage) {
@@ -286,7 +275,8 @@ class AssetEnvironment {
 
     sourceDirectory.watchSubscription =
         await _provideDirectorySources(rootPackage, rootDirectory);
-    return await sourceDirectory.serve();
+    return await sourceDirectory.serve(
+        dartDevcEnvironment: dartDevcEnvironment);
   }
 
   /// Binds a new port to serve assets from within the "bin" directory of
@@ -465,6 +455,11 @@ class AssetEnvironment {
     assert(_modifiedSources != null);
 
     barback.updateSources(_modifiedSources);
+    if (dartDevcEnvironment != null) {
+      var modifiedPackages = new Set<String>()
+        ..addAll(_modifiedSources.map((id) => id.package));
+      modifiedPackages.forEach(dartDevcEnvironment.invalidatePackage);
+    }
     _modifiedSources = null;
   }
 
@@ -478,9 +473,6 @@ class AssetEnvironment {
   /// If [Compiler.dart2JS], then the [Dart2JSTransformer] is implicitly
   /// added to end of the root package's transformer phases.
   ///
-  /// if [Compiler.dartDevc], then the [DevCompilerTransformer] is
-  /// implicitly added to the end of all package's transformer phases.
-  ///
   /// If [entrypoints] is passed, only transformers necessary to run those
   /// entrypoints will be loaded.
   ///
@@ -489,7 +481,8 @@ class AssetEnvironment {
   Future _load({Iterable<AssetId> entrypoints}) {
     return log.progress("Initializing barback", () async {
       // Bind a server that we can use to load the transformers.
-      var transformerServer = await BarbackServer.bind(this, _hostname, 0);
+      var transformerServer = await BarbackServer.bind(this, _hostname, 0,
+          dartDevcEnvironment: dartDevcEnvironment);
 
       var errorStream = barback.errors.map((error) {
         // Even most normally non-fatal barback errors should take down pub if
@@ -578,6 +571,7 @@ class AssetEnvironment {
     var ids = _listDirectorySources(package, dir);
     if (_modifiedSources == null) {
       barback.updateSources(ids);
+      dartDevcEnvironment?.invalidatePackage(package.name);
     } else {
       _modifiedSources.addAll(ids);
     }
@@ -588,6 +582,7 @@ class AssetEnvironment {
     var ids = _listDirectorySources(rootPackage, dir);
     if (_modifiedSources == null) {
       barback.removeSources(ids);
+      dartDevcEnvironment?.invalidatePackage(rootPackage.name);
     } else {
       _modifiedSources.removeAll(ids);
     }
@@ -662,11 +657,13 @@ class AssetEnvironment {
           _modifiedSources.remove(id);
         } else {
           barback.removeSources([id]);
+          dartDevcEnvironment?.invalidatePackage(package.name);
         }
       } else if (_modifiedSources != null) {
         _modifiedSources.add(id);
       } else {
         barback.updateSources([id]);
+        dartDevcEnvironment?.invalidatePackage(package.name);
       }
     });
 
diff --git a/lib/src/barback/barback_server.dart b/lib/src/barback/barback_server.dart
index 6e29bf0b65ed664a1b597560ba681523f1b0dd03..17e634ad0f30ba00d8c899aa7ae9559c93544c73 100644
--- a/lib/src/barback/barback_server.dart
+++ b/lib/src/barback/barback_server.dart
@@ -19,6 +19,8 @@ import '../utils.dart';
 import 'base_server.dart';
 import 'asset_environment.dart';
 
+import 'dartdevc/dartdevc_environment.dart';
+
 /// Callback for determining if an asset with [id] should be served or not.
 typedef bool AllowAsset(AssetId id);
 
@@ -43,6 +45,11 @@ class BarbackServer extends BaseServer<BarbackServerResult> {
   /// If this is `null`, all assets may be served.
   AllowAsset allowAsset;
 
+  /// Manages running the dartdevc compiler on top of barback.
+  ///
+  /// This is `null` unless `environment.compiler == Compiler.dartDevc`.
+  final DartDevcEnvironment dartDevcEnvironment;
+
   /// Creates a new server and binds it to [port] of [host].
   ///
   /// This server serves assets from [barback], and uses [rootDirectory]
@@ -52,7 +59,9 @@ class BarbackServer extends BaseServer<BarbackServerResult> {
   /// URLs). If [package] is omitted, it defaults to the entrypoint package.
   static Future<BarbackServer> bind(
       AssetEnvironment environment, String host, int port,
-      {String package, String rootDirectory}) {
+      {String package,
+      String rootDirectory,
+      DartDevcEnvironment dartDevcEnvironment}) {
     if (package == null) package = environment.rootPackage.name;
     return bindServer(host, port).then((server) {
       if (rootDirectory == null) {
@@ -60,12 +69,14 @@ class BarbackServer extends BaseServer<BarbackServerResult> {
       } else {
         log.fine('Bound "$rootDirectory" to $host:$port.');
       }
-      return new BarbackServer._(environment, server, package, rootDirectory);
+      return new BarbackServer._(environment, server, package, rootDirectory,
+          dartDevcEnvironment: dartDevcEnvironment);
     });
   }
 
   BarbackServer._(AssetEnvironment environment, HttpServer server, this.package,
-      this.rootDirectory)
+      this.rootDirectory,
+      {this.dartDevcEnvironment})
       : super(environment, server);
 
   /// Converts a [url] served by this server into an [AssetId] that can be
@@ -115,51 +126,54 @@ class BarbackServer extends BaseServer<BarbackServerResult> {
 
     return environment.barback
         .getAssetById(id)
-        .then((result) {
-          return result;
-        })
         .then((asset) => _serveAsset(request, asset))
         .catchError((error, trace) {
-          if (error is! AssetNotFoundException) throw error;
-          return environment.barback
-              .getAssetById(id.addExtension("/index.html"))
-              .then((asset) {
-            if (request.url.path.isEmpty || request.url.path.endsWith('/')) {
-              return _serveAsset(request, asset);
-            }
-
-            // We only want to serve index.html if the URL explicitly ends in a
-            // slash. For other URLs, we redirect to one with the slash added to
-            // implicitly support that too. This follows Apache's behavior.
-            logRequest(request, "302 Redirect to /${request.url}/");
-            return new shelf.Response.found('/${request.url}/');
-          }).catchError((newError, newTrace) {
-            // If we find neither the original file or the index, we should report
-            // the error about the original to the user.
-            throw newError is AssetNotFoundException ? error : newError;
-          });
-        })
-        .catchError((error, trace) {
-          if (error is! AssetNotFoundException) {
-            var chain = new Chain.forTrace(trace);
-            logRequest(request, "$error\n$chain");
-
-            addError(error, chain);
-            close();
-            return new shelf.Response.internalServerError();
-          }
-
-          addResult(new BarbackServerResult._failure(request.url, id, error));
-          return notFound(request, asset: id);
-        })
-        .then((response) {
-          // Allow requests of any origin to access "pub serve". This is useful for
-          // running "pub serve" in parallel with another development server. Since
-          // "pub serve" is only used as a development server and doesn't require
-          // any sort of credentials anyway, this is secure.
-          return response
-              .change(headers: const {"Access-Control-Allow-Origin": "*"});
-        });
+      if (error is! AssetNotFoundException) throw error;
+      return environment.barback
+          .getAssetById(id.addExtension("/index.html"))
+          .then((asset) {
+        if (request.url.path.isEmpty || request.url.path.endsWith('/')) {
+          return _serveAsset(request, asset);
+        }
+
+        // We only want to serve index.html if the URL explicitly ends in a
+        // slash. For other URLs, we redirect to one with the slash added to
+        // implicitly support that too. This follows Apache's behavior.
+        logRequest(request, "302 Redirect to /${request.url}/");
+        return new shelf.Response.found('/${request.url}/');
+      }).catchError((newError, newTrace) {
+        // If we find neither the original file or the index, we should report
+        // the error about the original to the user.
+        throw newError is AssetNotFoundException ? error : newError;
+      });
+    }).catchError((error, trace) {
+      if (error is! AssetNotFoundException || dartDevcEnvironment == null) {
+        throw error;
+      }
+      return dartDevcEnvironment.getAssetById(id).then((asset) {
+        if (asset == null) throw new AssetNotFoundException(id);
+        return _serveAsset(request, asset);
+      });
+    }).catchError((error, trace) {
+      if (error is! AssetNotFoundException) {
+        var chain = new Chain.forTrace(trace);
+        logRequest(request, "$error\n$chain");
+
+        addError(error, chain);
+        close();
+        return new shelf.Response.internalServerError();
+      }
+
+      addResult(new BarbackServerResult._failure(request.url, id, error));
+      return notFound(request, asset: id);
+    }).then((response) {
+      // Allow requests of any origin to access "pub serve". This is useful for
+      // running "pub serve" in parallel with another development server. Since
+      // "pub serve" is only used as a development server and doesn't require
+      // any sort of credentials anyway, this is secure.
+      return response
+          .change(headers: const {"Access-Control-Allow-Origin": "*"});
+    });
   }
 
   /// Returns the body of [asset] as a response to [request].
diff --git a/lib/src/barback/dartdevc/dartdevc.dart b/lib/src/barback/dartdevc/dartdevc.dart
index a17853ae5b0ecb87629af941f3b6e15cb221ae7e..9b79cbf0a38fe7aac45e021b3a488e3f223104d6 100644
--- a/lib/src/barback/dartdevc/dartdevc.dart
+++ b/lib/src/barback/dartdevc/dartdevc.dart
@@ -5,18 +5,32 @@
 import 'dart:async';
 import 'dart:io';
 
+import 'package:analyzer/analyzer.dart';
 import 'package:barback/barback.dart';
 import 'package:bazel_worker/bazel_worker.dart';
 import 'package:cli_util/cli_util.dart' as cli_util;
 import 'package:path/path.dart' as p;
 
+import '../../dart.dart';
 import '../../io.dart';
-import 'module.dart';
 import 'module_reader.dart';
 import 'scratch_space.dart';
 import 'summaries.dart';
 import 'workers.dart';
 
+/// Returns whether or not [dartId] is an app entrypoint (basically, whether or
+/// not it has a `main` function).
+Future<bool> isAppEntryPoint(
+    AssetId dartId, Future<Asset> getAsset(AssetId id)) async {
+  assert(dartId.extension == '.dart');
+  var dartAsset = await getAsset(dartId);
+  // Skip reporting errors here, dartdevc will report them later with nicer
+  // formatting.
+  var parsed = parseCompilationUnit(await dartAsset.readAsString(),
+      suppressErrors: true);
+  return isEntrypoint(parsed);
+}
+
 /// Bootstraps the JS module for the entrypoint dart file [dartEntrypointId]
 /// with two additional JS files:
 ///
@@ -34,7 +48,10 @@ import 'workers.dart';
 /// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know
 /// immediately what assets will be output.
 Map<AssetId, Future<Asset>> bootstrapDartDevcEntrypoint(
-    AssetId dartEntrypointId, BarbackMode mode, ModuleReader moduleReader) {
+    AssetId dartEntrypointId,
+    BarbackMode mode,
+    ModuleReader moduleReader,
+    Future<Asset> getAsset(AssetId id)) {
   var bootstrapId = dartEntrypointId.addExtension('.bootstrap.js');
   var jsEntrypointId = dartEntrypointId.addExtension('.js');
   var jsMapEntrypointId = jsEntrypointId.addExtension('.map');
@@ -63,15 +80,31 @@ Map<AssetId, Future<Asset>> bootstrapDartDevcEntrypoint(
     // TODO(jakemac53): Sane module name creation, this only works in the most
     // basic of cases.
     //
-    // See https://github.com/dart-lang/sdk/issues/27262 for the root issue which
-    // will allow us to not rely on the naming schemes that dartdevc uses
+    // See https://github.com/dart-lang/sdk/issues/27262 for the root issue
+    // which will allow us to not rely on the naming schemes that dartdevc uses
     // internally, but instead specify our own.
     var appModuleScope = p.url
         .split(p.url.withoutExtension(
             p.url.relative(dartEntrypointId.path, from: moduleDir)))
         .join("__")
         .replaceAll('.', '\$46');
+
+    // Modules not under a `packages` directory need custom module paths.
+    var customModulePaths = <String>[];
+    var transitiveDeps = await moduleReader.readTransitiveDeps(module);
+    for (var dep in transitiveDeps) {
+      if (dep.dir != 'lib') {
+        customModulePaths.add('"${dep.dir}/${dep.name}": "${dep.name}"');
+      }
+    }
+
     var bootstrapContent = '''
+require.config({
+    paths: {
+      ${customModulePaths.join(',\n      ')}
+    }
+});
+
 require(["$appModulePath", "dart_sdk"], function(app, dart_sdk) {
   dart_sdk._isolate_helper.startRootIsolate(() => {}, []);
   app.$appModuleScope.main();
@@ -112,20 +145,34 @@ document.head.appendChild(el);
 /// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know
 /// immediately what assets will be output.
 Map<AssetId, Future<Asset>> createDartdevcModule(
-    Module module,
+    AssetId id,
+    ModuleReader moduleReader,
     ScratchSpace scratchSpace,
-    Set<AssetId> linkedSummaryIds,
     Map<String, String> environmentConstants,
     BarbackMode mode,
     logError(String message)) {
+  assert(id.extension == '.js');
   var outputCompleters = <AssetId, Completer<Asset>>{
-    module.id.jsId: new Completer(),
+    id: new Completer(),
   };
   if (mode == BarbackMode.DEBUG) {
-    outputCompleters[module.id.jsSourceMapId] = new Completer();
+    outputCompleters[id.addExtension('.map')] = new Completer();
   }
 
   () async {
+    var module = await moduleReader.moduleFor(id);
+    if (module == null) {
+      logError('No module found for $id.');
+      outputCompleters.values.forEach((c) => c.complete(null));
+      return;
+    }
+    var transitiveModuleDeps = await moduleReader.readTransitiveDeps(module);
+    var linkedSummaryIds =
+        transitiveModuleDeps.map((depId) => depId.linkedSummaryId).toSet();
+    var allAssetIds = new Set<AssetId>()
+      ..addAll(module.assetIds)
+      ..addAll(linkedSummaryIds);
+    await scratchSpace.ensureAssets(allAssetIds);
     var jsOutputFile = scratchSpace.fileFor(module.id.jsId);
     var sdk_summary = p.url.join(sdkDir.path, 'lib/_internal/ddc_sdk.sum');
     var request = new WorkRequest();
@@ -158,8 +205,8 @@ Map<AssetId, Future<Asset>> createDartdevcModule(
       request.arguments.addAll(['-s', scratchSpace.fileFor(id).path]);
     }
 
-    // Add URL mappings for all the package: files to tell DartDevc where to find
-    // them.
+    // Add URL mappings for all the package: files to tell DartDevc where to
+    // find them.
     for (var id in module.assetIds) {
       var uri = canonicalUriFor(id);
       if (uri.startsWith('package:')) {
@@ -167,8 +214,8 @@ Map<AssetId, Future<Asset>> createDartdevcModule(
             .add('--url-mapping=$uri,${scratchSpace.fileFor(id).path}');
       }
     }
-    // And finally add all the urls to compile, using the package: path for files
-    // under lib and the full absolute path for other files.
+    // And finally add all the urls to compile, using the package: path for
+    // files under lib and the full absolute path for other files.
     request.arguments.addAll(module.assetIds.map((id) {
       var uri = canonicalUriFor(id);
       if (uri.startsWith('package:')) {
@@ -183,12 +230,9 @@ Map<AssetId, Future<Asset>> createDartdevcModule(
     // status code if something failed. Today we just make sure there is an output
     // JS file to verify it was successful.
     if (response.exitCode != EXIT_CODE_OK || !jsOutputFile.existsSync()) {
-      var message =
-          'Error compiling dartdevc module: ${module.id}.\n${response.output}';
-      logError(message);
-      outputCompleters.values.forEach((completer) {
-        completer.completeError(message);
-      });
+      logError('Error compiling dartdevc module: ${module.id}.\n'
+          '${response.output}');
+      outputCompleters.values.forEach((c) => c.complete(null));
     } else {
       outputCompleters[module.id.jsId].complete(
           new Asset.fromBytes(module.id.jsId, jsOutputFile.readAsBytesSync()));
diff --git a/lib/src/barback/dartdevc/dartdevc_bootstrap_transformer.dart b/lib/src/barback/dartdevc/dartdevc_bootstrap_transformer.dart
deleted file mode 100644
index 863531a839d87a6e0f1c97a197a8bdb9c5cf33b0..0000000000000000000000000000000000000000
--- a/lib/src/barback/dartdevc/dartdevc_bootstrap_transformer.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2017, 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.
-
-import 'dart:async';
-
-import 'package:analyzer/analyzer.dart';
-import 'package:barback/barback.dart';
-
-import '../../dart.dart';
-import '../../io.dart';
-import 'dartdevc.dart';
-import 'module_reader.dart';
-
-class DartDevcBootstrapTransformer extends Transformer {
-  final BarbackMode mode;
-
-  DartDevcBootstrapTransformer(this.mode);
-
-  @override
-  bool isPrimary(AssetId id) {
-    // Only `.dart` files not under `lib` or `bin` are considered candidates for
-    // being dartdevc application entrypoints.
-    if (id.extension != '.dart') return false;
-    var dir = topLevelDir(id.path);
-    return dir != 'lib' && dir != 'bin';
-  }
-
-  @override
-  Future apply(Transform transform) async {
-    var parsed =
-        parseCompilationUnit(await transform.primaryInput.readAsString());
-    if (!isEntrypoint(parsed)) return;
-    var outputs = await bootstrapDartDevcEntrypoint(transform.primaryInput.id,
-        mode, new ModuleReader(transform.readInputAsString));
-    await Future.wait(outputs.values
-        .map((futureAsset) async => transform.addOutput(await futureAsset)));
-  }
-}
diff --git a/lib/src/barback/dartdevc/dartdevc_environment.dart b/lib/src/barback/dartdevc/dartdevc_environment.dart
new file mode 100644
index 0000000000000000000000000000000000000000..382172807b40c16c63fc06a1b150bb78c7bedfa4
--- /dev/null
+++ b/lib/src/barback/dartdevc/dartdevc_environment.dart
@@ -0,0 +1,266 @@
+// Copyright (c) 2017, 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.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:barback/barback.dart';
+import 'package:cli_util/cli_util.dart' as cli_util;
+import 'package:path/path.dart' as p;
+
+import '../../io.dart';
+import '../../log.dart' as log;
+import '../../package_graph.dart';
+import 'dartdevc.dart';
+import 'module.dart';
+import 'module_computer.dart';
+import 'module_reader.dart';
+import 'scratch_space.dart';
+import 'summaries.dart';
+
+/// Handles running dartdevc on top of a [Barback] instance.
+///
+/// You must call [invalidatePackage] any time a package is updated, since
+/// barback doesn't provide a mechanism to tell you which files have changed.
+class DartDevcEnvironment {
+  final _AssetCache _assetCache;
+  final Barback _barback;
+  final Map<String, String> _environmentConstants;
+  final BarbackMode _mode;
+  ModuleReader _moduleReader;
+  final PackageGraph _packageGraph;
+  ScratchSpace _scratchSpace;
+
+  DartDevcEnvironment(
+      this._barback, this._mode, this._environmentConstants, this._packageGraph)
+      : _assetCache = new _AssetCache(_packageGraph) {
+    _moduleReader = new ModuleReader(_readModule);
+    _scratchSpace = new ScratchSpace(_getAsset);
+  }
+
+  /// Deletes the [_scratchSpace].
+  Future cleanUp() => _scratchSpace.delete();
+
+  /// Builds all dartdevc files required for all app entrypoints in
+  /// [inputAssets].
+  ///
+  /// Returns only the `.js` files which are required to load the apps.
+  Future<AssetSet> doFullBuild(AssetSet inputAssets,
+      {logError(String message)}) async {
+    var modulesToBuild = new Set<ModuleId>();
+    var jsAssets = new AssetSet();
+    for (var asset in inputAssets) {
+      if (asset.id.package != _packageGraph.entrypoint.root.name) continue;
+      if (asset.id.extension != '.dart') continue;
+      // We only care about real entrypoint modules, we collect those and all
+      // their transitive deps.
+      if (!await isAppEntryPoint(asset.id, _barback.getAssetById)) continue;
+      // Build the entrypoint js files, and collect the set of transitive
+      // modules that are required (will be built later).
+      var futureAssets =
+          _buildAsset(asset.id.addExtension('.js'), logError: logError);
+      var assets = await Future.wait(futureAssets.values);
+      jsAssets.addAll(assets.where((asset) => asset != null));
+      var module = await _moduleReader.moduleFor(asset.id);
+      modulesToBuild.add(module.id);
+      modulesToBuild.addAll(await _moduleReader.readTransitiveDeps(module));
+    }
+
+    // Build all required modules for the apps that were discovered.
+    var allFutureAssets = <Future<Asset>>[];
+    for (var module in modulesToBuild) {
+      allFutureAssets
+          .addAll(_buildAsset(module.jsId, logError: logError).values);
+    }
+    var assets = await Future.wait(allFutureAssets);
+    jsAssets.addAll(assets.where((asset) => asset != null));
+
+    return jsAssets;
+  }
+
+  /// Attempt to get an [Asset] by [id], completes with an
+  /// [AssetNotFoundException] if the asset couldn't be built.
+  Future<Asset> getAssetById(AssetId id) async {
+    if (_assetCache[id] == null) {
+      if (_isEntrypointId(id)) {
+        var dartId = _entrypointDartId(id);
+        if (dartId != null &&
+            await isAppEntryPoint(dartId, _barback.getAssetById)) {
+          _buildAsset(id);
+        }
+      } else {
+        _buildAsset(id);
+      }
+    }
+    return _assetCache[id];
+  }
+
+  /// Invalidates [package] and all packages that depend on [package].
+  void invalidatePackage(String package) {
+    _assetCache.invalidatePackage(package);
+    _scratchSpace.deletePackageFiles(package,
+        isRootPackage: package == _packageGraph.entrypoint.root.name);
+  }
+
+  /// Handles building all assets that we know how to build.
+  ///
+  /// Completes with an [AssetNotFoundException] if the asset couldn't be built.
+  Map<AssetId, Future<Asset>> _buildAsset(AssetId id,
+      {logError(String message)}) {
+    logError ??= log.error;
+    Map<AssetId, Future<Asset>> assets;
+    if (id.path.endsWith(unlinkedSummaryExtension)) {
+      assets = {
+        id: createUnlinkedSummary(id, _moduleReader, _scratchSpace, logError)
+      };
+    } else if (id.path.endsWith(linkedSummaryExtension)) {
+      assets = {
+        id: createLinkedSummary(id, _moduleReader, _scratchSpace, logError)
+      };
+    } else if (_isEntrypointId(id)) {
+      var dartId = _entrypointDartId(id);
+      if (dartId != null) {
+        assets = bootstrapDartDevcEntrypoint(
+            dartId, _mode, _moduleReader, _barback.getAssetById);
+      }
+    } else if (id.path.endsWith('require.js') ||
+        id.path.endsWith('dart_sdk.js')) {
+      assets = {id: _buildJsResource(id)};
+    } else if (id.path.endsWith('require.js.map') ||
+        id.path.endsWith('dart_sdk.js.map')) {
+      assets = {id: new Future.error(new AssetNotFoundException(id))};
+    } else if (id.path.endsWith('.js') || id.path.endsWith('.js.map')) {
+      var jsId = id.extension == '.map' ? id.changeExtension('') : id;
+      assets = createDartdevcModule(jsId, _moduleReader, _scratchSpace,
+          _environmentConstants, _mode, logError);
+    } else if (id.path.endsWith(moduleConfigName)) {
+      assets = {id: _buildModuleConfig(id)};
+    }
+    assets ??= <AssetId, Future<Asset>>{};
+
+    for (var id in assets.keys) {
+      _assetCache[id] = assets[id];
+    }
+    return assets;
+  }
+
+  /// Builds a module config asset at [id].
+  Future<Asset> _buildModuleConfig(AssetId id) async {
+    assert(id.path.endsWith(moduleConfigName));
+    var moduleDir = topLevelDir(id.path);
+    var allAssets = await _barback.getAllAssets();
+    var moduleAssets = allAssets.where((asset) =>
+        asset.id.package == id.package &&
+        asset.id.extension == '.dart' &&
+        topLevelDir(asset.id.path) == moduleDir);
+    var moduleMode =
+        moduleDir == 'lib' ? ModuleMode.public : ModuleMode.private;
+    var modules = await computeModules(moduleMode, moduleAssets);
+    var encoded = JSON.encode(modules);
+    return new Asset.fromString(id, encoded);
+  }
+
+  /// Builds the `dart_sdk.js` or `require.js` assets by copying them from the
+  /// SDK.
+  Future<Asset> _buildJsResource(AssetId id) async {
+    var sdk = cli_util.getSdkDir();
+
+    switch (p.url.basename(id.path)) {
+      case 'dart_sdk.js':
+        var sdkAmdJsPath =
+            p.url.join(sdk.path, 'lib/dev_compiler/amd/dart_sdk.js');
+        return new Asset.fromPath(id, sdkAmdJsPath);
+      case 'require.js':
+        var requireJsPath =
+            p.url.join(sdk.path, 'lib/dev_compiler/amd/require.js');
+        return new Asset.fromFile(id, new File(requireJsPath));
+      default:
+        return null;
+    }
+  }
+
+  /// Whether or not this looks like a request for an entrypoint or bootstrap
+  /// file.
+  bool _isEntrypointId(AssetId id) =>
+      id.path.endsWith('.bootstrap.js') ||
+      id.path.endsWith('.bootstrap.js.map') ||
+      id.path.endsWith('.dart.js') ||
+      id.path.endsWith('.dart.js.map');
+
+  /// Helper to read a module config file, used by [_moduleReader].
+  ///
+  /// Skips barback and reads directly from [this] since we create all these
+  /// files.
+  Future<String> _readModule(AssetId moduleConfigId) async {
+    var asset = await getAssetById(moduleConfigId);
+    return asset.readAsString();
+  }
+
+  /// Gets an [Asset] by [id] asynchronously.
+  ///
+  /// All `.dart` files are read from [_barback], and all other files are read
+  /// from [this]. This is because the only files we care about from barback are
+  /// `.dart` files.
+  Future<Asset> _getAsset(AssetId id) async {
+    var asset = id.extension == '.dart'
+        ? await _barback.getAssetById(id)
+        : await getAssetById(id);
+    if (asset == null) throw new AssetNotFoundException(id);
+    return asset;
+  }
+}
+
+/// Gives the dart entrypoint [AssetId] for a bootstrap js [id].
+AssetId _entrypointDartId(AssetId id) {
+  if (id.extension == '.map') id = id.changeExtension('');
+  assert(id.path.endsWith('.bootstrap.js') || id.path.endsWith('.dart.js'));
+  // Skip entrypoints under lib.
+  if (topLevelDir(id.path) == 'lib') return null;
+
+  // Remove the `.js` extension.
+  var dartId = id.changeExtension('');
+  // Conditionally change the `.bootstrap` extension to a `.dart` extension.
+  if (dartId.extension == '.bootstrap') dartId.changeExtension('.dart');
+  assert(dartId.extension == '.dart');
+  return dartId;
+}
+
+/// Manages a set of cached future [Asset]s.
+class _AssetCache {
+  /// [Asset]s are indexed first by package and then path, this allows us to
+  /// invalidate whole packages efficiently.
+  final _assets = <String, Map<String, Future<Asset>>>{};
+
+  final PackageGraph _packageGraph;
+
+  _AssetCache(this._packageGraph);
+
+  Future<Asset> operator [](AssetId id) {
+    var packageCache = _assets[id.package];
+    if (packageCache == null) return null;
+    return packageCache[id.path];
+  }
+
+  void operator []=(AssetId id, Future<Asset> asset) {
+    var packageCache =
+        _assets.putIfAbsent(id.package, () => <String, Future<Asset>>{});
+    packageCache[id.path] = asset;
+  }
+
+  /// Invalidates [package] and all packages that depend on [package].
+  void invalidatePackage(String packageNameToInvalidate) {
+    _assets.remove(packageNameToInvalidate);
+    // Also invalidate any package with a transitive dep on the invalidated
+    // package.
+    var packageToInvalidate = _packageGraph.packages[packageNameToInvalidate];
+    for (var packageName in _packageGraph.packages.keys) {
+      if (_packageGraph
+          .transitiveDependencies(packageName)
+          .contains(packageToInvalidate)) {
+        _assets.remove(packageName);
+      }
+    }
+  }
+}
diff --git a/lib/src/barback/dartdevc/dartdevc_module_transformer.dart b/lib/src/barback/dartdevc/dartdevc_module_transformer.dart
deleted file mode 100644
index f1f34bb8a73f1ad1738f5c47162d376e9e53ba5b..0000000000000000000000000000000000000000
--- a/lib/src/barback/dartdevc/dartdevc_module_transformer.dart
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) 2017, 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.
-
-import 'dart:async';
-
-import 'package:barback/barback.dart';
-
-import 'dartdevc.dart';
-import 'module.dart';
-import 'module_reader.dart';
-import 'scratch_space.dart';
-
-/// Creates dartdevc modules given [moduleConfigName] files which describe a set
-/// of [Module]s.
-///
-/// Linked summaries should have already been created for all modules and will
-/// be read in to create the modules.
-class DartDevcModuleTransformer extends Transformer {
-  @override
-  String get allowedExtensions => moduleConfigName;
-
-  final Map<String, String> environmentConstants;
-  final BarbackMode mode;
-
-  DartDevcModuleTransformer(this.mode, {this.environmentConstants = const {}});
-
-  @override
-  Future apply(Transform transform) async {
-    ScratchSpace scratchSpace;
-    var reader = new ModuleReader(transform.readInputAsString);
-    var modules = await reader.readModules(transform.primaryInput.id);
-    try {
-      var allAssetIds = new Set<AssetId>();
-      var summariesForModule = <ModuleId, Set<AssetId>>{};
-      for (var module in modules) {
-        var transitiveModuleDeps = await reader.readTransitiveDeps(module);
-        var linkedSummaryIds =
-            transitiveModuleDeps.map((depId) => depId.linkedSummaryId).toSet();
-        summariesForModule[module.id] = linkedSummaryIds;
-        allAssetIds..addAll(module.assetIds)..addAll(linkedSummaryIds);
-      }
-      // Create a single temp environment for all the modules in this package.
-      scratchSpace =
-          await ScratchSpace.create(allAssetIds, transform.readInput);
-      // We only log warnings in debug mode for ddc modules because they don't all
-      // need to compile successfully, only the ones imported by an entrypoint do.
-      var logError = mode == BarbackMode.DEBUG
-          ? transform.logger.warning
-          : transform.logger.error;
-      await Future.wait(modules.map((m) async {
-        var outputs = createDartdevcModule(m, scratchSpace,
-            summariesForModule[m.id], environmentConstants, mode, logError);
-        await Future.wait(outputs.values.map(
-            (futureAsset) async => transform.addOutput(await futureAsset)));
-      }));
-    } finally {
-      scratchSpace?.delete();
-    }
-  }
-}
diff --git a/lib/src/barback/dartdevc/dartdevc_resource_transformer.dart b/lib/src/barback/dartdevc/dartdevc_resource_transformer.dart
deleted file mode 100644
index d160243fde4ead56ba9f8a7a77191ee02567f49b..0000000000000000000000000000000000000000
--- a/lib/src/barback/dartdevc/dartdevc_resource_transformer.dart
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2017, 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.
-
-import 'dart:async';
-
-import 'package:analyzer/analyzer.dart';
-import 'package:barback/barback.dart';
-import 'package:path/path.dart' as p;
-
-import '../../dart.dart';
-import '../../io.dart';
-import 'dartdevc.dart';
-
-/// Copies the `dart_sdk.js` and `require.js` AMD files from the SDK into each
-/// entrypoint dir.
-class DartDevcResourceTransformer extends AggregateTransformer
-    implements DeclaringAggregateTransformer {
-  /// Group files by output directory, skipping `lib` and `bin`.
-  @override
-  String classifyPrimary(AssetId id) {
-    if (p.extension(id.path) != '.dart') return null;
-    var dir = topLevelDir(id.path);
-    if (dir == 'lib' || dir == 'bin') return null;
-    return p.url.dirname(id.path);
-  }
-
-  @override
-  Future apply(AggregateTransform transform) async {
-    // If there are no entrypoints then skip this folder.
-    var hasEntrypoint = false;
-    await for (var asset in transform.primaryInputs) {
-      if (isEntrypoint(parseCompilationUnit(await asset.readAsString(),
-          parseFunctionBodies: false))) {
-        hasEntrypoint = true;
-        break;
-      }
-    }
-    if (!hasEntrypoint) return;
-
-    var outputs = copyDartDevcResources(transform.package, transform.key);
-    outputs.values.forEach(transform.addOutput);
-  }
-
-  @override
-  Future declareOutputs(DeclaringAggregateTransform transform) async {
-    transform.declareOutput(new AssetId(
-        transform.package, p.url.join(transform.key, 'dart_sdk.js')));
-    transform.declareOutput(new AssetId(
-        transform.package, p.url.join(transform.key, 'require.js')));
-  }
-}
diff --git a/lib/src/barback/dartdevc/linked_summary_transformer.dart b/lib/src/barback/dartdevc/linked_summary_transformer.dart
deleted file mode 100644
index 1eba96c89f7d3f2c847ef38337ce3533be72029b..0000000000000000000000000000000000000000
--- a/lib/src/barback/dartdevc/linked_summary_transformer.dart
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2017, 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.
-
-import 'dart:async';
-
-import 'package:barback/barback.dart';
-
-import 'module.dart';
-import 'module_reader.dart';
-import 'scratch_space.dart';
-import 'summaries.dart';
-
-/// Creates linked analyzer summaries given [moduleConfigName] files which
-/// describe a set of [Module]s.
-///
-/// Unlinked summaries should have already been created for all modules and will
-/// be read in to create the linked summaries.
-class LinkedSummaryTransformer extends Transformer {
-  @override
-  String get allowedExtensions => moduleConfigName;
-
-  LinkedSummaryTransformer();
-
-  @override
-  Future apply(Transform transform) async {
-    var reader = new ModuleReader(transform.readInputAsString);
-    var configId = transform.primaryInput.id;
-    var modules = await reader.readModules(configId);
-    ScratchSpace scratchSpace;
-    try {
-      var allAssetIds = new Set<AssetId>();
-      var summariesForModule = <ModuleId, Set<AssetId>>{};
-      for (var module in modules) {
-        var transitiveModuleDeps = await reader.readTransitiveDeps(module);
-        var unlinkedSummaryIds = transitiveModuleDeps
-            .map((depId) => depId.unlinkedSummaryId)
-            .toSet();
-        summariesForModule[module.id] = unlinkedSummaryIds;
-        allAssetIds..addAll(module.assetIds)..addAll(unlinkedSummaryIds);
-      }
-      // Create a single temp environment for all the modules in this package.
-      scratchSpace =
-          await ScratchSpace.create(allAssetIds, transform.readInput);
-      await Future.wait(modules.map((m) async {
-        var outputs = createLinkedSummaryForModule(
-            m, summariesForModule[m.id], scratchSpace, transform.logger.error);
-
-        await Future.wait(outputs.values.map(
-            (futureAsset) async => transform.addOutput(await futureAsset)));
-      }));
-    } finally {
-      scratchSpace?.delete();
-    }
-  }
-}
diff --git a/lib/src/barback/dartdevc/module_config_transformer.dart b/lib/src/barback/dartdevc/module_config_transformer.dart
deleted file mode 100644
index b63cc95f2730966b57d310e01d8a794b178c349c..0000000000000000000000000000000000000000
--- a/lib/src/barback/dartdevc/module_config_transformer.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2017, 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.
-
-import 'dart:async';
-import 'dart:convert';
-
-import 'package:barback/barback.dart';
-import 'package:path/path.dart' as p;
-
-import '../../io.dart';
-import 'module.dart';
-import 'module_computer.dart';
-import 'module_reader.dart';
-
-/// Computes the ideal set of [Module]s for top level directories in a package,
-/// and outputs a single `.moduleConfig` file in each one.
-class ModuleConfigTransformer extends AggregateTransformer {
-  ModuleConfigTransformer();
-
-  @override
-  String classifyPrimary(AssetId id) {
-    if (p.extension(id.path) != '.dart') return null;
-    return topLevelDir(id.path);
-  }
-
-  @override
-  Future apply(AggregateTransform transform) async {
-    var moduleMode =
-        transform.key == 'lib' ? ModuleMode.public : ModuleMode.private;
-    var allAssets = await transform.primaryInputs.toList();
-    var modules = await computeModules(moduleMode, allAssets);
-    var encoded = JSON.encode(modules);
-    transform.addOutput(new Asset.fromString(
-        new AssetId(
-            transform.package, p.url.join(transform.key, moduleConfigName)),
-        encoded));
-  }
-}
diff --git a/lib/src/barback/dartdevc/module_reader.dart b/lib/src/barback/dartdevc/module_reader.dart
index 8e30ad55a630abf67d4cae8c96c2f456c7c59acb..a07f78458319edcf85aadd3403d421d24766f48f 100644
--- a/lib/src/barback/dartdevc/module_reader.dart
+++ b/lib/src/barback/dartdevc/module_reader.dart
@@ -9,6 +9,7 @@ import 'package:barback/barback.dart';
 import 'package:path/path.dart' as p;
 
 import 'module.dart';
+import 'summaries.dart';
 
 typedef FutureOr<String> ReadAsString(AssetId id);
 
@@ -45,7 +46,29 @@ class ModuleReader {
     var moduleConfigId =
         new AssetId(id.package, p.join(parts.first, moduleConfigName));
     await readModules(moduleConfigId);
-    return _modulesByAssetId[id];
+    if (id.extension == '.dart') {
+      return _modulesByAssetId[id];
+    } else {
+      var baseName = p.url.basename(id.path);
+      String moduleName;
+      if (baseName.endsWith('.js')) {
+        moduleName = p.withoutExtension(baseName);
+      } else if (baseName.endsWith('.js.map')) {
+        moduleName = baseName.substring(0, baseName.length - '.js.map'.length);
+      } else if (baseName.endsWith(unlinkedSummaryExtension)) {
+        moduleName = baseName.substring(
+            0, baseName.length - unlinkedSummaryExtension.length);
+      } else if (baseName.endsWith(linkedSummaryExtension)) {
+        moduleName = baseName.substring(
+            0, baseName.length - linkedSummaryExtension.length);
+      }
+      if (moduleName == null) {
+        throw new ArgumentError(
+            'Can only get modules for `.js` or `.dart` files, but got `$id`.');
+      }
+      var moduleId = new ModuleId(id.package, moduleName, parts.first);
+      return _modulesByModuleId[moduleId];
+    }
   }
 
   /// Computes the transitive deps of [id] by reading all the modules for all
diff --git a/lib/src/barback/dartdevc/scratch_space.dart b/lib/src/barback/dartdevc/scratch_space.dart
index 2c03986603319c86a167a1aa9bd407304efc5131..446787e0f47d9d8313cf64b912ff3ce80ffaaa80 100644
--- a/lib/src/barback/dartdevc/scratch_space.dart
+++ b/lib/src/barback/dartdevc/scratch_space.dart
@@ -10,32 +10,70 @@ import 'package:path/path.dart' as p;
 
 import '../../io.dart';
 
+typedef Future<Asset> AssetReader(AssetId id);
+
 /// An on-disk temporary environment for running executables that don't have
 /// a standard Dart library API.
 class ScratchSpace {
   final Directory tempDir;
   final Directory packagesDir;
+  final AssetReader getAsset;
+
+  // Assets which have a file created but it is still being written to.
+  final _pendingWrites = <AssetId, Future>{};
 
-  ScratchSpace._(Directory tempDir)
+  ScratchSpace._(Directory tempDir, this.getAsset)
       : packagesDir = new Directory(p.join(tempDir.path, 'packages')),
         this.tempDir = tempDir;
 
-  /// Creates a new [ScratchSpace] containing [assetIds].
+  factory ScratchSpace(Future<Asset> getAsset(AssetId id)) {
+    var tempDir = new Directory(createSystemTempDir());
+    return new ScratchSpace._(tempDir, getAsset);
+  }
+
+  /// Copies [assetIds] to [tempDir] if they don't exist.
   ///
   /// Any [Asset] that is under a `lib` dir will be output under a `packages`
   /// directory corresponding to its package, and any other assets are output
   /// directly under the temp dir using their unmodified path.
-  static Future<ScratchSpace> create(
-      Iterable<AssetId> assetIds, Stream<List<int>> readAsset(AssetId)) async {
-    var tempDir = new Directory(createSystemTempDir());
+  Future ensureAssets(Iterable<AssetId> assetIds) async {
     var futures = <Future>[];
     for (var id in assetIds) {
-      var filePath = p.join(tempDir.path, _relativePathFor(id));
-      ensureDir(p.dirname(filePath));
-      futures.add(createFileFromStream(readAsset(id), filePath));
+      var file = fileFor(id);
+      if (file.existsSync()) {
+        var pending = _pendingWrites[id];
+        if (pending != null) futures.add(pending);
+      } else {
+        file.createSync(recursive: true);
+        var done = () async {
+          var asset = await getAsset(id);
+          await createFileFromStream(asset.read(), file.path);
+          _pendingWrites.remove(id);
+        }();
+        _pendingWrites[id] = done;
+        futures.add(done);
+      }
+    }
+    return Future.wait(futures);
+  }
+
+  /// Deletes all files for [package] from the temp dir (synchronously).
+  ///
+  /// This always deletes the [package] dir under [packagesDir].
+  ///
+  /// If [isRootPackage] then this also deletes all top level entities under
+  /// [tempDir] other than the [packagesDir].
+  void deletePackageFiles(String package, {bool isRootPackage}) {
+    isRootPackage ??= false;
+    var packageDir = new Directory(p.join(packagesDir.path, package));
+    if (packageDir.existsSync()) packageDir.deleteSync(recursive: true);
+    if (isRootPackage) {
+      var entities = tempDir.listSync(recursive: false);
+      for (var entity in entities) {
+        if (entity.path == packagesDir.path) continue;
+        entity.deleteSync(recursive: true);
+      }
     }
-    await Future.wait(futures);
-    return new ScratchSpace._(tempDir);
   }
 
   /// Deletes the temp directory for this environment.
diff --git a/lib/src/barback/dartdevc/summaries.dart b/lib/src/barback/dartdevc/summaries.dart
index fc60170a2a84439294635b9dbca27abd6db58a77..b5d6e215c22e2715e0371d53716a8996674b2836 100644
--- a/lib/src/barback/dartdevc/summaries.dart
+++ b/lib/src/barback/dartdevc/summaries.dart
@@ -8,6 +8,7 @@ import 'package:barback/barback.dart';
 import 'package:bazel_worker/bazel_worker.dart';
 
 import 'module.dart';
+import 'module_reader.dart';
 import 'scratch_space.dart';
 import 'workers.dart';
 
@@ -22,104 +23,77 @@ final String unlinkedSummaryExtension = '.unlinked.sum';
 ///
 /// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know
 /// immediately what assets will be output.
-Map<AssetId, Future<Asset>> createLinkedSummaryForModule(
-    Module module,
-    Set<AssetId> unlinkedSummaryIds,
-    ScratchSpace scratchSpace,
-    logError(String message)) {
-  var outputCompleters = <AssetId, Completer<Asset>>{
-    module.id.linkedSummaryId: new Completer<Asset>(),
-  };
-
-  () async {
-    var summaryOutputFile = scratchSpace.fileFor(module.id.linkedSummaryId);
-    var request = new WorkRequest();
-    // TODO(jakemac53): Diet parsing results in erroneous errors in later steps,
-    // but ideally we would do that (pass '--build-summary-only-diet').
-    request.arguments.addAll([
-      '--build-summary-only',
-      '--build-summary-output=${summaryOutputFile.path}',
-      '--strong',
-    ]);
-    // Add all the unlinked summaries as build summary inputs.
-    request.arguments.addAll(unlinkedSummaryIds.map((id) =>
-        '--build-summary-unlinked-input=${scratchSpace.fileFor(id).path}'));
-    // Add all the files to include in the linked summary bundle.
-    request.arguments.addAll(module.assetIds.map((id) {
-      var uri = canonicalUriFor(id);
-      if (!uri.startsWith('package:')) {
-        uri = 'file://$uri';
-      }
-      return '$uri|${scratchSpace.fileFor(id).path}';
-    }));
-    var response = await analyzerDriver.doWork(request);
-    if (response.exitCode == EXIT_CODE_ERROR) {
-      var message =
-          'Error creating linked summaries for module: ${module.id}.\n'
-          '${response.output}';
-      logError(message);
-      outputCompleters.values.forEach((completer) {
-        completer.completeError(message);
-      });
-    } else {
-      outputCompleters[module.id.linkedSummaryId].complete(new Asset.fromBytes(
-          module.id.linkedSummaryId, summaryOutputFile.readAsBytesSync()));
-    }
-  }();
-
-  var outputFutures = <AssetId, Future<Asset>>{};
-  outputCompleters.forEach((k, v) => outputFutures[k] = v.future);
-  return outputFutures;
+Future<Asset> createLinkedSummary(AssetId id, ModuleReader moduleReader,
+    ScratchSpace scratchSpace, logError(String message)) async {
+  assert(id.path.endsWith(linkedSummaryExtension));
+  var module = await moduleReader.moduleFor(id);
+  var transitiveModuleDeps = await moduleReader.readTransitiveDeps(module);
+  var unlinkedSummaryIds =
+      transitiveModuleDeps.map((depId) => depId.unlinkedSummaryId).toSet();
+  var allAssetIds = new Set<AssetId>()
+    ..addAll(module.assetIds)
+    ..addAll(unlinkedSummaryIds);
+  await scratchSpace.ensureAssets(allAssetIds);
+  var summaryOutputFile = scratchSpace.fileFor(module.id.linkedSummaryId);
+  var request = new WorkRequest();
+  // TODO(jakemac53): Diet parsing results in erroneous errors in later steps,
+  // but ideally we would do that (pass '--build-summary-only-diet').
+  request.arguments.addAll([
+    '--build-summary-only',
+    '--build-summary-output=${summaryOutputFile.path}',
+    '--strong',
+  ]);
+  // Add all the unlinked summaries as build summary inputs.
+  request.arguments.addAll(unlinkedSummaryIds.map((id) =>
+      '--build-summary-unlinked-input=${scratchSpace.fileFor(id).path}'));
+  // Add all the files to include in the linked summary bundle.
+  request.arguments.addAll(_analyzerSourceArgsForModule(module, scratchSpace));
+  var response = await analyzerDriver.doWork(request);
+  if (response.exitCode == EXIT_CODE_ERROR) {
+    logError('Error creating linked summaries for module: ${module.id}.\n'
+        '${response.output}\n${request.arguments}');
+    return null;
+  }
+  return new Asset.fromBytes(
+      module.id.linkedSummaryId, summaryOutputFile.readAsBytesSync());
 }
 
-/// Creates an unlinked summary for [module].
-///
-/// [scratchSpace] must have the Dart sources for this module available.
-///
-/// Synchronously returns a `Map<AssetId, Future<Asset>>` so that you can know
-/// immediately what assets will be output.
-Map<AssetId, Future<Asset>> createUnlinkedSummaryForModule(
-    Module module, ScratchSpace scratchSpace, logError(String message)) {
-  var outputCompleters = <AssetId, Completer<Asset>>{
-    module.id.unlinkedSummaryId: new Completer<Asset>(),
-  };
+/// Creates an unlinked summary at [id].
+Future<Asset> createUnlinkedSummary(AssetId id, ModuleReader moduleReader,
+    ScratchSpace scratchSpace, logError(String message)) async {
+  assert(id.path.endsWith(unlinkedSummaryExtension));
+  var module = await moduleReader.moduleFor(id);
+  await scratchSpace.ensureAssets(module.assetIds);
+  var summaryOutputFile = scratchSpace.fileFor(module.id.unlinkedSummaryId);
+  var request = new WorkRequest();
+  // TODO(jakemac53): Diet parsing results in erroneous errors later on today,
+  // but ideally we would do that (pass '--build-summary-only-diet').
+  request.arguments.addAll([
+    '--build-summary-only',
+    '--build-summary-only-unlinked',
+    '--build-summary-output=${summaryOutputFile.path}',
+    '--strong',
+  ]);
+  // Add all the files to include in the unlinked summary bundle.
+  request.arguments.addAll(_analyzerSourceArgsForModule(module, scratchSpace));
+  var response = await analyzerDriver.doWork(request);
+  if (response.exitCode == EXIT_CODE_ERROR) {
+    logError('Error creating unlinked summaries for module: ${module.id}.\n'
+        '${response.output}');
+    return null;
+  }
+  return new Asset.fromBytes(
+      module.id.unlinkedSummaryId, summaryOutputFile.readAsBytesSync());
+}
 
-  () async {
-    var summaryOutputFile = scratchSpace.fileFor(module.id.unlinkedSummaryId);
-    var request = new WorkRequest();
-    // TODO(jakemac53): Diet parsing results in erroneous errors later on today,
-    // but ideally we would do that (pass '--build-summary-only-diet').
-    request.arguments.addAll([
-      '--build-summary-only',
-      '--build-summary-only-unlinked',
-      '--build-summary-output=${summaryOutputFile.path}',
-      '--strong',
-    ]);
-    // Add all the files to include in the unlinked summary bundle.
-    request.arguments.addAll(module.assetIds.map((id) {
-      var uri = canonicalUriFor(id);
-      if (!uri.startsWith('package:')) {
-        uri = 'file://$uri';
-      }
-      return '$uri|${scratchSpace.fileFor(id).path}';
-    }));
-    var response = await analyzerDriver.doWork(request);
-    if (response.exitCode == EXIT_CODE_ERROR) {
-      var message =
-          'Error creating unlinked summaries for module: ${module.id}.\n'
-          '${response.output}';
-      logError(message);
-      outputCompleters.values.forEach((completer) {
-        completer.completeError(message);
-      });
-    } else {
-      outputCompleters[module.id.unlinkedSummaryId].complete(
-          new Asset.fromBytes(module.id.unlinkedSummaryId,
-              summaryOutputFile.readAsBytesSync()));
+Iterable<String> _analyzerSourceArgsForModule(
+    Module module, ScratchSpace scratchSpace) {
+  return module.assetIds.map((id) {
+    var uri = canonicalUriFor(id);
+    var file = scratchSpace.fileFor(id);
+    if (!uri.startsWith('package:')) {
+      uri = file.uri.toString();
     }
-  }();
-
-  var outputFutures = <AssetId, Future<Asset>>{};
-  outputCompleters.forEach((k, v) => outputFutures[k] = v.future);
-  return outputFutures;
+    return '$uri|${file.path}';
+  });
 }
diff --git a/lib/src/barback/dartdevc/unlinked_summary_transformer.dart b/lib/src/barback/dartdevc/unlinked_summary_transformer.dart
deleted file mode 100644
index 8d1ad5458724f3a610204e0eafe16a194210e1e4..0000000000000000000000000000000000000000
--- a/lib/src/barback/dartdevc/unlinked_summary_transformer.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2017, 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.
-
-import 'dart:async';
-
-import 'package:barback/barback.dart';
-
-import 'module.dart';
-import 'module_reader.dart';
-import 'scratch_space.dart';
-import 'summaries.dart';
-
-/// Creates unlinked analyzer summaries given [moduleConfigName] files which
-/// describe a set of [Module]s.
-class UnlinkedSummaryTransformer extends Transformer {
-  @override
-  String get allowedExtensions => moduleConfigName;
-
-  UnlinkedSummaryTransformer();
-
-  @override
-  Future apply(Transform transform) async {
-    var reader = new ModuleReader(transform.readInputAsString);
-    var configId = transform.primaryInput.id;
-    var modules = await reader.readModules(configId);
-    ScratchSpace scratchSpace;
-    try {
-      var allAssetIds = modules.fold(new Set<AssetId>(), (allAssets, module) {
-        allAssets.addAll(module.assetIds);
-        return allAssets;
-      });
-      // Create a single temp environment for all the modules in this package.
-      scratchSpace =
-          await ScratchSpace.create(allAssetIds, transform.readInput);
-      await Future.wait(modules.map((m) async {
-        var outputs = createUnlinkedSummaryForModule(
-            m, scratchSpace, transform.logger.error);
-        await Future.wait(outputs.values.map(
-            (futureAsset) async => transform.addOutput(await futureAsset)));
-      }));
-    } finally {
-      scratchSpace?.delete();
-    }
-  }
-}
diff --git a/lib/src/barback/load_all_transformers.dart b/lib/src/barback/load_all_transformers.dart
index d4cbda291eba8511afc2ad772f9171d1a133ffb4..897823621767b5a9d077056e5bfdf5cc0ef17606 100644
--- a/lib/src/barback/load_all_transformers.dart
+++ b/lib/src/barback/load_all_transformers.dart
@@ -94,6 +94,7 @@ Future loadAllTransformers(
       var phases =
           await loader.transformersForPhases(package.pubspec.transformers);
       environment.barback.updateTransformers(packageName, phases);
+      environment.dartDevcEnvironment?.invalidatePackage(packageName);
     }));
   }
 
@@ -112,8 +113,10 @@ Future loadAllTransformers(
     // immediate emission. Issue 17305 means that the caller will be unable
     // to receive this result unless we delay the update to after this
     // function returns.
-    newFuture(
-        () => environment.barback.updateTransformers(package.name, phases));
+    newFuture(() {
+      environment.barback.updateTransformers(package.name, phases);
+      environment.dartDevcEnvironment?.invalidatePackage(package.name);
+    });
   }));
 }
 
diff --git a/lib/src/barback/source_directory.dart b/lib/src/barback/source_directory.dart
index 8d202d2cd0bd5dd2bccc90cbbe17c396c04f8f02..dc39723cce46e86b97e91fcbc80c923f707acf13 100644
--- a/lib/src/barback/source_directory.dart
+++ b/lib/src/barback/source_directory.dart
@@ -8,6 +8,7 @@ import 'package:watcher/watcher.dart';
 
 import 'asset_environment.dart';
 import 'barback_server.dart';
+import 'dartdevc/dartdevc_environment.dart';
 
 /// A directory in the entrypoint package whose contents have been made
 /// available to barback and that are bound to a server.
@@ -39,9 +40,10 @@ class SourceDirectory {
   SourceDirectory(this._environment, this.directory, this.hostname, this.port);
 
   /// Binds a server running on [hostname]:[port] to this directory.
-  Future<BarbackServer> serve() {
+  Future<BarbackServer> serve({DartDevcEnvironment dartDevcEnvironment}) {
     return BarbackServer
-        .bind(_environment, hostname, port, rootDirectory: directory)
+        .bind(_environment, hostname, port,
+            rootDirectory: directory, dartDevcEnvironment: dartDevcEnvironment)
         .then((server) {
       _serverCompleter.complete(server);
       return server;
diff --git a/lib/src/command/build.dart b/lib/src/command/build.dart
index 5805b152325ca06cba45f9150aab3d34c3001979..6e3e4a6c915c2818f7e47174a68886c1ad1a23cd 100644
--- a/lib/src/command/build.dart
+++ b/lib/src/command/build.dart
@@ -70,9 +70,9 @@ class BuildCommand extends BarbackCommand {
           environmentConstants: environmentConstants, compiler: compiler);
 
       var hasError = false;
-      // Show in-progress errors, but not results. Those get handled
-      // implicitly by getAllAssets().
-      environment.barback.errors.listen((error) {
+
+      // Unified error handler for barback and dartdevc.
+      logError(error) {
         log.error(log.red("Build error:\n$error"));
         hasError = true;
 
@@ -81,7 +81,11 @@ class BuildCommand extends BarbackCommand {
           // more properties later.
           errorsJson.add({"error": error.toString()});
         }
-      });
+      }
+
+      // Show in-progress errors, but not results. Those get handled
+      // implicitly by getAllAssets().
+      environment.barback.errors.listen(logError);
 
       // If we're using JSON output, the regular server logging is disabled.
       // Instead, we collect it here to include in the final JSON result.
@@ -102,16 +106,15 @@ class BuildCommand extends BarbackCommand {
         return environment.barback.getAllAssets();
       });
 
-      // Find all of the JS entrypoints we built.
-      var dart2JSEntrypoints = assets
-          .where((asset) => asset.id.path.endsWith(".dart.js"))
-          .map((asset) => asset.id);
-
-      await Future.wait(assets.map(_writeAsset));
-      await _copyBrowserJsFiles(dart2JSEntrypoints, assets);
-
-      log.message('Built $builtFiles ${pluralize('file', builtFiles)} '
-          'to "$outputDirectory".');
+      // Add all the js assets from dartdevc to the build, we don't need the
+      // rest once a build is complete.
+      if (environment.dartDevcEnvironment != null) {
+        await log.progress("Building dartdevc modules", () async {
+          assets.addAll(await environment.dartDevcEnvironment
+              .doFullBuild(assets, logError: logError));
+        });
+        await environment.dartDevcEnvironment.cleanUp();
+      }
 
       if (hasError) {
         log.error(log.red("Build failed."));
@@ -119,6 +122,16 @@ class BuildCommand extends BarbackCommand {
             {"buildResult": "failure", "errors": errorsJson, "log": logJson});
         return flushThenExit(exit_codes.DATA);
       } else {
+        // Find all of the JS entrypoints we built.
+        var dart2JSEntrypoints = assets
+            .where((asset) => asset.id.path.endsWith(".dart.js"))
+            .map((asset) => asset.id);
+
+        await Future.wait(assets.map(_writeAsset));
+        await _copyBrowserJsFiles(dart2JSEntrypoints, assets);
+
+        log.message('Built $builtFiles ${pluralize('file', builtFiles)} '
+            'to "$outputDirectory".');
         log.json.message({
           "buildResult": "success",
           "outputDirectory": outputDirectory,
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index 12f30bceada66a1dd5912105e36e416b919765f6..570af5d7d38406b51cc7a0c7cbe50b6dbfb73911 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -159,7 +159,6 @@ class PackageGraph {
   /// Note that a static package isn't the same as an immutable package (see
   /// [isPackageMutable]).
   bool isPackageStatic(String package, Compiler compiler) {
-    if (compiler == Compiler.dartDevc) return false;
     var id = lockFile.packages[package];
     if (id == null) return false;
     if (entrypoint.cache.source(id.source) is! CachedSource) return false;
diff --git a/test/barback/dartdevc/dartdevc_resource_transformer_test.dart b/test/barback/dartdevc/dartdevc_resource_transformer_test.dart
index fe5a17af5c2d7d204bd1cf7dda60268784a6b0a8..20d21bd7fe42a02298c5b08f46844595ea1022d1 100644
--- a/test/barback/dartdevc/dartdevc_resource_transformer_test.dart
+++ b/test/barback/dartdevc/dartdevc_resource_transformer_test.dart
@@ -7,7 +7,7 @@ import '../../test_pub.dart';
 import '../../serve/utils.dart';
 
 main() {
-  integration("dartdevc resources are copied next to entrypoints only", () {
+  integration("dartdevc resources are copied next to entrypoints", () {
     d.dir(appPath, [
       d.appPubspec(),
       d.dir("lib", [
@@ -27,8 +27,6 @@ main() {
     requestShouldSucceed('require.js', null);
     requestShouldSucceed('subdir/dart_sdk.js', null);
     requestShouldSucceed('subdir/require.js', null);
-    requestShould404('packages/$appPath/dart_sdk.js');
-    requestShould404('packages/$appPath/require.js');
     endPubServe();
   });
 }
diff --git a/test/barback/dartdevc/linked_summary_transformer_test.dart b/test/barback/dartdevc/linked_summary_transformer_test.dart
index 000b023bcbe4e93d7d292380e7ad3b6d4d37a706..2f3a45208483366aa00ba678f32fe940bfa54a72 100644
--- a/test/barback/dartdevc/linked_summary_transformer_test.dart
+++ b/test/barback/dartdevc/linked_summary_transformer_test.dart
@@ -53,34 +53,34 @@ void main() {}
     pubServe(args: ['--compiler', 'dartdevc']);
 
     linkedSummaryRequestShouldSucceed('web__main$linkedSummaryExtension', [
-      'file://web/main.dart',
-      'package:myapp/hello.dart',
-      'package:foo/foo.dart'
+      endsWith('web/main.dart'),
+      equals('package:myapp/hello.dart'),
+      equals('package:foo/foo.dart')
     ], [
-      'file://web/main.dart'
+      endsWith('web/main.dart')
     ], [
       endsWith('packages/myapp/lib__hello.unlinked.sum'),
       endsWith('packages/foo/lib__foo.unlinked.sum'),
     ]);
     linkedSummaryRequestShouldSucceed(
         'packages/myapp/lib__hello$linkedSummaryExtension', [
-      'package:myapp/hello.dart',
-      'package:foo/foo.dart'
+      equals('package:myapp/hello.dart'),
+      equals('package:foo/foo.dart')
     ], [
-      'package:myapp/hello.dart'
+      equals('package:myapp/hello.dart')
     ], [
       endsWith('packages/foo/lib__foo.unlinked.sum'),
     ]);
     linkedSummaryRequestShouldSucceed(
         'packages/foo/lib__foo$linkedSummaryExtension',
-        ['package:foo/foo.dart'],
-        ['package:foo/foo.dart']);
+        [equals('package:foo/foo.dart')],
+        [equals('package:foo/foo.dart')]);
     endPubServe();
   });
 }
 
 void linkedSummaryRequestShouldSucceed(String uri,
-    List<String> expectedLinkedUris, List<String> expectedUnlinkedUris,
+    List<Matcher> expectedLinkedUris, List<Matcher> expectedUnlinkedUris,
     [List<Matcher> expectedSummaryDeps = const []]) {
   scheduleRequest(uri).then((response) {
     expect(response.statusCode, 200);
diff --git a/test/barback/dartdevc/scratch_space_test.dart b/test/barback/dartdevc/scratch_space_test.dart
index cfa65528ce19a38802a22c79aede9759be5f1a48..f734ffabeff538fc1ce0f83c0ee3388a75120424 100644
--- a/test/barback/dartdevc/scratch_space_test.dart
+++ b/test/barback/dartdevc/scratch_space_test.dart
@@ -2,7 +2,6 @@
 // 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.
 
-import 'dart:async';
 import 'dart:io';
 
 import 'package:barback/barback.dart';
@@ -13,7 +12,8 @@ import 'package:pub/src/barback/dartdevc/scratch_space.dart';
 import 'package:pub/src/io.dart';
 
 void main() {
-  test('Can create and delete a scratch space', () async {
+  group('ScratchSpace', () {
+    ScratchSpace scratchSpace;
     Map<AssetId, List<int>> allAssets = [
       'dep|lib/dep.dart',
       'myapp|lib/myapp.dart',
@@ -23,35 +23,57 @@ void main() {
       return assets;
     });
 
-    var scratchSpace = await ScratchSpace.create(
-        allAssets.keys, (id) => new Stream.fromIterable([allAssets[id]]));
+    setUp(() async {
+      scratchSpace = new ScratchSpace(
+          (id) async => new Asset.fromBytes(id, allAssets[id]));
+      await scratchSpace.ensureAssets(allAssets.keys);
+    });
+
+    tearDown(() async {
+      await scratchSpace.delete();
+      for (var id in allAssets.keys) {
+        var file = scratchSpace.fileFor(id);
+        expect(file.existsSync(), isFalse);
+      }
+      expect(scratchSpace.tempDir.existsSync(), isFalse);
+    });
 
-    expect(p.isWithin(Directory.systemTemp.path, scratchSpace.tempDir.path),
-        isTrue);
+    test('Can create and delete a scratch space', () async {
+      expect(p.isWithin(Directory.systemTemp.path, scratchSpace.tempDir.path),
+          isTrue);
 
-    for (var id in allAssets.keys) {
-      var file = scratchSpace.fileFor(id);
-      expect(file.existsSync(), isTrue);
-      expect(file.readAsStringSync(), equals('$id'));
+      for (var id in allAssets.keys) {
+        var file = scratchSpace.fileFor(id);
+        expect(file.existsSync(), isTrue);
+        expect(file.readAsStringSync(), equals('$id'));
 
-      var relativeFilePath =
-          p.relative(file.path, from: scratchSpace.tempDir.path);
-      if (topLevelDir(id.path) == 'lib') {
-        var packagesPath =
-            p.join('packages', id.package, p.relative(id.path, from: 'lib'));
-        expect(relativeFilePath, equals(packagesPath));
-      } else {
-        expect(relativeFilePath, equals(id.path));
+        var relativeFilePath =
+            p.relative(file.path, from: scratchSpace.tempDir.path);
+        if (topLevelDir(id.path) == 'lib') {
+          var packagesPath =
+              p.join('packages', id.package, p.relative(id.path, from: 'lib'));
+          expect(relativeFilePath, equals(packagesPath));
+        } else {
+          expect(relativeFilePath, equals(id.path));
+        }
       }
-    }
+    });
 
-    await scratchSpace.delete();
+    test('can delete an individual package from a scratch space', () async {
+      scratchSpace.deletePackageFiles('dep', isRootPackage: false);
+      var depId = new AssetId.parse('dep|lib/dep.dart');
+      expect(scratchSpace.fileFor(depId).existsSync(), isFalse);
+      allAssets.keys.where((id) => id.package == 'myapp').forEach((id) {
+        expect(scratchSpace.fileFor(id).existsSync(), isTrue);
+      });
 
-    for (var id in allAssets.keys) {
-      var file = scratchSpace.fileFor(id);
-      expect(file.existsSync(), isFalse);
-    }
-    expect(scratchSpace.tempDir.existsSync(), isFalse);
+      await scratchSpace.ensureAssets(allAssets.keys);
+      scratchSpace.deletePackageFiles('myapp', isRootPackage: true);
+      allAssets.keys.where((id) => id.package == 'myapp').forEach((id) {
+        expect(scratchSpace.fileFor(id).existsSync(), isFalse);
+      });
+      expect(scratchSpace.fileFor(depId).existsSync(), isTrue);
+    });
   });
 
   test('canonicalUriFor', () {
diff --git a/test/barback/dartdevc/unlinked_summary_transformer_test.dart b/test/barback/dartdevc/unlinked_summary_transformer_test.dart
index a8259ede07c1137cff33a1e075b9da5eda6b0198..ecb9866af0c1b9a84fe689cc44959377ed3525ce 100644
--- a/test/barback/dartdevc/unlinked_summary_transformer_test.dart
+++ b/test/barback/dartdevc/unlinked_summary_transformer_test.dart
@@ -54,19 +54,19 @@ void main() {}
     pubServe(args: ['--compiler', 'dartdevc']);
 
     unlinkedSummaryRequestShouldSucceed(
-        'web__main$unlinkedSummaryExtension', ['file://web/main.dart']);
+        'web__main$unlinkedSummaryExtension', [endsWith('web/main.dart')]);
     unlinkedSummaryRequestShouldSucceed(
         'packages/myapp/lib__hello$unlinkedSummaryExtension',
-        ['package:myapp/hello.dart']);
+        [equals('package:myapp/hello.dart')]);
     unlinkedSummaryRequestShouldSucceed(
         'packages/foo/lib__foo$unlinkedSummaryExtension',
-        ['package:foo/foo.dart']);
+        [equals('package:foo/foo.dart')]);
     endPubServe();
   });
 }
 
 void unlinkedSummaryRequestShouldSucceed(
-    String uri, List<String> expectedUnlinkedUris) {
+    String uri, List<Matcher> expectedUnlinkedUris) {
   var expected = unorderedMatches(expectedUnlinkedUris);
   scheduleRequest(uri).then((response) {
     expect(response.statusCode, 200);
diff --git a/test/compiler/ignores_non_entrypoint_dart_files_test.dart b/test/compiler/ignores_non_entrypoint_dart_files_test.dart
index 3e678e43610c7b3e8690d05d85cd9cdb238798bb..555a338162fba78d278dae71faefd17d123b34fb 100644
--- a/test/compiler/ignores_non_entrypoint_dart_files_test.dart
+++ b/test/compiler/ignores_non_entrypoint_dart_files_test.dart
@@ -29,17 +29,8 @@ main() {
         args: ["build", "--compiler=${compiler.name}"],
         output: new RegExp(r'Built [\d]+ files? to "build".'));
 
-    var expectedWebDir;
-    switch (compiler) {
-      case Compiler.dart2JS:
-        expectedWebDir = d.nothing('web');
-        break;
-      case Compiler.dartDevc:
-        expectedWebDir = d.dir('web', [d.file('.moduleConfig', '[]')]);
-        break;
-    }
     d.dir(appPath, [
-      d.dir('build', [expectedWebDir])
+      d.dir('build', [d.nothing('web')])
     ]).validate();
   });