-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
In this issue, I wanted to analyze the network activity that happens during page load of a sample flutter web app.
Problem
Bigger apps typically have more fonts and/or assets (more requests), but the basic dependency tree between these requests looks something like this:
+--> canvaskit.js --> canvaskit.wasm
|
index.html -> flutter.js -> main.dart.js -+ +--> MaterialIcons-Regular.otf
| |
+--> FontManifest.json -+--> CupertinoIcons.ttf
|
+--> Roboto.ttf
This tree is not ideal, for 2 main reasons:
- We are loading too many things for a basic app!
- The tree is narrow and deep, i.e. leaf nodes have a long chain of dependencies before they can be loaded.
Better?
A better tree should be wider and shallower to get more parallelization and fewer dependencies:
+--> main.dart.js ---> canvaskit.js
|
-------------------\ +--> canvaskit.wasm
index.html \ |
(flutter.js) +---+--> MaterialIcons-Regular.otf
(FontManifest.json) / |
-------------------/ +--> CupertinoIcons.ttf
|
+--> Roboto.ttf
How?
The idea can be split into 2 parts:
1. Inlining
There's no reason to load flutter.js and FontManifest.json (and potentially others) from the network. They could be inlined into index.html so that they are available immediately and don't require an extra hop to the server.
Flutter generates the index.html file but we don't own it anymore after creation. The app developer owns the file and can change it as they see fit. This means we can't make any assumptions about the structure of the file, and we can't force-inline anything. The flutter tools should support inlining but the user should have full control.
One way to achieve this is to use placeholders, similar to $FLUTTER_BASE_HREF:
| <!-- | |
| If you are serving your web app in a path other than the root, change the | |
| href value below to reflect the base path you are serving from. | |
| The path provided below has to start and end with a slash "/" in order for | |
| it to work correctly. | |
| For more details: | |
| * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base | |
| This is a placeholder for base href that will be replaced by the value of | |
| the `--base-href` argument provided to `flutter build`. | |
| --> | |
| <base href="$FLUTTER_BASE_HREF"> |
We can define a set of placeholders that the user can put in. The flutter tool substitutes the placeholders at build-time. The placeholders may look something like this:
<script>$FLUTTER_JS_SCRIPT</script>They can be placed anywhere the user chooses. E.g:
<script>
const fontManifest = $$FLUTTER_FONT_MANIFEST; // This will be replaced with the actual json.
_flutter.loader.loadEntrypoint({
fontManifest: fontManifest,
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: async function(engineInitializer) {
const appRunner = await engineInitializer.initializeEngine({
fontManifest: fontManifest,
});
appRunner.runApp();
}
});
</script>2. Auto prefetching
Since flutter.js is now inlined in index.html, it runs immediately. It can fire requests for main.dart.js and canvaskit.wasm in parallel.
At the same time, it can also prefetch all the needed fonts since it has immediate access to the inlined FontManifest.json.
This ensures maximum parallelization. All the requests are sent immediately as index.html loads.
