Blazor grid performance scenarios (#23301)
* Add complex table benchmark * Add FastGrid scenario too * Make the two grids consistent with each other * Add scenario for PlainTable * Empty commit to trigger re-rerun on CI. Clicking retry doesn't seem to be working.
This commit is contained in:
parent
38f9b9abdb
commit
1998a06bdc
|
|
@ -0,0 +1,122 @@
|
|||
@page "/gridrendering"
|
||||
@inject IJSRuntime JSRuntime
|
||||
@using Wasm.Performance.TestApp.Shared.FastGrid
|
||||
|
||||
<h1>20 x 200 Grid</h1>
|
||||
|
||||
<fieldset>
|
||||
<select id="render-mode" @bind="selectedRenderMode">
|
||||
<option>@RenderMode.FastGrid</option>
|
||||
<option>@RenderMode.PlainTable</option>
|
||||
<option>@RenderMode.ComplexTable</option>
|
||||
</select>
|
||||
|
||||
<button id="show" @onclick="Show">Show</button>
|
||||
<button id="hide" @onclick="Hide">Hide</button>
|
||||
@if (forecasts != null)
|
||||
{
|
||||
<button id="change-page" @onclick="ChangePage">Switch pages</button>
|
||||
}
|
||||
</fieldset>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p><em>(No data assigned)</em></p>
|
||||
}
|
||||
else if (selectedRenderMode == RenderMode.FastGrid)
|
||||
{
|
||||
<p>FastGrid represents a minimal, optimized implementation of a grid.</p>
|
||||
|
||||
<Grid Data="@forecasts">
|
||||
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Date">@context.Date.ToShortDateString()</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureC">@context.TemperatureC</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="TemperatureF">@context.TemperatureF</GridColumn>
|
||||
<GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
|
||||
</Grid>
|
||||
}
|
||||
else if (selectedRenderMode == RenderMode.PlainTable)
|
||||
{
|
||||
<p>PlainTable represents a minimal but not optimized implementation of a grid.</p>
|
||||
|
||||
<Wasm.Performance.TestApp.Shared.PlainTable.TableComponent Data="@forecasts" Columns="@Columns" />
|
||||
}
|
||||
else if (selectedRenderMode == RenderMode.ComplexTable)
|
||||
{
|
||||
<p>ComplexTable represents a maximal, not optimized implementation of a grid, using a wide range of Blazor features at once.</p>
|
||||
|
||||
<Wasm.Performance.TestApp.Shared.ComplexTable.TableComponent Data="@forecasts" Columns="@Columns" />
|
||||
}
|
||||
|
||||
@code {
|
||||
enum RenderMode { PlainTable, ComplexTable, FastGrid }
|
||||
|
||||
private RenderMode selectedRenderMode = RenderMode.FastGrid;
|
||||
|
||||
private WeatherForecast[] forecasts;
|
||||
public List<string> Columns { get; set; } = new List<string>
|
||||
{
|
||||
"Date", "TemperatureC", "TemperatureF", "Summary",
|
||||
"Date", "TemperatureC", "TemperatureF", "Summary",
|
||||
"Date", "TemperatureC", "TemperatureF", "Summary",
|
||||
"Date", "TemperatureC", "TemperatureF", "Summary",
|
||||
"Date", "TemperatureC", "TemperatureF", "Summary",
|
||||
};
|
||||
|
||||
private static string[] sampleSummaries = new[] { "Balmy", "Chilly", "Freezing", "Bracing" };
|
||||
private static WeatherForecast[] staticSampleDataPage1 = Enumerable.Range(0, 200).Select(CreateSampleDataItem).ToArray();
|
||||
private static WeatherForecast[] staticSampleDataPage2 = Enumerable.Range(200, 200).Select(CreateSampleDataItem).ToArray();
|
||||
|
||||
private static WeatherForecast CreateSampleDataItem(int index) => new WeatherForecast
|
||||
{
|
||||
Date = DateTime.Now.Date.AddDays(index),
|
||||
Summary = sampleSummaries[index % sampleSummaries.Length],
|
||||
TemperatureC = index,
|
||||
};
|
||||
|
||||
void Show()
|
||||
{
|
||||
forecasts = staticSampleDataPage1;
|
||||
}
|
||||
|
||||
void Hide()
|
||||
{
|
||||
forecasts = null;
|
||||
}
|
||||
|
||||
void ChangePage()
|
||||
{
|
||||
forecasts = (forecasts == staticSampleDataPage1) ? staticSampleDataPage2 : staticSampleDataPage1;
|
||||
}
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
BenchmarkEvent.Send(JSRuntime, "Finished rendering table");
|
||||
}
|
||||
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public string Summary { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
<td @attributes="@Attributes"
|
||||
@onclick="@(() => OnClick.Invoke(CellIndex))"
|
||||
>
|
||||
@switch (Field)
|
||||
{
|
||||
case "Date":
|
||||
@Item.Date.ToShortDateString()
|
||||
break;
|
||||
case "TemperatureC":
|
||||
@Item.TemperatureC
|
||||
break;
|
||||
case "TemperatureF":
|
||||
@Item.TemperatureF
|
||||
break;
|
||||
case "Summary":
|
||||
@Item.Summary
|
||||
break;
|
||||
}
|
||||
</td>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public WeatherForecast Item { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public TableComponent ParentTable { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Field { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int CellIndex { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int RowIndex { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Selected { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string FormatString { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<int, Task> OnClick { get; set; }
|
||||
|
||||
private protected Dictionary<string, object> Attributes
|
||||
{
|
||||
get
|
||||
{
|
||||
var attributes = new Dictionary<string, object>();
|
||||
|
||||
attributes["tabindex"] = CellIndex;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
<tr class="complex-table-row" style="@rowStyle">
|
||||
@foreach (var item in Columns)
|
||||
{
|
||||
<Cell Item="@Item"
|
||||
Field="@item"
|
||||
CellIndex="1"
|
||||
RowIndex="2"
|
||||
Selected="@isSelected"
|
||||
FormatString="foo"
|
||||
OnClick="@OnCellClick">
|
||||
</Cell>
|
||||
}
|
||||
</tr>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
private bool isSelected = false;
|
||||
|
||||
private string rowStyle => isSelected ? "background-color: lightblue;" : "";
|
||||
|
||||
[Parameter]
|
||||
public WeatherForecast Item { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string> Columns { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<int, Task> OnClick { get; set; }
|
||||
|
||||
Task OnCellClick(int args)
|
||||
{
|
||||
isSelected = !isSelected;
|
||||
|
||||
return OnClick.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
@foreach (var item in Data)
|
||||
{
|
||||
<Row Item="@item" Columns="@Columns"
|
||||
OnClick="@OnClick"></Row>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public WeatherForecast[] Data { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string> Columns { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<int, Task> OnClick { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@foreach (var item in Columns)
|
||||
{
|
||||
<th>@item</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<CascadingValue Value="@this">
|
||||
<RowCollection Data="@Data"
|
||||
Columns="@Columns"
|
||||
OnClick="@RefreshComponent"></RowCollection>
|
||||
</CascadingValue>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public WeatherForecast[] Data { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string> Columns { get; set; }
|
||||
|
||||
DateTime t1;
|
||||
DateTime t2;
|
||||
Task RefreshComponent(int index)
|
||||
{
|
||||
t1 = DateTime.Now;
|
||||
StateHasChanged();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender)
|
||||
{
|
||||
t2 = DateTime.Now;
|
||||
Console.WriteLine("Refresh Time " + (t2 - t1).TotalMilliseconds);
|
||||
}
|
||||
return base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
@typeparam TRowData
|
||||
|
||||
<CascadingValue IsFixed="true" Value="this">@ChildContent</CascadingValue>
|
||||
|
||||
<table @attributes="@Attributes">
|
||||
<thead>
|
||||
<tr>
|
||||
@foreach (var col in columns)
|
||||
{
|
||||
col.RenderHeader(__builder);
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Data)
|
||||
{
|
||||
<tr @key="item.GetHashCode()" class="@(RowClass?.Invoke(item))">
|
||||
@foreach (var col in columns)
|
||||
{
|
||||
col.RenderCell(__builder, item);
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@code {
|
||||
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> Attributes { get; set; }
|
||||
[Parameter] public ICollection<TRowData> Data { get; set; }
|
||||
[Parameter] public RenderFragment ChildContent { get; set; }
|
||||
[Parameter] public Func<TRowData, string> RowClass { get; set; }
|
||||
|
||||
private List<GridColumn<TRowData>> columns = new List<GridColumn<TRowData>>();
|
||||
|
||||
internal void AddColumn(GridColumn<TRowData> column)
|
||||
{
|
||||
columns.Add(column);
|
||||
}
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// On the first render, we collect the list of columns, then we have to render them.
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
@typeparam TRowData
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
@code {
|
||||
[CascadingParameter] public Grid<TRowData> OwnerGrid { get; set; }
|
||||
[Parameter] public string Title { get; set; }
|
||||
[Parameter] public TRowData RowData { get; set; }
|
||||
[Parameter] public RenderFragment<TRowData> ChildContent { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
OwnerGrid.AddColumn(this);
|
||||
}
|
||||
|
||||
internal void RenderHeader(RenderTreeBuilder __builder)
|
||||
{
|
||||
<th>@Title</th>
|
||||
}
|
||||
|
||||
internal void RenderCell(RenderTreeBuilder __builder, TRowData rowData)
|
||||
{
|
||||
<td>@ChildContent(rowData)</td>
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
<a href="renderlist">RenderList</a> |
|
||||
<a href="json">JSON</a> |
|
||||
<a href="orgchart">OrgChart</a> |
|
||||
<a href="timer">Timer</a>
|
||||
<a href="timer">Timer</a> |
|
||||
<a href="gridrendering">Grid</a>
|
||||
|
||||
<hr />
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
<td @onclick="@(() => OnClick.Invoke(1))">
|
||||
@switch (Field)
|
||||
{
|
||||
case "Date":
|
||||
@Item.Date.ToShortDateString()
|
||||
break;
|
||||
case "TemperatureC":
|
||||
@Item.TemperatureC
|
||||
break;
|
||||
case "TemperatureF":
|
||||
@Item.TemperatureF
|
||||
break;
|
||||
case "Summary":
|
||||
@Item.Summary
|
||||
break;
|
||||
}
|
||||
</td>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public WeatherForecast Item { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Field { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<int, Task> OnClick { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
<tr style="@rowStyle">
|
||||
@foreach (var item in Columns)
|
||||
{
|
||||
<Cell Item="@Item"
|
||||
Field="@item"
|
||||
OnClick="@OnCellClick">
|
||||
</Cell>
|
||||
}
|
||||
</tr>
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
private bool isSelected = false;
|
||||
|
||||
private string rowStyle => isSelected ? "background-color: lightblue;" : "";
|
||||
|
||||
[Parameter]
|
||||
public WeatherForecast Item { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string> Columns { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<int, Task> OnClick { get; set; }
|
||||
|
||||
Task OnCellClick(int args)
|
||||
{
|
||||
isSelected = !isSelected;
|
||||
|
||||
return OnClick.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
@foreach (var item in Data)
|
||||
{
|
||||
<Row Item="@item" Columns="@Columns"
|
||||
OnClick="@OnClick"></Row>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public WeatherForecast[] Data { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string> Columns { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<int, Task> OnClick { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
@using WeatherForecast = Pages.GridRendering.WeatherForecast
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
@foreach (var item in Columns)
|
||||
{
|
||||
<th>@item</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<RowCollection Data="@Data"
|
||||
Columns="@Columns"
|
||||
OnClick="@RefreshComponent"></RowCollection>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public WeatherForecast[] Data { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string> Columns { get; set; }
|
||||
|
||||
DateTime t1;
|
||||
DateTime t2;
|
||||
Task RefreshComponent(int index)
|
||||
{
|
||||
t1 = DateTime.Now;
|
||||
StateHasChanged();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender)
|
||||
{
|
||||
t2 = DateTime.Now;
|
||||
Console.WriteLine("Refresh Time " + (t2 - t1).TotalMilliseconds);
|
||||
}
|
||||
return base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js';
|
||||
import { receiveEvent } from './util/BenchmarkEvents.js';
|
||||
import { BlazorApp } from './util/BlazorApp.js';
|
||||
import { setInputValue } from './util/DOM.js';
|
||||
|
||||
group('Grid', () => {
|
||||
let app;
|
||||
|
||||
setup(async () => {
|
||||
app = new BlazorApp();
|
||||
await app.start();
|
||||
app.navigateTo('gridrendering');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
app.dispose();
|
||||
});
|
||||
|
||||
benchmark('PlainTable: From blank', () => measureRenderGridFromBlank(app), {
|
||||
setup: () => prepare(app, 'PlainTable', false),
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-plaintable-from-blank',
|
||||
description: 'Time to render plain table from blank (ms)'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('PlainTable: Switch pages', () => measureRenderGridSwitchPages(app), {
|
||||
setup: () => prepare(app, 'PlainTable', true),
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-plaintable-switch-pages',
|
||||
description: 'Time to render plain table change of page (ms)'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('ComplexTable: From blank', () => measureRenderGridFromBlank(app), {
|
||||
setup: () => prepare(app, 'ComplexTable', false),
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-complextable-from-blank',
|
||||
description: 'Time to render complex table from blank (ms)'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('ComplexTable: Switch pages', () => measureRenderGridSwitchPages(app), {
|
||||
setup: () => prepare(app, 'ComplexTable', true),
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-complextable-switch-pages',
|
||||
description: 'Time to render complex table change of page (ms)'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('FastGrid: From blank', () => measureRenderGridFromBlank(app), {
|
||||
setup: () => prepare(app, 'FastGrid', false),
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-fastgrid-from-blank',
|
||||
description: 'Time to render fast grid from blank (ms)'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('FastGrid: Switch pages', () => measureRenderGridSwitchPages(app), {
|
||||
setup: () => prepare(app, 'FastGrid', true),
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-fastgrid-switch-pages',
|
||||
description: 'Time to render fast grid change of page (ms)'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function prepare(app, renderMode, populateTable) {
|
||||
const renderModeSelect = app.window.document.querySelector('#render-mode');
|
||||
setInputValue(renderModeSelect, renderMode);
|
||||
|
||||
if (populateTable) {
|
||||
let nextRenderCompletion = receiveEvent('Finished rendering table');
|
||||
app.window.document.querySelector(populateTable ? '#show' : '#hide').click();
|
||||
await nextRenderCompletion;
|
||||
}
|
||||
}
|
||||
|
||||
async function measureRenderGridFromBlank(app) {
|
||||
const appDocument = app.window.document;
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished rendering table');
|
||||
appDocument.querySelector('#hide').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('tbody tr').length !== 0) {
|
||||
throw new Error('Wrong number of rows rendered');
|
||||
}
|
||||
|
||||
nextRenderCompletion = receiveEvent('Finished rendering table');
|
||||
appDocument.querySelector('#show').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('tbody tr').length !== 200) {
|
||||
throw new Error('Wrong number of rows rendered');
|
||||
}
|
||||
}
|
||||
|
||||
async function measureRenderGridSwitchPages(app) {
|
||||
const appDocument = app.window.document;
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished rendering table');
|
||||
appDocument.querySelector('#change-page').click();
|
||||
await nextRenderCompletion;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import './appStartup.js';
|
|||
import './renderList.js';
|
||||
import './jsonHandling.js';
|
||||
import './orgChart.js';
|
||||
import './grid.js';
|
||||
import { getBlazorDownloadSize } from './blazorDownloadSize.js';
|
||||
|
||||
new HtmlUI('E2E Performance', '#display');
|
||||
|
|
|
|||
Loading…
Reference in New Issue