Skip to content

Commit 2c461fb

Browse files
committed
improve page load speed
split the charts javascript code from the rest of the frontend code, and load it only when necessary. This greatly diminishes the amount of js loaded by default, and achieves very good performance scores by default.
1 parent e999079 commit 2c461fb

File tree

10 files changed

+184
-169
lines changed

10 files changed

+184
-169
lines changed

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CHANGELOG.md
22

3-
## unreleased
3+
## 0.9.3 (2023-08-03)
44

55
- Icons are now loaded directly from the sqlpage binary instead of loading them from a CDN.
66
This allows pages to load faster, and to get a better score on google's performance audits, potentially improving your position in search results.
@@ -11,6 +11,9 @@
1111
- Faster page loads, less work on the server
1212
- Fix a bug where table search would fail to find a row if the search term contained some special characters.
1313
- Fixes https://github.com/lovasoa/SQLpage/issues/46
14+
- Split the charts javascript code from the rest of the frontend code, and load it only when necessary.
15+
This greatly diminishes the amount of js loaded by default, and achieves very good performance scores by default.
16+
SQLPage websites now load even faster, een on slow mobile connections.
1417

1518
## 0.9.2 (2023-08-01)
1619

Cargo.lock

+15-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqlpage"
3-
version = "0.9.2"
3+
version = "0.9.3"
44
edition = "2021"
55
description = "A SQL-only web application framework. Takes .sql files and formats the query result using pre-made configurable professional-looking components."
66
keywords = ["web", "sql", "framework"]

build.rs

+8-15
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,22 @@ use std::path::{Path, PathBuf};
1111
async fn main() {
1212
println!("cargo:rerun-if-changed=build.rs");
1313

14-
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
15-
1614
for h in [
17-
spawn(download_deps(
18-
"sqlpage/sqlpage.js",
19-
out_dir.join("sqlpage.js"),
20-
)),
21-
spawn(download_deps(
22-
"sqlpage/sqlpage.css",
23-
out_dir.join("sqlpage.css"),
24-
)),
25-
spawn(download_deps(
26-
"sqlpage/tabler-icons.svg",
27-
out_dir.join("tabler-icons.svg"),
28-
)),
15+
spawn(download_deps("sqlpage.js")),
16+
spawn(download_deps("sqlpage.css")),
17+
spawn(download_deps("tabler-icons.svg")),
18+
spawn(download_deps("apexcharts.js")),
2919
] {
3020
h.await.unwrap();
3121
}
3222
}
3323

