1
0
mirror of https://github.com/flutter/samples.git synced 2026-03-25 22:01:51 +00:00

Add web startup analyzer to material 3 demo (#2144)

This adds a tool to measure web app startup for the Material 3 demo.

Demo:
- [Example app](https://flutter-web-perf-experiments.web.app/)
- [Material
3](https://flutter-web-perf-experiments--material3-vswzldcy.web.app/)
(open console)

---------

Co-authored-by: Brett Morgan <brett.morgan@gmail.com>
Co-authored-by: Kevin Moore <kevmoo@google.com>
This commit is contained in:
John Ryan
2024-01-29 13:24:56 -08:00
committed by GitHub
parent dc37f37b5c
commit d96bb336b6
26 changed files with 670 additions and 17 deletions

View File

@@ -0,0 +1,43 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "f5fb61b953a631f47191124a31169701911ee1f4"
channel: "main"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
- platform: android
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
- platform: ios
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
- platform: linux
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
- platform: macos
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
- platform: web
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
- platform: windows
create_revision: f5fb61b953a631f47191124a31169701911ee1f4
base_revision: f5fb61b953a631f47191124a31169701911ee1f4
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,3 @@
include: package:flutter_lints/flutter.yaml
linter:
rules:

View File

@@ -0,0 +1,152 @@
// Copyright 2021 The Flutter team. All rights reserved.
// 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:flutter/material.dart';
import 'package:web_startup_analyzer/web_startup_analyzer.dart';
main() async {
var analyzer = WebStartupAnalyzer(additionalFrameCount: 10);
print(json.encode(analyzer.startupTiming));
analyzer.onFirstFrame.addListener(() {
print(json.encode({'firstFrame': analyzer.onFirstFrame.value}));
});
analyzer.onFirstPaint.addListener(() {
print(json.encode({
'firstPaint': analyzer.onFirstPaint.value?.$1,
'firstContentfulPaint': analyzer.onFirstPaint.value?.$2,
}));
});
analyzer.onAdditionalFrames.addListener(() {
print(json.encode({
'additionalFrames': analyzer.onAdditionalFrames.value,
}));
});
runApp(
WebStartupAnalyzerSample(
analyzer: analyzer,
),
);
}
class WebStartupAnalyzerSample extends StatelessWidget {
final WebStartupAnalyzer analyzer;
const WebStartupAnalyzerSample({super.key, required this.analyzer});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter web app timing',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green.shade100),
useMaterial3: true,
),
home: WebStartupAnalyzerScreen(analyzer: analyzer),
);
}
}
class WebStartupAnalyzerScreen extends StatefulWidget {
final WebStartupAnalyzer analyzer;
const WebStartupAnalyzerScreen({super.key, required this.analyzer});
@override
State<WebStartupAnalyzerScreen> createState() =>
_WebStartupAnalyzerScreenState();
}
class _WebStartupAnalyzerScreenState extends State<WebStartupAnalyzerScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber.shade50,
body: Align(
alignment: Alignment.topCenter,
child: Container(
margin: const EdgeInsets.all(8.0),
constraints: const BoxConstraints(maxWidth: 400),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
),
child: ListenableBuilder(
listenable: widget.analyzer.onChange,
builder: (BuildContext context, child) {
return ListView(
shrinkWrap: true,
children: [
TimingWidget(
name: 'DCL',
timingMs: widget.analyzer.domContentLoaded,
),
TimingWidget(
name: 'Load entrypoint',
timingMs: widget.analyzer.loadEntrypoint,
),
TimingWidget(
name: 'Initialize engine',
timingMs: widget.analyzer.initializeEngine,
),
TimingWidget(
name: 'Run app',
timingMs: widget.analyzer.appRunnerRunApp,
),
if (widget.analyzer.firstFrame != null)
TimingWidget(
name: 'First frame',
timingMs: widget.analyzer.firstFrame!,
),
if (widget.analyzer.firstPaint != null)
TimingWidget(
name: 'First paint',
timingMs: widget.analyzer.firstPaint!),
if (widget.analyzer.firstContentfulPaint != null)
TimingWidget(
name: 'First contentful paint',
timingMs: widget.analyzer.firstContentfulPaint!),
if (widget.analyzer.additionalFrames != null) ...[
for (var i in widget.analyzer.additionalFrames!)
TimingWidget(name: 'Frame', timingMs: i.toDouble()),
] else
TextButton(
child: const Text('Trigger frames'),
onPressed: () {},
),
],
);
},
),
),
),
);
}
}
class TimingWidget extends StatelessWidget {
final String name;
final double timingMs;
const TimingWidget({
super.key,
required this.name,
required this.timingMs,
});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(
name,
style: const TextStyle(fontSize: 18),
overflow: TextOverflow.ellipsis,
),
trailing: Text(
'${timingMs.truncate()}ms',
style: const TextStyle(fontSize: 18),
),
);
}
}

View File

@@ -0,0 +1,18 @@
name: example
description: "flutter_web_startup_analyzer example"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=3.4.0-16.0.dev <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
web_startup_analyzer:
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="web_startup_analyzer example">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<link rel="icon" type="image/png" href="favicon.png"/>
<title>web_perf_metrics example</title>
<link rel="manifest" href="manifest.json">
<script>
const serviceWorkerVersion = null;
</script>
<script src="flutter.js" defer></script>
</head>
<body>
<script type="text/javascript" src="assets/packages/web_startup_analyzer/lib/web_startup_analyzer.js"></script>
<script>
var flutterWebStartupAnalyzer = new FlutterWebStartupAnalyzer();
var analyzer = flutterWebStartupAnalyzer;
window.addEventListener('load', function(ev) {
analyzer.markStart("loadEntrypoint");
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
analyzer.markFinished("loadEntrypoint");
analyzer.markStart("initializeEngine");
engineInitializer.initializeEngine().then(function(appRunner) {
analyzer.markFinished("initializeEngine");
analyzer.markStart("appRunnerRunApp");
appRunner.runApp();
});
}
});
});
</script>
</body>
</html>

View File

@@ -0,0 +1,35 @@
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}