web.dart 7.78 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../../artifacts.dart';
import '../../base/file_system.dart';
import '../../base/io.dart';
import '../../build_info.dart';
9
import '../../compile.dart';
10
import '../../dart/package_map.dart';
11
import '../../globals.dart' as globals;
12 13
import '../../project.dart';
import '../build_system.dart';
14
import '../depfile.dart';
15 16 17 18 19 20 21 22 23 24 25 26 27 28
import 'assets.dart';
import 'dart.dart';

/// Whether web builds should call the platform initialization logic.
const String kInitializePlatform = 'InitializePlatform';

/// Whether the application has web plugins.
const String kHasWebPlugins = 'HasWebPlugins';

/// An override for the dart2js build mode.
///
/// Valid values are O1 (lowest, profile default) to O4 (highest, release default).
const String kDart2jsOptimization = 'Dart2jsOptimization';

29
/// Generates an entry point for a web target.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
class WebEntrypointTarget extends Target {
  const WebEntrypointTarget();

  @override
  String get name => 'web_entrypoint';

  @override
  List<Target> get dependencies => const <Target>[];

  @override
  List<Source> get inputs => const <Source>[
    Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/web.dart'),
  ];

  @override
  List<Source> get outputs => const <Source>[
    Source.pattern('{BUILD_DIR}/main.dart'),
  ];

  @override
  Future<void> build(Environment environment) async {
    final String targetFile = environment.defines[kTargetFile];
    final bool shouldInitializePlatform = environment.defines[kInitializePlatform] == 'true';
    final bool hasPlugins = environment.defines[kHasWebPlugins] == 'true';
54
    final String importPath = globals.fs.path.absolute(targetFile);
55 56 57 58 59 60 61

    // Use the package uri mapper to find the correct package-scheme import path
    // for the user application. If the application has a mix of package-scheme
    // and relative imports for a library, then importing the entrypoint as a
    // file-scheme will cause said library to be recognized as two distinct
    // libraries. This can cause surprising behavior as types from that library
    // will be considered distinct from each other.
62 63 64 65 66 67
    final PackageUriMapper packageUriMapper = PackageUriMapper(
      importPath,
      PackageMap.globalPackagesPath,
      null,
      null,
    );
68 69

    // By construction, this will only be null if the .packages file does not
70 71 72
    // have an entry for the user's application or if the main file is
    // outside of the lib/ directory.
    final String mainImport = packageUriMapper.map(importPath)?.toString()
73
      ?? globals.fs.file(importPath).absolute.uri.toString();
74 75 76

    String contents;
    if (hasPlugins) {
77 78 79
      final String generatedPath = environment.projectDir
        .childDirectory('lib')
        .childFile('generated_plugin_registrant.dart')
80 81
        .absolute.path;
      final Uri generatedImport = packageUriMapper.map(generatedPath);
82 83 84 85 86
      contents = '''
import 'dart:ui' as ui;

import 'package:flutter_web_plugins/flutter_web_plugins.dart';

87 88
import '$generatedImport';
import '$mainImport' as entrypoint;
89 90 91 92 93 94 95 96 97 98 99 100 101

Future<void> main() async {
  registerPlugins(webPluginRegistry);
  if ($shouldInitializePlatform) {
    await ui.webOnlyInitializePlatform();
  }
  entrypoint.main();
}
''';
    } else {
      contents = '''
import 'dart:ui' as ui;

102
import '$mainImport' as entrypoint;
103 104 105 106 107 108 109 110 111 112 113 114 115 116

Future<void> main() async {
  if ($shouldInitializePlatform) {
    await ui.webOnlyInitializePlatform();
  }
  entrypoint.main();
}
''';
    }
    environment.buildDir.childFile('main.dart')
      ..writeAsStringSync(contents);
  }
}

117
/// Compiles a web entry point with dart2js.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
class Dart2JSTarget extends Target {
  const Dart2JSTarget();

  @override
  String get name => 'dart2js';

  @override
  List<Target> get dependencies => const <Target>[
    WebEntrypointTarget()
  ];

  @override
  List<Source> get inputs => const <Source>[
    Source.artifact(Artifact.flutterWebSdk),
    Source.artifact(Artifact.dart2jsSnapshot),
    Source.artifact(Artifact.engineDartBinary),
    Source.pattern('{BUILD_DIR}/main.dart'),
    Source.pattern('{PROJECT_DIR}/.packages'),
  ];