3424
/// Creates a file with inlined remote files included
35-
async fn download_deps(path_in: &str, path_out: PathBuf) {
25+
async fn download_deps(filename: &str) {
3626
println!("cargo:rerun-if-changed=build.rs");
27+
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
28+
let path_in = format!("sqlpage/{}", filename);
29+
let path_out: PathBuf = out_dir.join(filename);
3730
// Generate outfile by reading infile and interpreting all comments
3831
// like "/* !include https://... */" as a request to include the contents of
3932
// the URL in the generated file.

sqlpage/apexcharts.js

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/* !include https://cdn.jsdelivr.net/npm/[email protected]/dist/apexcharts.min.js */
2+
3+
4+
function sqlpage_chart() {
5+
6+
const tblrColors = Object.fromEntries(['azure', 'red', 'lime', 'blue', 'pink', 'indigo', 'purple', 'yellow', 'cyan', 'green', 'orange', 'cyan']
7+
.map(c => [c, getComputedStyle(document.documentElement).getPropertyValue('--tblr-' + c)]));
8+
9+
/** @typedef { { [name:string]: {data:{x:number,y:number}[], name:string} } } Series */
10+
11+
/**
12+
* @param {Series} series
13+
* @returns {Series} */
14+
function align_categories(series) {
15+
const new_series = series.map(s => ({ name: s.name, data: [] }));
16+
do {
17+
var category = null;
18+
series.forEach((s, s_i) => {
19+
const point = s.data[0];
20+
let new_point = { x: category, y: NaN };
21+
if (point) {
22+
if (category == null) category = point.x;
23+
if (category === point.x) {
24+
new_point = s.data.shift();
25+
}
26+
}
27+
new_series[s_i].data.push(new_point);
28+
})
29+
new_series.forEach(s => s.data[s.data.length - 1].x = category);
30+
} while (category != null);
31+
new_series.forEach(s => s.data.pop());
32+
return new_series;
33+
}
34+
35+
for (const c of document.getElementsByClassName("chart")) {
36+
try {
37+
const data = JSON.parse(c.querySelector("data").innerText);
38+
/** @type { Series } */
39+
const series_map = {};
40+
data.points.forEach(([name, x, y]) => {
41+
series_map[name] = series_map[name] || { name, data: [] }
42+
series_map[name].data.push({ x, y });
43+
})
44+
if (data.xmin == null) data.xmin = undefined;
45+
if (data.xmax == null) data.xmax = undefined;
46+
if (data.ymin == null) data.ymin = undefined;
47+
if (data.ymax == null) data.ymax = undefined;
48+
49+
const colors = [
50+
...data.colors.filter(c => c).map(c => tblrColors[c]),
51+
...Object.values(tblrColors)
52+
];
53+
54+
let series = Object.values(series_map);
55+
56+
// tickamount is the number of intervals, not the number of ticks
57+
const tickAmount = data.xticks ||
58+
Math.min(30, Math.max(...series.map(s => s.data.length - 1)));
59+
60+
let labels;
61+
const categories = typeof data.points[0][1] === "string";
62+
if (data.type === "pie") {
63+
labels = data.points.map(([name, x, y]) => x || name);
64+
series = data.points.map(([name, x, y]) => y);
65+
} else if (categories) series = align_categories(series);
66+
67+
const options = {
68+
chart: {
69+
type: data.type || 'line',
70+
fontFamily: 'inherit',
71+
parentHeightOffset: 0,
72+
height: c.style.height,
73+
stacked: !!data.stacked,
74+
toolbar: {
75+
show: false,
76+
},
77+
animations: {
78+
enabled: false
79+
},
80+
zoom: {
81+
enabled: false
82+
}
83+
},
84+
theme: {
85+
palette: 'palette4',
86+
},
87+
dataLabels: {
88+
enabled: !!data.labels,
89+
},
90+
fill: {
91+
type: data.type === 'area' ? 'gradient' : 'solid',
92+
},
93+
stroke: {
94+
width: data.type === 'area' ? 3 : 1,
95+
lineCap: "round",
96+
curve: "smooth",
97+
},
98+
xaxis: {
99+
tooltip: {
100+
enabled: false
101+
},
102+
min: data.xmin,
103+
max: data.xmax,
104+
tickAmount,
105+
title: {
106+
text: data.xtitle || undefined,
107+
},
108+
type: data.time ? 'datetime' : categories ? 'category' : undefined,
109+
},
110+
yaxis: {
111+
logarithmic: !!data.logarithmic,
112+
min: data.ymin,
113+
max: data.ymax,
114+
title: {
115+
text: data.ytitle || undefined,
116+
}
117+
},
118+
markers: {
119+
size: data.marker || 0,
120+
strokeWidth: 0,
121+
hover: {
122+
sizeOffset: 5,
123+
}
124+
},
125+
tooltip: {
126+
fillSeriesColor: false,
127+
},
128+
plotOptions: { bar: { horizontal: !!data.horizontal } },
129+
colors,
130+
series,
131+
};
132+
if (labels) options.labels = labels;
133+
c.innerHTML = "";
134+
const chart = new ApexCharts(c, options);
135+
chart.render();
136+
if (window.charts) window.charts.push(chart);
137+
else window.charts = [chart];
138+
} catch (e) { console.log(e) }
139+
}
140+
}
141+
142+
sqlpage_chart();

0 commit comments

Comments
 (0)