[flutter_tools] Include flutter.js.map in web builds (#192257)
The prebuilt Flutter web loader ends with a reference to
`flutter.js.map`. The matching source map is already part of the Flutter
web SDK, but `WebBuiltInAssets` only copies `flutter.js` and CanvasKit
into `build/web`. As a result, browser developer tools request a file
that is missing from every web build, and tools such as error reporters
cannot resolve stack frames from the loader.
This change declares `flutter.js.map` as a built-in web output and
copies it alongside `flutter.js`.
The copy is intentionally independent of `--[no-]source-maps`. That
option controls source maps produced while compiling the application,
while `flutter.js` is a prebuilt SDK artifact that always references its
own prebuilt map.
Fixes #145111.
## Validation
- `./bin/dart --enable-asserts dev/bots/analyze.dart`
- `../../bin/dart test
test/general.shard/build_system/targets/web_test.dart` (626 tests)
- `bin/flutter-dev build web`
- incremental rebuild after removing `build/web/flutter.js.map`
- `bin/flutter-dev build web --source-maps`
- `bin/flutter-dev build web --wasm`
- verified the emitted map is byte-for-byte identical to the Flutter web
SDK artifact
No changes to `flutter/tests` are required.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
---------
Co-authored-by: Mouad Debbar <mdebbar@google.com>
diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart
index 64f6956..6dec585 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/web.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -1490,6 +1490,7 @@
@override
List<Source> get outputs => <Source>[
const Source.pattern('{BUILD_DIR}/flutter.js'),
+ const Source.pattern('{BUILD_DIR}/flutter.js.map'),
for (final File file in _canvasKitFiles)
Source.pattern('{BUILD_DIR}/canvaskit/${_filePathRelativeToCanvasKitDirectory(file)}'),
];
@@ -1506,15 +1507,15 @@
file.copySync(targetPath);
}
- // Write the flutter.js file
- final String flutterJsOut = fileSystem.path.join(environment.outputDir.path, 'flutter.js');
- final File flutterJsFile = fileSystem.file(
- fileSystem.path.join(
- globals.artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path,
- 'flutter.js',
- ),
+ // Write the Flutter loader and its source map.
+ final Directory flutterJsDirectory = fileSystem.directory(
+ globals.artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path,
);
- flutterJsFile.copySync(flutterJsOut);
+ for (final fileName in <String>['flutter.js', 'flutter.js.map']) {
+ final File sourceFile = flutterJsDirectory.childFile(fileName);
+ final File targetFile = environment.outputDir.childFile(fileName);
+ sourceFile.copySync(targetFile.path);
+ }
}
}
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
index b34c78d..6c8e531 100644
--- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart
@@ -85,6 +85,9 @@
.file('bin/cache/flutter_web_sdk/flutter_js/flutter.js')
.createSync(recursive: true);
globals.fs
+ .file('bin/cache/flutter_web_sdk/flutter_js/flutter.js.map')
+ .createSync(recursive: true);
+ globals.fs
.file('engine/src/flutter/txt/third_party/fonts/Roboto-Regular.ttf')
.createSync(recursive: true);
@@ -1731,6 +1734,25 @@
);
test(
+ 'WebBuiltInAssets declares and copies the Flutter loader source map',
+ () => testbed.run(() async {
+ final File flutterJsMapInput = globals.fs.file(
+ 'bin/cache/flutter_web_sdk/flutter_js/flutter.js.map',
+ )..createSync(recursive: true);
+ flutterJsMapInput.writeAsStringSync('source map', flush: true);
+ globals.fs.directory('bin/cache/flutter_web_sdk/canvaskit').createSync(recursive: true);
+
+ final target = WebBuiltInAssets(globals.fs);
+ expect(target.outputs, contains(const Source.pattern('{BUILD_DIR}/flutter.js.map')));
+ await target.build(environment);
+
+ final File flutterJsMapOutput = environment.outputDir.childFile('flutter.js.map');
+ expect(flutterJsMapOutput, exists);
+ expect(flutterJsMapOutput.readAsStringSync(), 'source map');
+ }),
+ );
+
+ test(
'WebBuiltInAssets copies over canvaskit again if the web sdk changes',
() => testbed.run(() async {
final File canvasKitInput = globals.fs.file(