Skip to content

Commit c4ceaad

Browse files
committed
chore: perf.prod app improvements
1 parent 521bb6f commit c4ceaad

File tree

7 files changed

+474
-32
lines changed

7 files changed

+474
-32
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import {
2+
component$,
3+
useSignal,
4+
useStore,
5+
$,
6+
type QwikIntrinsicElements,
7+
type FunctionComponent,
8+
createContextId,
9+
useContext,
10+
useContextProvider,
11+
} from "@qwik.dev/core";
12+
13+
type Item = { id: number; label: string; selected: boolean };
14+
15+
let idCounter = 1;
16+
const adjectives = [
17+
"pretty",
18+
"large",
19+
"big",
20+
"small",
21+
"tall",
22+
"short",
23+
"long",
24+
"handsome",
25+
"plain",
26+
"quaint",
27+
"clean",
28+
"elegant",
29+
"easy",
30+
"angry",
31+
"crazy",
32+
"helpful",
33+
"mushy",
34+
"odd",
35+
"unsightly",
36+
"adorable",
37+
"important",
38+
"inexpensive",
39+
"cheap",
40+
"expensive",
41+
"fancy",
42+
],
43+
colours = [
44+
"red",
45+
"yellow",
46+
"blue",
47+
"green",
48+
"pink",
49+
"brown",
50+
"purple",
51+
"brown",
52+
"white",
53+
"black",
54+
"orange",
55+
],
56+
nouns = [
57+
"table",
58+
"chair",
59+
"house",
60+
"bbq",
61+
"desk",
62+
"car",
63+
"pony",
64+
"cookie",
65+
"sandwich",
66+
"burger",
67+
"pizza",
68+
"mouse",
69+
"keyboard",
70+
];
71+
72+
function _random(max: number) {
73+
return Math.round(Math.random() * 1000) % max;
74+
}
75+
76+
export function buildData(count: number): Item[] {
77+
const data: Item[] = new Array(count);
78+
for (let i = 0; i < count; i++) {
79+
data[i] = {
80+
id: idCounter++,
81+
label: `${adjectives[_random(adjectives.length)]} ${
82+
colours[_random(colours.length)]
83+
} ${nouns[_random(nouns.length)]}`,
84+
selected: false,
85+
};
86+
}
87+
return data;
88+
}
89+
90+
const helpers = createContextId<{
91+
reset$: (count: number) => void;
92+
update$: () => void;
93+
add$: () => void;
94+
swap$: () => void;
95+
select$: (item: Item) => void;
96+
delete$: (item: Item) => void;
97+
}>("h");
98+
99+
const Button: FunctionComponent<QwikIntrinsicElements["button"]> = (props) => (
100+
<div class="col-sm-6 smallpad">
101+
<button type="button" class="btn btn-primary btn-block" {...props} />
102+
</div>
103+
);
104+
105+
const Row = component$<{
106+
item: Item;
107+
}>(({ item }) => {
108+
const { select$, delete$ } = useContext(helpers);
109+
return (
110+
<tr class={item.selected ? "danger" : ""}>
111+
<td class="col-md-1">{item.id}</td>
112+
<td class="col-md-4">
113+
<a onClick$={() => select$(item)}>{item.label}</a>
114+
</td>
115+
<td class="col-md-1">
116+
<a onClick$={() => delete$(item)}>
117+
<span aria-hidden="true">x</span>
118+
</a>
119+
</td>
120+
<td class="col-md-6" />
121+
</tr>
122+
);
123+
});
124+
125+
const Table = component$<{
126+
data: Item[];
127+
}>(({ data }) => (
128+
<table class="table table-hover table-striped test-data">
129+
<tbody>
130+
{data.map((item) => (
131+
<Row key={item.id} item={item} />
132+
))}
133+
</tbody>
134+
</table>
135+
));
136+
137+
export const Buttons = component$(() => {
138+
const h = useContext(helpers);
139+
return (
140+
<>
141+
<Button id="run" onClick$={() => h.reset$(1000)}>
142+
Create 1,000 rows
143+
</Button>
144+
<Button id="runlots" onClick$={() => h.reset$(10000)}>
145+
Create 10,000 rows
146+
</Button>
147+
<Button id="add" onClick$={h.add$}>
148+
Append 1,000 rows
149+
</Button>
150+
<Button id="update" onClick$={h.update$}>
151+
Update every 10th row
152+
</Button>
153+
<Button id="clear" onClick$={() => h.reset$(0)}>
154+
Clear
155+
</Button>
156+
<Button id="swaprows" onClick$={h.swap$}>
157+
Swap Rows
158+
</Button>
159+
</>
160+
);
161+
});
162+
163+
type BenchState = {
164+
data: Item[];
165+
};
166+
export default component$(() => {
167+
const state = useStore<BenchState>({ data: [] });
168+
const selectedItem = useSignal<Item | null>(null);
169+
const redraw = useSignal(0);
170+
useContextProvider(helpers, {
171+
reset$: $((count: number) => {
172+
state.data = buildData(count);
173+
selectedItem.value = null;
174+
redraw.value++;
175+
}),
176+
update$: $(() => {
177+
for (let i = 0, d = state.data, len = d.length; i < len; i += 10) {
178+
d[i].label += " !!!";
179+
}
180+
}),
181+
add$: $(() => state.data.push(...buildData(1000))),
182+
swap$: $(() => {
183+
const d = state.data;
184+
if (d.length > 998) {
185+
const tmp = d[1];
186+
d[1] = d[998];
187+
d[998] = tmp;
188+
}
189+
}),
190+
select$: $((item: Item) => {
191+
if (selectedItem.value) {
192+
selectedItem.value.selected = false;
193+
}
194+
selectedItem.value = item;
195+
item.selected = true;
196+
}),
197+
delete$: $((item: Item) => {
198+
state.data.splice(state.data.indexOf(item), 1);
199+
if (selectedItem.value === item) {
200+
selectedItem.value = null;
201+
}
202+
}),
203+
});
204+
205+
return (
206+
<div class="container">
207+
<div class="jumbotron">
208+
<div class="row">
209+
<div class="col-md-6">
210+
<h1>Qwik Component Implementation</h1>
211+
</div>
212+
<div class="col-md-6">
213+
<div class="row">
214+
<Buttons />
215+
</div>
216+
</div>
217+
</div>
218+
</div>
219+
<Table key={redraw.value} data={state.data} />
220+
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true" />
221+
</div>
222+
);
223+
});
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import {
2+
component$,
3+
createSignal,
4+
untrack,
5+
useSignal,
6+
type QRL,
7+
type Signal,
8+
} from "@qwik.dev/core";
9+
10+
const adjectives = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean", "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", "cheap", "expensive", "fancy"]; // prettier-ignore
11+
const colors = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"]; // prettier-ignore
12+
const nouns = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard"]; // prettier-ignore
13+
14+
const random = (max: number) => Math.round(Math.random() * 1000) % max;
15+
16+
let nextId = 1;
17+
18+
type Row = {
19+
id: number;
20+
label: Signal<string>;
21+
};
22+
23+
const buildData = (count: number) => {
24+
const data = new Array(count);
25+
for (let i = 0; i < count; i++) {
26+
const label = createSignal(
27+
`${adjectives[random(adjectives.length)]} ${colors[random(colors.length)]} ${nouns[random(nouns.length)]}`,
28+
);
29+
data[i] = createSignal({ id: nextId++, label });
30+
}
31+
return data;
32+
};
33+
34+
type ButtonProps = {
35+
id: string;
36+
text: string;
37+
click$: QRL<() => void>;
38+
};
39+
40+
const Button = component$<ButtonProps>(({ id, text, click$ }) => {
41+
return (
42+
<div class="col-sm-6 smallpad">
43+
<button
44+
id={id}
45+
class="btn btn-primary btn-block"
46+
type="button"
47+
onClick$={click$}
48+
>
49+
{text}
50+
</button>
51+
</div>
52+
);
53+
});
54+
55+
export default component$(() => {
56+
const data = useSignal<Signal<Row>[]>([]);
57+
const selected = useSignal<number | null>(null);
58+
59+
return (
60+
<div class="container">
61+
<div class="jumbotron">
62+
<div class="row">
63+
<div class="col-md-6">
64+
<h1>Qwik Signal Implementation</h1>
65+
</div>
66+
<div class="col-md-6">
67+
<div class="row">
68+
<Button
69+
id="run"
70+
text="Create 1,000 rows"
71+
click$={() => (data.value = buildData(1_000))}
72+
/>
73+
<Button
74+
id="runlots"
75+
text="Create 10,000 rows"
76+
click$={() => (data.value = buildData(10_000))}
77+
/>
78+
<Button
79+
id="add"
80+
text="Append 1,000 rows"
81+
click$={() =>
82+
(data.value = [...data.value, ...buildData(1_000)])
83+
}
84+
/>
85+
<Button
86+
id="update"
87+
text="Update every 10th row"
88+
click$={() => {
89+
const dataValue = untrack(() => data.value);
90+
for (
91+
let i = 0, d = dataValue, len = d.length;
92+
i < len;
93+
i += 10
94+
) {
95+
d[i].value.label.value += " !!!";
96+
}
97+
}}
98+
/>
99+
<Button
100+
id="clear"
101+
text="Clear"
102+
click$={() => (data.value = [])}
103+
/>
104+
<Button
105+
id="swaprows"
106+
text="Swap Rows"
107+
click$={() => {
108+
const list = data.value;
109+
if (list.length > 998) {
110+
const item = list[1].value;
111+
list[1].value = list[998].value;
112+
list[998].value = item;
113+
}
114+
}}
115+
/>
116+
</div>
117+
</div>
118+
</div>
119+
</div>
120+
<table class="table table-hover table-striped test-data">
121+
<tbody>
122+
{data.value.map((row) => {
123+
return (
124+
<tr
125+
key={untrack(() => row.value.id)}
126+
class={selected.value === row.value.id ? "danger" : ""}
127+
>
128+
<td class="col-md-1">{row.value.id}</td>
129+
<td class="col-md-4">
130+
<a onClick$={() => (selected.value = row.value.id)}>
131+
{row.value.label.value}
132+
</a>
133+
</td>
134+
<td class="col-md-1">
135+
<a
136+
onClick$={() => {
137+
const dataValue = untrack(() => data.value);
138+
const currentRow = row.value;
139+
data.value = dataValue.toSpliced(
140+
dataValue.findIndex(
141+
(d) => d.value.id === currentRow.id,
142+
),
143+
1,
144+
);
145+
}}
146+
>
147+
<span aria-hidden="true">x</span>
148+
</a>
149+
</td>
150+
<td class="col-md-6" />
151+
</tr>
152+
);
153+
})}
154+
</tbody>
155+
</table>
156+
<span class="preloadicon glyphicon glyphicon-remove" aria-hidden="true" />
157+
</div>
158+
);
159+
});

0 commit comments

Comments
 (0)