| // 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(); |
| environment.buildDir.childFile('main.dart.js_1.part.js').createSync(); |
| environment.buildDir.childFile('main.dart.js_1.part.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.childFile('main.dart.js_1.part.js') |
| .existsSync(), true); |
| expect(environment.outputDir.childFile('main.dart.js_1.part.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('Dart2JSTarget has unique build keys for compiler configurations', () { |
| const List<JsCompilerConfig> testConfigs = <JsCompilerConfig>[ |
| // Default values |
| JsCompilerConfig(), |
| |
| // Each individual property being made non-default |
| JsCompilerConfig(csp: true), |
| JsCompilerConfig(dumpInfo: true), |
| JsCompilerConfig(nativeNullAssertions: true), |
| JsCompilerConfig(optimizationLevel: 0), |
| JsCompilerConfig(noFrequencyBasedMinification: true), |
| JsCompilerConfig(sourceMaps: false), |
| JsCompilerConfig(renderer: WebRendererMode.canvaskit), |
| |
| // All properties non-default |
| JsCompilerConfig( |
| csp: true, |
| dumpInfo: true, |
| nativeNullAssertions: true, |
| optimizationLevel: 0, |
| noFrequencyBasedMinification: true, |
| sourceMaps: false, |
| renderer: WebRendererMode.canvaskit, |
| ), |
| ]; |
| |
| final Iterable<String> buildKeys = testConfigs.map((JsCompilerConfig config) { |
| final Dart2JSTarget target = Dart2JSTarget(config); |
| return target.buildKey; |
| }); |
| |
| // Make sure all the build keys are unique. |
| expect(buildKeys.toSet().length, buildKeys.length); |
| }); |
| |
| test('Dart2Wasm has unique build keys for compiler configurations', () { |
| const List<WasmCompilerConfig> testConfigs = <WasmCompilerConfig>[ |
| // Default values |
| WasmCompilerConfig(), |
| |
| // Each individual property being made non-default |
| WasmCompilerConfig(optimizationLevel: 0), |
| WasmCompilerConfig(renderer: WebRendererMode.canvaskit), |
| WasmCompilerConfig(stripWasm: false), |
| |
| // All properties non-default |
| WasmCompilerConfig( |
| optimizationLevel: 0, |
| stripWasm: false, |
| renderer: WebRendererMode.canvaskit, |
| ), |
| ]; |
| |
| final Iterable<String> buildKeys = testConfigs.map((WasmCompilerConfig config) { |
| final Dart2WasmTarget target = Dart2WasmTarget(config); |
| return target.buildKey; |
| }); |
| |
| // Make sure all the build keys are unique. |
| expect(buildKeys.toSet().length, buildKeys.length); |
| }); |
| |
| 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'); |
| })); |
| } |