// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/template.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/depfile.dart'; import 'package:flutter_tools/src/build_system/targets/web.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/isolated/mustache_template.dart'; import 'package:flutter_tools/src/web/compile.dart'; import 'package:flutter_tools/src/web/file_generators/flutter_service_worker_js.dart'; import 'package:flutter_tools/src/web_template.dart'; import '../../../src/common.dart'; import '../../../src/fake_process_manager.dart'; import '../../../src/testbed.dart'; const List<String> _kDart2jsLinuxArgs = <String>[ 'Artifact.engineDartBinary.TargetPlatform.web_javascript', '--disable-dart-dev', 'Artifact.dart2jsSnapshot.TargetPlatform.web_javascript', '--platform-binaries=HostArtifact.webPlatformKernelFolder', '--invoker=flutter_tool', ]; const List<String> _kDart2WasmLinuxArgs = <String> [ 'Artifact.engineDartBinary.TargetPlatform.web_javascript', 'compile', 'wasm', '--packages=.dart_tool/package_config.json', '--extra-compiler-option=--dart-sdk=Artifact.engineDartSdkPath.TargetPlatform.web_javascript', '--extra-compiler-option=--platform=HostArtifact.webPlatformKernelFolder/dart2wasm_platform.dill', '--extra-compiler-option=--delete-tostring-package-uri=dart:ui', '--extra-compiler-option=--delete-tostring-package-uri=package:flutter', ]; void main() { late Testbed testbed; late Environment environment; late FakeProcessManager processManager; final Platform linux = FakePlatform( environment: <String, String>{}, ); final Platform windows = FakePlatform( operatingSystem: 'windows', environment: <String, String>{}, ); setUp(() { testbed = Testbed(setup: () { globals.fs.file('.packages') ..createSync(recursive: true) ..writeAsStringSync('foo:foo/lib/\n'); globals.fs.currentDirectory.childDirectory('bar').createSync(); processManager = FakeProcessManager.empty(); globals.fs.file('bin/cache/flutter_web_sdk/flutter_js/flutter.js') .createSync(recursive: true); environment = Environment.test( globals.fs.currentDirectory, projectDir: globals.fs.currentDirectory.childDirectory('foo'), outputDir: globals.fs.currentDirectory.childDirectory('bar'), defines: <String, String>{ kTargetFile: globals.fs.path.join('foo', 'lib', 'main.dart'), }, artifacts: Artifacts.test(), processManager: processManager, logger: globals.logger, fileSystem: globals.fs, ); environment.buildDir.createSync(recursive: true); }, overrides: <Type, Generator>{ Platform: () => linux, }); }); test('WebEntrypointTarget generates an entrypoint with plugins and init platform', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'true'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Plugins expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;")); expect(generated, contains('pluginRegistrant.registerPlugins();')); // Import. expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('version.json is created after release build', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('index.html') .createSync(recursive: true); environment.buildDir.childFile('main.dart.js').createSync(); await WebReleaseBundle(<WebCompilerConfig>[ const JsCompilerConfig() ]).build(environment); expect(environment.outputDir.childFile('version.json'), exists); })); test('override version values', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[kBuildName] = '2.0.0'; environment.defines[kBuildNumber] = '22'; final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('index.html').createSync(recursive: true); environment.buildDir.childFile('main.dart.js').createSync(); await WebReleaseBundle(<WebCompilerConfig>[ const JsCompilerConfig() ]).build(environment); final String versionFile = environment.outputDir .childFile('version.json') .readAsStringSync(); expect(versionFile, contains('"version":"2.0.0"')); expect(versionFile, contains('"build_number":"22"')); })); test('Base href is created in index.html with given base-href after release build', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[kBaseHref] = '/basehreftest/'; final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('index.html').createSync(recursive: true); webResources.childFile('index.html').writeAsStringSync(''' <!DOCTYPE html><html><base href="$kBaseHrefPlaceholder"><head></head></html> '''); environment.buildDir.childFile('main.dart.js').createSync(); await WebTemplatedFiles('buildConfig').build(environment); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); })); test('null base href does not override existing base href in index.html', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('index.html').createSync(recursive: true); webResources.childFile('index.html').writeAsStringSync(''' <!DOCTYPE html><html><head><base href='/basehreftest/'></head></html> '''); environment.buildDir.childFile('main.dart.js').createSync(); await WebTemplatedFiles('build config').build(environment); expect(environment.outputDir.childFile('index.html').readAsStringSync(), contains('/basehreftest/')); })); test('WebReleaseBundle copies dart2js output and resource files to output directory', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('foo.txt') ..createSync(recursive: true) ..writeAsStringSync('A'); environment.buildDir.childFile('main.dart.js').createSync(); environment.buildDir.childFile('main.dart.js.map').createSync(); await WebReleaseBundle(<WebCompilerConfig>[ const JsCompilerConfig() ]).build(environment); expect(environment.outputDir.childFile('foo.txt') .readAsStringSync(), 'A'); expect(environment.outputDir.childFile('main.dart.js') .existsSync(), true); expect(environment.outputDir.childFile('main.dart.js.map') .existsSync(), true); expect(environment.outputDir.childDirectory('assets') .childFile('AssetManifest.bin.json').existsSync(), true); // Update to arbitrary resource file triggers rebuild. webResources.childFile('foo.txt').writeAsStringSync('B'); await WebReleaseBundle(<WebCompilerConfig>[ const JsCompilerConfig() ]).build(environment); expect(environment.outputDir.childFile('foo.txt') .readAsStringSync(), 'B'); })); test('WebReleaseBundle copies over output files when they change', () => testbed.run(() async { final Directory webResources = environment.projectDir.childDirectory('web'); webResources.childFile('foo.txt') ..createSync(recursive: true) ..writeAsStringSync('A'); environment.buildDir.childFile('main.dart.wasm')..createSync()..writeAsStringSync('old wasm'); environment.buildDir.childFile('main.dart.mjs')..createSync()..writeAsStringSync('old mjs'); await WebReleaseBundle(<WebCompilerConfig>[ const WasmCompilerConfig() ]).build(environment); expect(environment.outputDir.childFile('main.dart.wasm') .readAsStringSync(), 'old wasm'); expect(environment.outputDir.childFile('main.dart.mjs') .readAsStringSync(), 'old mjs'); environment.buildDir.childFile('main.dart.wasm')..createSync()..writeAsStringSync('new wasm'); environment.buildDir.childFile('main.dart.mjs')..createSync()..writeAsStringSync('new mjs'); await WebReleaseBundle(<WebCompilerConfig>[ const WasmCompilerConfig() ]).build(environment); expect(environment.outputDir.childFile('main.dart.wasm') .readAsStringSync(), 'new wasm'); expect(environment.outputDir.childFile('main.dart.mjs') .readAsStringSync(), 'new mjs'); })); test('WebEntrypointTarget generates an entrypoint for a file outside of main', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('other', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Import. expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;")); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('WebEntrypointTarget generates a plugin registrant for a file outside of main', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('other', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'true'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Import. expect(generated, contains("import 'file:///other/lib/main.dart' as entrypoint;")); expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;")); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('WebEntrypointTarget generates an entrypoint with plugins and init platform on windows', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'true'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Plugins expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;")); expect(generated, contains('pluginRegistrant.registerPlugins();')); // Import. expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ Platform: () => windows, TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('WebEntrypointTarget generates an entrypoint without plugins and init platform', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'false'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Plugins (the generated file is a noop) expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;")); expect(generated, contains('pluginRegistrant.registerPlugins();')); // Import. expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('WebEntrypointTarget generates an entrypoint with a language version', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('// @dart=2.8\nvoid main() {}'); environment.defines[kTargetFile] = mainFile.path; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Language version expect(generated, contains('// @dart=2.8')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('WebEntrypointTarget generates an entrypoint with a language version from a package config', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); globals.fs.file(globals.fs.path.join('pubspec.yaml')) .writeAsStringSync('name: foo\n'); environment.defines[kTargetFile] = mainFile.path; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Language version expect(generated, contains('// @dart=2.7')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('WebEntrypointTarget generates an entrypoint without plugins and without init platform', () => testbed.run(() async { final File mainFile = globals.fs.file(globals.fs.path.join('foo', 'lib', 'main.dart')) ..createSync(recursive: true) ..writeAsStringSync('void main() {}'); environment.defines[kTargetFile] = mainFile.path; environment.defines[kHasWebPlugins] = 'false'; await const WebEntrypointTarget().build(environment); final String generated = environment.buildDir.childFile('main.dart').readAsStringSync(); // Plugins expect(generated, contains("import 'web_plugin_registrant.dart' as pluginRegistrant;")); expect(generated, contains('pluginRegistrant.registerPlugins();')); // Import. expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), })); test('Dart2JSTarget calls dart2js with expected args with csp', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[JsCompilerConfig.kCspMode] = 'true'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', '--csp', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( csp: true, sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget ignores frontend server starter path option when calling dart2js', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[kFrontendServerStarterPath] = 'path/to/frontend_server_starter.dart'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args with enabled experiment', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[kExtraFrontEndOptions] = '--enable-experiment=non-nullable'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '--enable-experiment=non-nullable', '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '--enable-experiment=non-nullable', '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args in profile mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args in release mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args in release mode with native null assertions', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[JsCompilerConfig.kNativeNullAssertions] = 'true'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--native-null-assertions', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--native-null-assertions', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( nativeNullAssertions: true, sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args in release with dart2js optimization override', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O3', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( optimizationLevel: 3, sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget produces expected depfile', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ], onRun: (_) { environment.buildDir.childFile('app.dill.deps') .writeAsStringSync('file:///a.dart'); }, )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( sourceMaps: false, ) ).build(environment); expect(environment.buildDir.childFile('dart2js.d'), exists); final Depfile depfile = environment.depFileService.parse(environment.buildDir.childFile('dart2js.d')); expect(depfile.inputs.single.path, globals.fs.path.absolute('a.dart')); expect(depfile.outputs.single.path, environment.buildDir.childFile('main.dart.js').absolute.path); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with Dart defines in release mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[kDartDefines] = encodeDartDefines(<String>['FOO=bar', 'BAZ=qux']); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFOO=bar', '-DBAZ=qux', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFOO=bar', '-DBAZ=qux', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget can enable source maps', () => testbed.run(() async { environment.defines[kBuildMode] = 'release'; environment.defines[JsCompilerConfig.kSourceMapsEnabled] = 'true'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.product=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig() ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with Dart defines in profile mode', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[kDartDefines] = encodeDartDefines(<String>['FOO=bar', 'BAZ=qux']); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFOO=bar', '-DBAZ=qux', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFOO=bar', '-DBAZ=qux', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args with dump-info', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[JsCompilerConfig.kDart2jsDumpInfo] = 'true'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', '--dump-info', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( dumpInfo: true, sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); test('Dart2JSTarget calls dart2js with expected args with no-frequency-based-minification', () => testbed.run(() async { environment.defines[kBuildMode] = 'profile'; environment.defines[JsCompilerConfig.kDart2jsNoFrequencyBasedMinification] = 'true'; processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-source-maps', '-o', environment.buildDir.childFile('app.dill').absolute.path, '--packages=.dart_tool/package_config.json', '--cfe-only', environment.buildDir.childFile('main.dart').absolute.path, ] )); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2jsLinuxArgs, '-Ddart.vm.profile=true', '-DFLUTTER_WEB_AUTO_DETECT=true', '--no-minify', '--no-source-maps', '-O4', '--no-frequency-based-minification', '-o', environment.buildDir.childFile('main.dart.js').absolute.path, environment.buildDir.childFile('app.dill').absolute.path, ] )); await Dart2JSTarget( const JsCompilerConfig( noFrequencyBasedMinification: true, sourceMaps: false, ) ).build(environment); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); for (final WebRendererMode renderer in <WebRendererMode>[WebRendererMode.canvaskit, WebRendererMode.skwasm]) { for (int level = 1; level <= 4; level++) { for (final bool strip in <bool>[true, false]) { for (final List<String> defines in const <List<String>>[<String>[], <String>['FOO=bar', 'BAZ=qux']]) { for (final String buildMode in const <String>['profile', 'release']) { test('Dart2WasmTarget invokes dart2wasm with renderer=$renderer, -O$level, stripping=$strip, defines=$defines, modeMode=$buildMode', () => testbed.run(() async { environment.defines[kBuildMode] = buildMode; environment.defines[kDartDefines] = encodeDartDefines(defines); final File depFile = environment.buildDir.childFile('dart2wasm.d'); final File outputJsFile = environment.buildDir.childFile('main.dart.mjs'); processManager.addCommand(FakeCommand( command: <String>[ ..._kDart2WasmLinuxArgs, if (renderer == WebRendererMode.skwasm) ...<String>[ '--extra-compiler-option=--import-shared-memory', '--extra-compiler-option=--shared-memory-max-pages=32768', ], '-Ddart.vm.${buildMode == 'release' ? 'product' : 'profile' }=true', ...defines.map((String define) => '-D$define'), if (renderer == WebRendererMode.skwasm) ...<String>[ '-DFLUTTER_WEB_AUTO_DETECT=false', '-DFLUTTER_WEB_USE_SKIA=false', '-DFLUTTER_WEB_USE_SKWASM=true', ], if (renderer == WebRendererMode.canvaskit) ...<String>[ '-DFLUTTER_WEB_AUTO_DETECT=false', '-DFLUTTER_WEB_USE_SKIA=true', ], '--extra-compiler-option=--depfile=${depFile.absolute.path}', '-O$level', if (strip && buildMode == 'release') '--no-name-section' else '--name-section', '-o', environment.buildDir.childFile('main.dart.wasm').absolute.path, environment.buildDir.childFile('main.dart').absolute.path, ], onRun: (_) => outputJsFile..createSync()..writeAsStringSync('foo')) ); await Dart2WasmTarget( WasmCompilerConfig( optimizationLevel: level, stripWasm: strip, renderer: renderer, ) ).build(environment); expect(outputJsFile.existsSync(), isTrue); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, })); } } } } } test('Generated service worker is empty with none-strategy', () => testbed.run(() { final String fileGeneratorsPath = environment.artifacts.getArtifactPath(Artifact.flutterToolsFileGenerators); final String result = generateServiceWorker( fileGeneratorsPath, <String, String>{'/foo': 'abcd'}, <String>[], serviceWorkerStrategy: ServiceWorkerStrategy.none, ); expect(result, ''); })); test('Generated service worker correctly inlines file hashes', () => testbed.run(() { final String fileGeneratorsPath = environment.artifacts.getArtifactPath(Artifact.flutterToolsFileGenerators); final String result = generateServiceWorker( fileGeneratorsPath, <String, String>{'/foo': 'abcd'}, <String>[], serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst, ); expect(result, contains('{"/foo": "abcd"};')); })); test('Generated service worker includes core files', () => testbed.run(() { final String fileGeneratorsPath = environment.artifacts.getArtifactPath(Artifact.flutterToolsFileGenerators); final String result = generateServiceWorker( fileGeneratorsPath, <String, String>{'/foo': 'abcd'}, <String>['foo', 'bar'], serviceWorkerStrategy: ServiceWorkerStrategy.offlineFirst, ); expect(result, contains('"foo",\n"bar"')); })); test('WebServiceWorker generates a service_worker for a web resource folder', () => testbed.run(() async { environment.outputDir.childDirectory('a').childFile('a.txt') ..createSync(recursive: true) ..writeAsStringSync('A'); await WebServiceWorker(globals.fs, <WebCompilerConfig>[ const JsCompilerConfig() ]).build(environment); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); // Contains file hash. expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), contains('"a/a.txt": "7fc56270e7a70fa81a5935b72eacbe29"')); expect(environment.buildDir.childFile('service_worker.d'), exists); // Depends on resource file. expect(environment.buildDir.childFile('service_worker.d').readAsStringSync(), contains('a/a.txt')); // Does NOT contain NOTICES expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), isNot(contains('NOTICES'))); })); test('WebServiceWorker contains baseUrl cache', () => testbed.run(() async { environment.outputDir .childFile('index.html') .createSync(recursive: true); environment.outputDir .childFile('assets/index.html') ..createSync(recursive: true) ..writeAsStringSync('A'); await WebServiceWorker(globals.fs, <WebCompilerConfig>[ const JsCompilerConfig() ]).build(environment); expect(environment.outputDir.childFile('flutter_service_worker.js'), exists); // Contains the same file hash for both `/` and the root index.html file. const String rootIndexHash = 'd41d8cd98f00b204e9800998ecf8427e'; expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), contains('"/": "$rootIndexHash"')); expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), contains('"index.html": "$rootIndexHash"')); // Make sure `assets/index.html` has a different hash than `index.html`. expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), contains('"assets/index.html": "7fc56270e7a70fa81a5935b72eacbe29"')); expect(environment.buildDir.childFile('service_worker.d'), exists); })); test('WebServiceWorker does not cache source maps', () => testbed.run(() async { environment.outputDir .childFile('main.dart.js') .createSync(recursive: true); environment.outputDir .childFile('main.dart.js.map') .createSync(recursive: true); await WebServiceWorker(globals.fs, <WebCompilerConfig>[ const JsCompilerConfig() ]).build(environment); // No caching of source maps. expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), isNot(contains('"main.dart.js.map"'))); // Expected twice, once for RESOURCES and once for CORE. expect(environment.outputDir.childFile('flutter_service_worker.js').readAsStringSync(), contains('"main.dart.js"')); })); test('WebBuiltInAssets copies over canvaskit again if the web sdk changes', () => testbed.run(() async { final File canvasKitInput = globals.fs.file('bin/cache/flutter_web_sdk/canvaskit/canvaskit.wasm') ..createSync(recursive: true); canvasKitInput.writeAsStringSync('foo', flush: true); await WebBuiltInAssets(globals.fs).build(environment); final File canvasKitOutputBefore = environment.outputDir.childDirectory('canvaskit') .childFile('canvaskit.wasm'); expect(canvasKitOutputBefore.existsSync(), true); expect(canvasKitOutputBefore.readAsStringSync(), 'foo'); canvasKitInput.writeAsStringSync('bar', flush: true); await WebBuiltInAssets(globals.fs).build(environment); final File canvasKitOutputAfter = environment.outputDir.childDirectory('canvaskit') .childFile('canvaskit.wasm'); expect(canvasKitOutputAfter.existsSync(), true); expect(canvasKitOutputAfter.readAsStringSync(), 'bar'); })); }