  @override
139 140 141 142 143
  List<Source> get outputs => const <Source>[];

  @override
  List<String> get depfiles => const <String>[
    'dart2js.d',
144 145 146 147 148 149
  ];

  @override
  Future<void> build(Environment environment) async {
    final String dart2jsOptimization = environment.defines[kDart2jsOptimization];
    final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
150
    final String specPath = globals.fs.path.join(globals.artifacts.getArtifactPath(Artifact.flutterWebSdk), 'libraries.json');
151 152 153
    final String packageFile = FlutterProject.fromDirectory(environment.projectDir).hasBuilders
      ? PackageMap.globalGeneratedPackagesPath
      : PackageMap.globalPackagesPath;
154
    final File outputFile = environment.buildDir.childFile('main.dart.js');
155

156 157 158
    final ProcessResult result = await globals.processManager.run(<String>[
      globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
      globals.artifacts.getArtifactPath(Artifact.dart2jsSnapshot),
159 160 161 162 163
      '--libraries-spec=$specPath',
      if (dart2jsOptimization != null)
        '-$dart2jsOptimization'
      else
        '-O4',
164 165
      if (buildMode == BuildMode.profile)
        '--no-minify',
166
      '-o',
167
      outputFile.path,
168 169 170 171 172
      '--packages=$packageFile',
      if (buildMode == BuildMode.profile)
        '-Ddart.vm.profile=true'
      else
        '-Ddart.vm.product=true',
173
      for (final String dartDefine in parseDartDefines(environment))
174
        '-D$dartDefine',
175 176 177 178 179
      environment.buildDir.childFile('main.dart').path,
    ]);
    if (result.exitCode != 0) {
      throw Exception(result.stdout + result.stderr);
    }
180 181 182
    final File dart2jsDeps = environment.buildDir
      .childFile('main.dart.js.deps');
    if (!dart2jsDeps.existsSync()) {
183
      globals.printError('Warning: dart2js did not produced expected deps list at '
184 185 186 187 188 189 190 191
        '${dart2jsDeps.path}');
      return;
    }
    final Depfile depfile = Depfile.parseDart2js(
      environment.buildDir.childFile('main.dart.js.deps'),
      outputFile,
    );
    depfile.writeToFile(environment.buildDir.childFile('dart2js.d'));
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
  }
}

/// Unpacks the dart2js compilation to a given output directory
class WebReleaseBundle extends Target {
  const WebReleaseBundle();

  @override
  String get name => 'web_release_bundle';

  @override
  List<Target> get dependencies => const <Target>[
    Dart2JSTarget(),
  ];

  @override
  List<Source> get inputs => const <Source>[
    Source.pattern('{BUILD_DIR}/main.dart.js'),
210
    Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
211 212 213 214 215 216 217
    Source.pattern('{PROJECT_DIR}/web/index.html'),
  ];

  @override
  List<Source> get outputs => const <Source>[
    Source.pattern('{OUTPUT_DIR}/main.dart.js'),
    Source.pattern('{OUTPUT_DIR}/index.html'),
218 219 220 221 222
  ];

  @override
  List<String> get depfiles => const <String>[
    'dart2js.d',
223 224 225 226
  ];

  @override
  Future<void> build(Environment environment) async {
227
    for (final File outputFile in environment.buildDir.listSync(recursive: true).whereType<File>()) {
228
      if (!globals.fs.path.basename(outputFile.path).contains('main.dart.js')) {
229 230 231
        continue;
      }
      outputFile.copySync(
232
        environment.outputDir.childFile(globals.fs.path.basename(outputFile.path)).path
233 234
      );
    }
235 236
    final Directory outputDirectory = environment.outputDir.childDirectory('assets');
    outputDirectory.createSync(recursive: true);
237 238 239
    environment.projectDir
      .childDirectory('web')
      .childFile('index.html')
240
      .copySync(globals.fs.path.join(environment.outputDir.path, 'index.html'));
241 242
    final Depfile depfile = await copyAssets(environment, environment.outputDir.childDirectory('assets'));
    depfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
243 244
  }
}