mirror of
https://github.com/flutter/samples.git
synced 2026-03-26 06:11:45 +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:
43
web/_packages/web_startup_analyzer/example/.gitignore
vendored
Normal file
43
web/_packages/web_startup_analyzer/example/.gitignore
vendored
Normal 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
|
||||
45
web/_packages/web_startup_analyzer/example/.metadata
Normal file
45
web/_packages/web_startup_analyzer/example/.metadata
Normal 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'
|
||||
@@ -0,0 +1,3 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
linter:
|
||||
rules:
|
||||
152
web/_packages/web_startup_analyzer/example/lib/main.dart
Normal file
152
web/_packages/web_startup_analyzer/example/lib/main.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
web/_packages/web_startup_analyzer/example/pubspec.yaml
Normal file
18
web/_packages/web_startup_analyzer/example/pubspec.yaml
Normal 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
|
||||
BIN
web/_packages/web_startup_analyzer/example/web/favicon.png
Normal file
BIN
web/_packages/web_startup_analyzer/example/web/favicon.png
Normal file
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 |
50
web/_packages/web_startup_analyzer/example/web/index.html
Normal file
50
web/_packages/web_startup_analyzer/example/web/index.html
Normal 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>
|
||||
35
web/_packages/web_startup_analyzer/example/web/manifest.json
Normal file
35
web/_packages/web_startup_analyzer/example/web/manifest.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user