bruAristimunha commited on
Commit
ba02f4d
·
1 Parent(s): 31167c5

Fix OOM build: load Plotly from CDN instead of npm bundle

Browse files

The plotly.js-basic-dist npm package (~1.4MB gzipped) caused the
Docker build to OOM on the HF Space cpu-basic tier. Replace it with
a lightweight CDN loader that fetches plotly-basic at runtime. This
removes the heavy webpack processing entirely and drops the JS bundle
from 1.7MB to 250KB.

frontend/package.json CHANGED
@@ -20,10 +20,8 @@
20
  "cors": "^2.8.5",
21
  "express": "^4.18.2",
22
  "http-proxy-middleware": "^2.0.6",
23
- "plotly.js-basic-dist": "^3.4.0",
24
  "react": "^18.3.1",
25
  "react-dom": "^18.3.1",
26
- "react-plotly.js": "^2.6.0",
27
  "react-router-dom": "^6.28.0",
28
  "react-scripts": "5.0.1",
29
  "serve-static": "^1.15.0",
 
20
  "cors": "^2.8.5",
21
  "express": "^4.18.2",
22
  "http-proxy-middleware": "^2.0.6",
 
23
  "react": "^18.3.1",
24
  "react-dom": "^18.3.1",
 
25
  "react-router-dom": "^6.28.0",
26
  "react-scripts": "5.0.1",
27
  "serve-static": "^1.15.0",
frontend/src/pages/LeaderboardPage/components/Leaderboard/components/Chart/Chart.js CHANGED
@@ -1,12 +1,80 @@
1
- import React, { useState, lazy, Suspense } from "react";
2
  import { Box, Typography, Chip, CircularProgress } from "@mui/material";
3
  import { useTheme } from "@mui/material/styles";
4
  import useChartData from "./useChartData";
5
  import { DATASET_OPTIONS, REFERENCE_LINE_COLOR } from "../../constants/chartDefaults";
6
 
7
- const Plot = lazy(() =>
8
- import("react-plotly.js").then((mod) => ({ default: mod.default }))
9
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  const Chart = ({ filteredData, scoreDisplay }) => {
12
  const theme = useTheme();
@@ -124,32 +192,16 @@ const Chart = ({ filteredData, scoreDisplay }) => {
124
  </Box>
125
 
126
  {/* Plot */}
127
- <Suspense
128
- fallback={
129
- <Box
130
- sx={{
131
- display: "flex",
132
- justifyContent: "center",
133
- alignItems: "center",
134
- minHeight: 400,
135
- }}
136
- >
137
- <CircularProgress size={28} />
138
- </Box>
139
- }
140
- >
141
- <Plot
142
- data={traces}
143
- layout={themedLayout}
144
- config={{
145
- responsive: true,
146
- displayModeBar: false,
147
- staticPlot: false,
148
- }}
149
- style={{ width: "100%" }}
150
- useResizeHandler
151
- />
152
- </Suspense>
153
 
154
  {/* Reference line legend */}
155
  <Box
 
1
+ import React, { useState, useEffect, useRef, useCallback } from "react";
2
  import { Box, Typography, Chip, CircularProgress } from "@mui/material";
3
  import { useTheme } from "@mui/material/styles";
4
  import useChartData from "./useChartData";
5
  import { DATASET_OPTIONS, REFERENCE_LINE_COLOR } from "../../constants/chartDefaults";
6
 
7
+ const PLOTLY_CDN = "https://cdn.plot.ly/plotly-basic-2.35.2.min.js";
8
+
9
+ // Load Plotly from CDN once, return a promise
10
+ let plotlyPromise = null;
11
+ const loadPlotly = () => {
12
+ if (window.Plotly) return Promise.resolve(window.Plotly);
13
+ if (plotlyPromise) return plotlyPromise;
14
+ plotlyPromise = new Promise((resolve, reject) => {
15
+ const script = document.createElement("script");
16
+ script.src = PLOTLY_CDN;
17
+ script.async = true;
18
+ script.onload = () => resolve(window.Plotly);
19
+ script.onerror = () => reject(new Error("Failed to load Plotly"));
20
+ document.head.appendChild(script);
21
+ });
22
+ return plotlyPromise;
23
+ };
24
+
25
+ const PlotlyChart = ({ data, layout, config, style }) => {
26
+ const containerRef = useRef(null);
27
+ const [Plotly, setPlotly] = useState(window.Plotly || null);
28
+ const [loading, setLoading] = useState(!window.Plotly);
29
+
30
+ useEffect(() => {
31
+ if (!Plotly) {
32
+ loadPlotly()
33
+ .then((P) => {
34
+ setPlotly(P);
35
+ setLoading(false);
36
+ })
37
+ .catch(() => setLoading(false));
38
+ }
39
+ }, [Plotly]);
40
+
41
+ const draw = useCallback(() => {
42
+ if (Plotly && containerRef.current && data?.length) {
43
+ Plotly.react(containerRef.current, data, layout, config);
44
+ }
45
+ }, [Plotly, data, layout, config]);
46
+
47
+ useEffect(() => {
48
+ draw();
49
+ }, [draw]);
50
+
51
+ // Resize handler
52
+ useEffect(() => {
53
+ if (!Plotly || !containerRef.current) return;
54
+ const ro = new ResizeObserver(() => {
55
+ if (containerRef.current) Plotly.Plots.resize(containerRef.current);
56
+ });
57
+ ro.observe(containerRef.current);
58
+ return () => ro.disconnect();
59
+ }, [Plotly]);
60
+
61
+ if (loading) {
62
+ return (
63
+ <Box
64
+ sx={{
65
+ display: "flex",
66
+ justifyContent: "center",
67
+ alignItems: "center",
68
+ minHeight: 400,
69
+ }}
70
+ >
71
+ <CircularProgress size={28} />
72
+ </Box>
73
+ );
74
+ }
75
+
76
+ return <div ref={containerRef} style={style} />;
77
+ };
78
 
79
  const Chart = ({ filteredData, scoreDisplay }) => {
80
  const theme = useTheme();
 
192
  </Box>
193
 
194
  {/* Plot */}
195
+ <PlotlyChart
196
+ data={traces}
197
+ layout={themedLayout}
198
+ config={{
199
+ responsive: true,
200
+ displayModeBar: false,
201
+ staticPlot: false,
202
+ }}
203
+ style={{ width: "100%" }}
204
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
  {/* Reference line legend */}
207
  <Box