Skip to content

Commit

Permalink
feat: load activities data lazily to reduce bundle size
Browse files Browse the repository at this point in the history
Signed-off-by: Frost Ming <[email protected]>
  • Loading branch information
frostming committed Sep 13, 2023
1 parent 9ca343b commit 1d8e0ad
Show file tree
Hide file tree
Showing 16 changed files with 131 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ node_modules
yarn-error.log

# Build directory
/build
/dist
.DS_Store
.sass-cache/
_posts/
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"url": "https://github.com/yihong0618/running_page"
},
"devDependencies": {
"@types/geojson": "^7946.0.10",
"@types/mapbox__polyline": "^1.0.2",
"@types/node": "^20.3.3",
"@types/react": "^18.2.14",
Expand Down
9 changes: 6 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default function Loading() {
return <h2>🌀 Loading...</h2>;
}
5 changes: 3 additions & 2 deletions src/components/LocationStat/CitiesStat.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React from 'react';
import Stat from '@/components/Stat';
import useActivities from '@/hooks/useActivities';
import Loading from '../Loading';

// only support China for now
const CitiesStat = ({ onClick }: { onClick: (_city: string) => void }) => {
const { cities } = useActivities();
const [{ cities }, loading] = useActivities();

const citiesArr = Object.entries(cities);
citiesArr.sort((a, b) => b[1] - a[1]);
return (
<div style={{ cursor: 'pointer' }}>
<section>
{citiesArr.map(([city, distance]) => (
{loading ? <Loading /> : citiesArr.map(([city, distance]) => (
<Stat
key={city}
value={city}
Expand Down
2 changes: 1 addition & 1 deletion src/components/LocationStat/LocationSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import useActivities from '@/hooks/useActivities';

// only support China for now
const LocationSummary = () => {
const { years, countries, provinces, cities } = useActivities();
const [{ years, countries, provinces, cities }] = useActivities();
return (
<div style={{ cursor: 'pointer' }}>
<section>
Expand Down
5 changes: 3 additions & 2 deletions src/components/LocationStat/PeriodStat.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from 'react';
import Stat from '@/components/Stat';
import useActivities from '@/hooks/useActivities';
import Loading from '../Loading';

const PeriodStat = ({ onClick }: { onClick: (_period: string) => void }) => {
const { runPeriod } = useActivities();
const [{ runPeriod }, loading] = useActivities();

const periodArr = Object.entries(runPeriod);
periodArr.sort((a, b) => b[1] - a[1]);
return (
<div style={{ cursor: 'pointer' }}>
<section>
{periodArr.map(([period, times]) => (
{loading ? <Loading /> : periodArr.map(([period, times]) => (
<Stat
key={period}
value={period}
Expand Down
2 changes: 1 addition & 1 deletion src/components/RunMap/RunMapButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import useActivities from '@/hooks/useActivities';
import styles from './style.module.scss';

const RunMapButtons = ({ changeYear, thisYear }: { changeYear: (_year: string) => void, thisYear: string }) => {
const { years } = useActivities();
const [{ years }] = useActivities();
const yearsButtons = years.slice();
yearsButtons.push('Total');

Expand Down
63 changes: 35 additions & 28 deletions src/components/RunMap/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import MapboxLanguage from '@mapbox/mapbox-gl-language';
import React, { useRef, useCallback } from 'react';
import React, { useRef, useCallback, useMemo } from 'react';
import ReactMapGL, {
Layer,
Source,
Expand All @@ -18,12 +18,14 @@ import {
LINE_OPACITY,
MAP_HEIGHT,
} from '@/utils/const';
import { Coordinate, IViewport, geoJsonForMap } from '@/utils/utils';
import { Coordinate, IViewport } from '@/utils/utils';
import RunMarker from './RunMaker';
import RunMapButtons from './RunMapButtons';
import styles from './style.module.scss';
import { FeatureCollection } from 'geojson';
import { RPGeometry } from '@/static/run_countries';
import useChinaGeoJson from '@/hooks/useChinaGeo';
import Loading from '../Loading';

interface IRunMapProps {
title: string;
Expand All @@ -42,7 +44,7 @@ const RunMap = ({
geoData,
thisYear,
}: IRunMapProps) => {
const { provinces } = useActivities();
const [{ provinces }, loading] = useActivities();
const mapRef = useRef<MapRef>();
const mapRefCallback = useCallback(
(ref: MapRef) => {
Expand All @@ -69,26 +71,38 @@ const RunMap = ({
filterProvinces.unshift('in', 'name');

const isBigMap = (viewport.zoom ?? 0) <= 3;
if (isBigMap && IS_CHINESE) {
geoData = geoJsonForMap();
}
const chinaGeo = useChinaGeoJson();

const geoDataForMap = useMemo(() => {
return isBigMap && IS_CHINESE ? chinaGeo : geoData;
}, [isBigMap, chinaGeo, geoData]);

const isSingleRun = useMemo(() => {
return (
geoDataForMap?.features.length === 1 &&
geoDataForMap?.features[0].geometry.coordinates.length
);
}, [geoDataForMap]);

const markerGeo = useMemo(() => {
let startLon = 0;
let startLat = 0;
let endLon = 0;
let endLat = 0;
if (isSingleRun) {
const points = geoDataForMap?.features[0].geometry.coordinates as (Coordinate[] | undefined);
if (typeof points !== 'undefined') {
[startLon, startLat] = points[0];
[endLon, endLat] = points[points.length - 1];
}
}
return { startLon, startLat, endLon, endLat };
}, [geoDataForMap, isSingleRun]);

const isSingleRun =
geoData.features.length === 1 &&
geoData.features[0].geometry.coordinates.length;
let startLon = 0;
let startLat = 0;
let endLon = 0;
let endLat = 0;
if (isSingleRun) {
const points = geoData.features[0].geometry.coordinates as Coordinate[];
[startLon, startLat] = points[0];
[endLon, endLat] = points[points.length - 1];
}
let dash = USE_DASH_LINE && !isSingleRun ? [2, 2] : [2, 0];

return (
<ReactMapGL
loading ? <Loading /> : <ReactMapGL
{...viewport}
width="100%"
height={MAP_HEIGHT}
Expand All @@ -99,7 +113,7 @@ const RunMap = ({
>
<RunMapButtons changeYear={changeYear} thisYear={thisYear} />
<FullscreenControl className={styles.fullscreenButton} />
<Source id="data" type="geojson" data={geoData}>
<Source id="data" type="geojson" data={geoDataForMap}>
<Layer
id="province"
type="fill"
Expand All @@ -123,14 +137,7 @@ const RunMap = ({
}}
/>
</Source>
{isSingleRun && (
<RunMarker
startLat={startLat}
startLon={startLon}
endLat={endLat}
endLon={endLon}
/>
)}
{isSingleRun && <RunMarker {...markerGeo} />}
<span className={styles.runTitle}>{title}</span>
</ReactMapGL>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/YearStat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import useHover from '@/hooks/useHover';
import { yearStats } from '@assets/index'

const YearStat = ({ year, onClick }: { year: string, onClick: (_year: string) => void }) => {
let { activities: runs, years } = useActivities();
let [{ activities: runs, years }] = useActivities();
// for hover
const [hovered, eventHandlers] = useHover();
// lazy Component
Expand Down
2 changes: 1 addition & 1 deletion src/components/YearsStat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import useActivities from '@/hooks/useActivities';
import { INFO_MESSAGE } from '@/utils/const';

const YearsStat = ({ year, onClick }: { year: string, onClick: (_year: string) => void }) => {
const { years } = useActivities();
const [{ years }] = useActivities();
// make sure the year click on front
let yearsArrayUpdate = years.slice();
yearsArrayUpdate.push('Total');
Expand Down
32 changes: 29 additions & 3 deletions src/hooks/useActivities.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { locationForRun, titleForRun } from '@/utils/utils';
import activities from '@/static/activities.json';
import { locationForRun, titleForRun, Activity } from '@/utils/utils';
import { useEffect, useState } from 'react';

const useActivities = () => {
const emptyStat = {
activities: [],
years: [],
countries: [],
provinces: [],
cities: {},
runPeriod: {},
thisYear: '',
}

const analyzeActivities = (activities: Activity[]) => {
const cities: Record<string, number> = {};
const runPeriod: Record<string, number> = {};
const provinces: Set<string> = new Set();
Expand Down Expand Up @@ -44,4 +54,20 @@ const useActivities = () => {
};
};

type Stat = ReturnType<typeof analyzeActivities>;

function useActivities(): [Stat, boolean] {
const [data, setData] = useState<Stat>(emptyStat);
const [loading, setLoading] = useState(true);

useEffect(() => {
import('@/static/activities.json').then((res) => {
setData(analyzeActivities(res.default));
setLoading(false);
});
}, []);

return [data, loading];
}

export default useActivities;
16 changes: 16 additions & 0 deletions src/hooks/useChinaGeo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FeatureCollection, LineString, Polygon, MultiPolygon } from 'geojson';
import { useEffect, useState } from 'react';

type RPGeometry = LineString | Polygon | MultiPolygon;

export default function useChinaGeoJson() {
const [geoData, setGeoData] = useState<FeatureCollection<RPGeometry>>();

useEffect(() => {
import('@/static/run_countries').then((res) => {
setGeoData(res.chinaGeojson);
})
}, []);

return geoData;
}
29 changes: 20 additions & 9 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Analytics } from '@vercel/analytics/react';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import Layout from '@/components/Layout';
import LocationStat from '@/components/LocationStat';
import RunMap from '@/components/RunMap';
Expand All @@ -25,16 +25,26 @@ import {

const Index = () => {
const { siteTitle } = useSiteMetadata();
const { activities, thisYear } = useActivities();
const [{ activities, thisYear }] = useActivities();
const [year, setYear] = useState(thisYear);
const [runIndex, setRunIndex] = useState(-1);
const [runs, setActivity] = useState(
filterAndSortRuns(activities, year, filterYearRuns, sortDateFunc)
);

useEffect(() => {
setYear(thisYear);
}, [thisYear]);

const [runs, setRuns] = useState<Activity[]>([]);

useEffect(() => {
setRuns(filterAndSortRuns(activities, year, filterYearRuns, sortDateFunc));
}, [activities, year]);

const [title, setTitle] = useState('');
const [geoData, setGeoData] = useState(geoJsonForRuns(runs));
const [geoData, setGeoData] = useState(geoJsonForRuns([]));
// for auto zoom
const bounds = getBoundsForGeoData(geoData);
const bounds = useMemo(() => {
return geoData.features.length > 0 ? getBoundsForGeoData(geoData) : {};
}, [geoData])
const [intervalId, setIntervalId] = useState<number>();

const [viewport, setViewport] = useState<IViewport>({
Expand All @@ -47,7 +57,7 @@ const Index = () => {
func: (_run: Activity, _value: string) => boolean
) => {
scrollToMap();
setActivity(filterAndSortRuns(activities, item, func, sortDateFunc));
setRuns(filterAndSortRuns(activities, item, func, sortDateFunc));
setRunIndex(-1);
setTitle(`${item} ${name} Running Heatmap`);
};
Expand Down Expand Up @@ -119,6 +129,7 @@ const Index = () => {
i += sliceNume;
}, 100);
setIntervalId(id);
return () => clearInterval(id);
}, [runs]);

useEffect(() => {
Expand Down Expand Up @@ -182,7 +193,7 @@ const Index = () => {
<RunTable
runs={runs}
locateActivity={locateActivity}
setActivity={setActivity}
setActivity={setRuns}
runIndex={runIndex}
setRunIndex={setRunIndex}
/>
Expand Down
3 changes: 0 additions & 3 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as mapboxPolyline from '@mapbox/polyline';
import gcoord from 'gcoord';
import { WebMercatorViewport } from 'react-map-gl';
import { chinaGeojson } from '@/static/run_countries';
import { chinaCities } from '@/static/city';
import { MUNICIPALITY_CITIES_ARR, NEED_FIX_MAP, RUN_TITLES } from './const';
import { FeatureCollection, LineString } from 'geojson';
Expand Down Expand Up @@ -174,7 +173,6 @@ const geoJsonForRuns = (runs: Activity[]): FeatureCollection<LineString> => ({
}),
});

const geoJsonForMap = () => chinaGeojson;

const titleForRun = (run: Activity): string => {
const runDistance = run.distance / 1000;
Expand Down Expand Up @@ -285,7 +283,6 @@ export {
intComma,
pathForRun,
geoJsonForRuns,
geoJsonForMap,
titleForRun,
filterYearRuns,
filterCityRuns,
Expand Down
Loading

0 comments on commit 1d8e0ad

Please sign in to comment.