Unverified Commit 89ebd700 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Support deferred imports on profile/release builds of Flutter Web (#41222)

parent 4f3199f8
......@@ -11,6 +11,7 @@ import 'dart:isolate';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:archive/archive.dart';
import 'package:build/build.dart';
import 'package:build_config/build_config.dart';
import 'package:build_modules/build_modules.dart';
......@@ -26,6 +27,7 @@ import 'package:build_web_compilers/build_web_compilers.dart';
import 'package:build_web_compilers/builders.dart';
import 'package:build_web_compilers/src/dev_compiler_bootstrap.dart';
import 'package:crypto/crypto.dart';
import 'package:glob/glob.dart';
import 'package:path/path.dart' as path; // ignore: package_path_import
import 'package:scratch_space/scratch_space.dart';
import 'package:test_core/backend.dart';
......@@ -461,8 +463,32 @@ Future<void> bootstrapDart2Js(BuildStep buildStep, String flutterWebSdk, bool pr
final AssetId jsOutputId = dartEntrypointId.changeExtension(jsEntrypointExtension);
final File jsOutputFile = scratchSpace.fileFor(jsOutputId);
if (result.succeeded && jsOutputFile.existsSync()) {
log.info(result.output);
// Explicitly write out the original js file and sourcemap.
final String rootDir = path.dirname(jsOutputFile.path);
final String dartFile = path.basename(dartEntrypointId.path);
final Glob fileGlob = Glob('$dartFile.js*');
final Archive archive = Archive();
await for (FileSystemEntity jsFile in fileGlob.list(root: rootDir)) {
if (jsFile.path.endsWith(jsEntrypointExtension) ||
jsFile.path.endsWith(jsEntrypointSourceMapExtension)) {
// These are explicitly output, and are not part of the archive.
continue;
}
if (jsFile is File) {
final String fileName = path.relative(jsFile.path, from: rootDir);
final FileStat fileStats = jsFile.statSync();
archive.addFile(
ArchiveFile(fileName, fileStats.size, await jsFile.readAsBytes())
..mode = fileStats.mode
..lastModTime = fileStats.modified.millisecondsSinceEpoch);
}
}
if (archive.isNotEmpty) {
final AssetId archiveId = dartEntrypointId.changeExtension(jsEntrypointArchiveExtension);
await buildStep.writeAsBytes(archiveId, TarEncoder().encode(archive));
}
// Explicitly write out the original js file and sourcemap - we can't output
// these as part of the archive because they already have asset nodes.
await scratchSpace.copyOutput(jsOutputId, buildStep);
final AssetId jsSourceMapId =
dartEntrypointId.changeExtension(jsEntrypointSourceMapExtension);
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:archive/archive.dart';
import 'package:meta/meta.dart';
import '../asset.dart';
......@@ -48,9 +49,23 @@ Future<void> buildWeb(FlutterProject flutterProject, String target, BuildInfo bu
flutterProject.manifest.appName,
'${fs.path.withoutExtension(target)}_web_entrypoint.dart.js',
);
// Check for deferred import outputs.
final File dart2jsArchive = fs.file(fs.path.join(
flutterProject.dartTool.path,
'build',
'flutter_web',
'${flutterProject.manifest.appName}',
'${fs.path.withoutExtension(target)}_web_entrypoint.dart.js.tar.gz'),
);
fs.file(outputPath).copySync(fs.path.join(outputDir.path, 'main.dart.js'));
fs.file('$outputPath.map').copySync(fs.path.join(outputDir.path, 'main.dart.js.map'));
flutterProject.web.indexFile.copySync(fs.path.join(outputDir.path, 'index.html'));
if (dart2jsArchive.existsSync()) {
final Archive archive = TarDecoder().decodeBytes(dart2jsArchive.readAsBytesSync());
for (ArchiveFile file in archive.files) {
outputDir.childFile(file.name).writeAsBytesSync(file.content);
}
}
}
} catch (err) {
printError(err.toString());
......
......@@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:archive/archive.dart';
import 'package:args/command_runner.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
......@@ -25,6 +29,7 @@ void main() {
MockWebCompilationProxy mockWebCompilationProxy;
Testbed testbed;
MockPlatform mockPlatform;
bool addArchive = false;
setUpAll(() {
Cache.flutterRoot = '';
......@@ -32,6 +37,7 @@ void main() {
});
setUp(() {
addArchive = false;
mockWebCompilationProxy = MockWebCompilationProxy();
testbed = Testbed(setup: () {
fs.file('pubspec.yaml')
......@@ -46,9 +52,18 @@ void main() {
mode: anyNamed('mode'),
initializePlatform: anyNamed('initializePlatform'),
)).thenAnswer((Invocation invocation) {
final String path = fs.path.join('.dart_tool', 'build', 'flutter_web', 'foo', 'lib', 'main_web_entrypoint.dart.js');
final String prefix = fs.path.join('.dart_tool', 'build', 'flutter_web', 'foo', 'lib');
final String path = fs.path.join(prefix, 'main_web_entrypoint.dart.js');
fs.file(path).createSync(recursive: true);
fs.file('$path.map').createSync();
if (addArchive) {
final List<int> bytes = utf8.encode('void main() {}');
final TarEncoder encoder = TarEncoder();
final Archive archive = Archive()
..addFile(ArchiveFile.noCompress('main_web_entrypoint.1.dart.js', bytes.length, bytes));
fs.file(fs.path.join(prefix, 'main_web_entrypoint.dart.js.tar.gz'))
..writeAsBytes(encoder.encode(archive));
}
return Future<bool>.value(true);
});
}, overrides: <Type, Generator>{
......@@ -59,6 +74,19 @@ void main() {
});
});
test('Copies generated part files out of build directory', () => testbed.run(() async {
addArchive = true;
await buildWeb(
FlutterProject.current(),
fs.path.join('lib', 'main.dart'),
BuildInfo.release,
false,
);
expect(fs.file(fs.path.join('build', 'web', 'main_web_entrypoint.1.dart.js')), exists);
expect(fs.file(fs.path.join('build', 'web', 'main.dart.js')), exists);
}));
test('Refuses to build for web when missing index.html', () => testbed.run(() async {
fs.file(fs.path.join('web', 'index.html')).deleteSync();
......@@ -82,16 +110,6 @@ void main() {
expect(await runner.run(), 1);
}));
test('Can build for web', () => testbed.run(() async {
await buildWeb(
FlutterProject.current(),
fs.path.join('lib', 'main.dart'),
BuildInfo.debug,
false,
);
}));
test('Refuses to build a debug build for web', () => testbed.run(() async {
final CommandRunner<void> runner = createTestCommandRunner(BuildCommand());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment