perf: dynamic-import chart.js in FitnessChart
Chart.js (~244 KB) was a top-level import, so every route that referenced FitnessChart.svelte transitively pulled it. Defer it to an async block inside onMount so non-stats fitness routes (workout, check-in, nutrition, history list) no longer ship chart.js. - `ChartCtor` holds the async-loaded constructor - `disposed` guard handles unmount during the import - theme MutationObserver / matchMedia wiring moved inside the async block so it only attaches once the chart actually exists
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
Order = impact. Font items + app.html preload intentionally skipped.
|
||||
|
||||
- [x] 1. Lucide subpath imports — convert `from '@lucide/svelte'` barrel imports to `@lucide/svelte/icons/<kebab-name>` so Vite tree-shakes per-icon (current 748 KB shared chunk)
|
||||
- [ ] 2. Chart.js dynamic import in `FitnessChart.svelte` (drop 244 KB from non-stats fitness routes)
|
||||
- [x] 2. Chart.js dynamic import in `FitnessChart.svelte` (drop 244 KB from non-stats fitness routes)
|
||||
- [ ] 3. Recipe `all_brief` endpoint — drop `JSON.parse(JSON.stringify(...))`, move shuffle client-side, enable caching
|
||||
- [ ] 4. Favorites page — drop unnecessary `all_brief` fetch (verify consumer first)
|
||||
- [ ] 5. Replace redundant `locals.auth()` with `locals.session` across recipe/calendar/fitness loaders
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.46.13",
|
||||
"version": "1.46.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
@@ -20,9 +18,10 @@
|
||||
|
||||
/** @type {HTMLCanvasElement | undefined} */
|
||||
let canvas = $state(undefined);
|
||||
/** @type {Chart | null} */
|
||||
/** @type {import('chart.js').Chart | null} */
|
||||
let chart = $state(null);
|
||||
let registered = false;
|
||||
/** @type {typeof import('chart.js').Chart | null} */
|
||||
let ChartCtor = null;
|
||||
|
||||
const nordColors = [
|
||||
'#88C0D0', '#A3BE8C', '#EBCB8B', '#D08770', '#BF616A',
|
||||
@@ -37,11 +36,7 @@
|
||||
}
|
||||
|
||||
function createChart() {
|
||||
if (!canvas || !data?.datasets) return;
|
||||
if (!registered) {
|
||||
Chart.register(...registerables);
|
||||
registered = true;
|
||||
}
|
||||
if (!canvas || !data?.datasets || !ChartCtor) return;
|
||||
if (chart) chart.destroy();
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
@@ -104,7 +99,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
chart = new Chart(ctx, /** @type {any} */ ({
|
||||
chart = new ChartCtor(ctx, /** @type {any} */ ({
|
||||
type,
|
||||
data: { labels: plainLabels, datasets: plainDatasets },
|
||||
plugins,
|
||||
@@ -182,30 +177,42 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
createChart();
|
||||
requestAnimationFrame(() => {
|
||||
if (chart) {
|
||||
chart.options.animation = { duration: 300 };
|
||||
chart.options.transitions = {
|
||||
active: { animation: { duration: 200 } }
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
let disposed = false;
|
||||
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const onTheme = () => setTimeout(createChart, 100);
|
||||
mq.addEventListener('change', onTheme);
|
||||
/** @type {MutationObserver | undefined} */
|
||||
let obs;
|
||||
|
||||
const obs = new MutationObserver((muts) => {
|
||||
for (const m of muts) {
|
||||
if (m.attributeName === 'data-theme') onTheme();
|
||||
}
|
||||
});
|
||||
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
(async () => {
|
||||
const [{ Chart, registerables }] = await Promise.all([
|
||||
import('chart.js'),
|
||||
import('chartjs-adapter-date-fns')
|
||||
]);
|
||||
if (disposed) return;
|
||||
Chart.register(...registerables);
|
||||
ChartCtor = Chart;
|
||||
createChart();
|
||||
requestAnimationFrame(() => {
|
||||
if (chart) {
|
||||
chart.options.animation = { duration: 300 };
|
||||
chart.options.transitions = {
|
||||
active: { animation: { duration: 200 } }
|
||||
};
|
||||
}
|
||||
});
|
||||
mq.addEventListener('change', onTheme);
|
||||
obs = new MutationObserver((muts) => {
|
||||
for (const m of muts) {
|
||||
if (m.attributeName === 'data-theme') onTheme();
|
||||
}
|
||||
});
|
||||
obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
})();
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
mq.removeEventListener('change', onTheme);
|
||||
obs.disconnect();
|
||||
obs?.disconnect();
|
||||
if (chart) chart.destroy();
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user