refactor(chart): lazy load Chart.js with suspense

- chore: add ico favicon
- chore: lint
- chore: add react-hooks lint rules
This commit is contained in:
Haishan 2018-10-30 23:37:42 +08:00
parent d247e37890
commit 7f75345c03
12 changed files with 81 additions and 67 deletions

View file

@ -10,9 +10,9 @@ parser: babel-eslint
plugins: plugins:
- import - import
- react - react
- react-hooks
- jest - jest
extends: extends:
- eslint:recommended - eslint:recommended
- plugin:import/errors - plugin:import/errors
@ -31,3 +31,4 @@ rules:
react/jsx-uses-react: "error" react/jsx-uses-react: "error"
react/jsx-uses-vars: "error" react/jsx-uses-vars: "error"
react/react-in-jsx-scope: "error" react/react-in-jsx-scope: "error"
react-hooks/rules-of-hooks: error

BIN
assets/yacd.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -40,6 +40,7 @@
"modern-normalize": "^0.5.0", "modern-normalize": "^0.5.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react": "^16.7.0-alpha.0", "react": "^16.7.0-alpha.0",
"react-cache": "^2.0.0-alpha.0",
"react-dom": "^16.7.0-alpha.0", "react-dom": "^16.7.0-alpha.0",
"react-modal": "^3.6.1", "react-modal": "^3.6.1",
"react-redux": "^6.0.0-alpha.2a2f108", "react-redux": "^6.0.0-alpha.2a2f108",
@ -71,6 +72,7 @@
"eslint-plugin-import": "2.14.0", "eslint-plugin-import": "2.14.0",
"eslint-plugin-jest": "^21.26.1", "eslint-plugin-jest": "^21.26.1",
"eslint-plugin-react": "7.11.1", "eslint-plugin-react": "7.11.1",
"eslint-plugin-react-hooks": "^0.0.0",
"file-loader": "^2.0.0", "file-loader": "^2.0.0",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"husky": "^1.0.1", "husky": "^1.0.1",

View file

@ -26,6 +26,7 @@ let even = false;
const store = { const store = {
logs: [], logs: [],
size: Size, size: Size,
fetched: false,
subscribers: [], subscribers: [],
appendData(o) { appendData(o) {
const now = new Date(); const now = new Date();
@ -68,14 +69,18 @@ function pump(reader) {
}); });
} }
let fetched = false;
function fetchLogs() { function fetchLogs() {
if (fetched) return store; if (store.fetched) return store;
store.fetched = true;
const { url, init } = getURLAndInit(); const { url, init } = getURLAndInit();
fetch(url, init).then(response => { fetch(url, init)
fetched = true; .then(response => {
const reader = response.body.getReader(); const reader = response.body.getReader();
pump(reader); pump(reader);
})
.catch(err => {
store.fetched = false;
console.log('Error', err);
}); });
return store; return store;
} }

View file

@ -1,8 +1,9 @@
import React from 'react'; import React, { Suspense } from 'react';
import ContentHeader from 'c/ContentHeader'; import ContentHeader from 'c/ContentHeader';
import TrafficChart from 'c/TrafficChart'; import TrafficChart from 'c/TrafficChart';
import TrafficNow from 'c/TrafficNow'; import TrafficNow from 'c/TrafficNow';
import Loading from 'c/Loading';
import s0 from 'c/Home.module.scss'; import s0 from 'c/Home.module.scss';
export default function Home() { export default function Home() {
@ -14,7 +15,9 @@ export default function Home() {
<TrafficNow /> <TrafficNow />
</div> </div>
<div className={s0.chart}> <div className={s0.chart}>
<Suspense fallback={<Loading height="200px" />} maxDuration={10}>
<TrafficChart /> <TrafficChart />
</Suspense>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,14 +1,19 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import style from './Loading.module.scss'; import s0 from './Loading.module.scss';
const Loading = () => { const Loading = ({ height }) => {
const style = height ? { height } : {};
return ( return (
<div className={style.loading}> <div className={s0.loading} style={style}>
<div className={style.left + ' ' + style.circle} /> <div className={s0.pulse} />
<div className={style.right + ' ' + style.circle} />
</div> </div>
); );
}; };
Loading.propTypes = {
height: PropTypes.string
};
export default Loading; export default Loading;

View file

@ -1,50 +1,31 @@
$color1: #2a477a; // $color1: #2a477a;
$color2: #dddddd; $color1: #dddddd;
@keyframes moveRight { $size: 40px;
0% {
transform: translate(-50px);
}
100% {
transform: translate(10px);
}
}
@keyframes moveLeft {
0% {
transform: translate(50px);
}
100% {
transform: translate(-10px);
}
}
.loading { .loading {
position: relative;
width: 100%; width: 100%;
height: 300px; height: 100%;
margin: 0 auto; display: flex;
height: 30vh; justify-content: center;
align-items: center;
} }
.circle { .pulse {
width: 40px; width: $size;
height: 40px; height: $size;
border-radius: 50%; margin: 10px;
position: absolute;
top: 50%;
}
.left {
background-color: $color1; background-color: $color1;
left: 50%; border-radius: 100%;
animation: moveRight 1s ease-in-out 0s infinite alternate; animation: pulseScaleOut 1s infinite ease-in-out;
} }
.right { @keyframes pulseScaleOut {
background-color: $color2; 0% {
right: 50%; transform: scale(0);
animation: moveLeft 1s ease-in-out 0s infinite alternate; }
100% {
transform: scale(1);
opacity: 0;
}
} }

View file

@ -47,7 +47,7 @@ export default function Logs() {
const x = fetchLogs(); const x = fetchLogs();
setLogs(x.logs); setLogs(x.logs);
return x.subscribe(() => setLogs(x.logs)); return x.subscribe(() => setLogs(x.logs));
}); }, []);
return ( return (
<div> <div>

View file

@ -1,8 +1,12 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import Chart from 'chart.js/dist/Chart.min.js';
import prettyBytes from 'm/pretty-bytes'; import prettyBytes from 'm/pretty-bytes';
import { fetchData } from '../api/traffic'; import { fetchData } from '../api/traffic';
import { unstable_createResource as createResource } from 'react-cache';
// const delay = ms => new Promise(r => setTimeout(r, ms));
const chartJSResource = createResource(() => {
return import('chart.js/dist/Chart.min.js').then(c => c.default);
});
const colorCombo = { const colorCombo = {
0: { 0: {
@ -108,13 +112,15 @@ const options = {
}; };
const chartWrapperStyle = { const chartWrapperStyle = {
// make chartjs chart responsive
position: 'relative', position: 'relative',
width: '90%' width: '90%'
}; };
export default function TrafficChart() { export default function TrafficChart() {
const Chart = chartJSResource.read();
useEffect(() => { useEffect(() => {
const ctx = document.getElementById('myChart').getContext('2d'); const ctx = document.getElementById('trafficChart').getContext('2d');
const traffic = fetchData(); const traffic = fetchData();
const data = { const data = {
labels: traffic.labels, labels: traffic.labels,
@ -139,7 +145,7 @@ export default function TrafficChart() {
return ( return (
<div style={chartWrapperStyle}> <div style={chartWrapperStyle}>
<canvas id="myChart" /> <canvas id="trafficChart" />
</div> </div>
); );
} }

View file

@ -2,13 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="shortcut icon" href="yacd.ico">
<link rel="icon" type="image/png" sizes="64x64" href="yacd-64.png">
<link rel="icon" type="image/png" sizes="128x128" href="yacd-128.png">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name=viewport content="width=device-width, initial-scale=1"> <meta name=viewport content="width=device-width, initial-scale=1">
<meta name="application-name" content="yacd"> <meta name="application-name" content="yacd">
<meta name="description" content="Yet Another Clash Dashboard"> <meta name="description" content="Yet Another Clash Dashboard">
<meta name="theme-color" content="#202020"> <meta name="theme-color" content="#202020">
<link id="favicon" rel="icon" type="image/png" sizes="64x64" href="yacd-64.png">
<link id="favicon" rel="icon" type="image/png" sizes="128x128" href="yacd-128.png">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
<link rel="prefetch" href="https://cdn.jsdelivr.net/npm/@hsjs/fonts@0.0.1/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2"> <link rel="prefetch" href="https://cdn.jsdelivr.net/npm/@hsjs/fonts@0.0.1/robotomono/v5/L0x5DF4xlVMF-BfR8bXMIjhLq3-cXbKD.woff2">
<meta property="og:image" content="https://user-images.githubusercontent.com/1166872/47304841-536f3d80-d65a-11e8-8908-1917127dafc5.png"> <meta property="og:image" content="https://user-images.githubusercontent.com/1166872/47304841-536f3d80-d65a-11e8-8908-1917127dafc5.png">

View file

@ -106,11 +106,11 @@ module.exports = {
// test: /[\\/]node_modules[\\/](core-js)[\\/]/, // test: /[\\/]node_modules[\\/](core-js)[\\/]/,
// chunks: 'all' // chunks: 'all'
// }, // },
chartjs: { // chartjs: {
test: /[\\/]node_modules[\\/]chart\.js[\\/]/, // test: /[\\/]node_modules[\\/]chart\.js[\\/]/,
// name: 'chartjs', // // name: 'chartjs',
chunks: 'all' // chunks: 'all'
}, // },
react: { react: {
test: /[\\/]node_modules[\\/](react-dom|react|redux|react-router|react-router-dom|schedule|react-redux|react-modal)[\\/]/, test: /[\\/]node_modules[\\/](react-dom|react|redux|react-router|react-router-dom|schedule|react-redux|react-modal)[\\/]/,
chunks: 'all' chunks: 'all'

View file

@ -2919,6 +2919,11 @@ eslint-plugin-jest@^21.26.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.26.2.tgz#5b24413970e83e2c5b87c5c047a08a4881783605" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.26.2.tgz#5b24413970e83e2c5b87c5c047a08a4881783605"
integrity sha512-SCTBC6q182D4qQlQAN81D351jdte/YwTMo4f+l19Gvh1VemaNZP7ak3MLLvw6xkL9dO2FxVjCLk5DCdl1KfdLw== integrity sha512-SCTBC6q182D4qQlQAN81D351jdte/YwTMo4f+l19Gvh1VemaNZP7ak3MLLvw6xkL9dO2FxVjCLk5DCdl1KfdLw==
eslint-plugin-react-hooks@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-0.0.0.tgz#9988f14082a159931c3dfa9ba699130457da927a"
integrity sha512-SXyU7C3E8AJbXKMdb10P/zHazcxzfuWR5OFwAVZKXVU7P/H56NLszVG6WdQBo9Pt80FfnPXtUGGbWhs3/98N4w==
eslint-plugin-react@7.11.1: eslint-plugin-react@7.11.1:
version "7.11.1" version "7.11.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz#c01a7af6f17519457d6116aa94fc6d2ccad5443c" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz#c01a7af6f17519457d6116aa94fc6d2ccad5443c"
@ -6603,6 +6608,11 @@ rc@^1.2.7:
minimist "^1.2.0" minimist "^1.2.0"
strip-json-comments "~2.0.1" strip-json-comments "~2.0.1"
react-cache@^2.0.0-alpha.0:
version "2.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/react-cache/-/react-cache-2.0.0-alpha.0.tgz#d02d16a4565fd9f12478a2a41e980ba4fcbab4d5"
integrity sha512-o7nA1dIbi6wOoIoQPQBL58CgIQ4tCA3itIR3WlE2VHMIFkIXbvsMyolg+Q9wDVUlCvU6b2n40RTTv8vOmjXCOQ==
react-dom@^16.7.0-alpha.0: react-dom@^16.7.0-alpha.0:
version "16.7.0-alpha.0" version "16.7.0-alpha.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0-alpha.0.tgz#8379158d4c76d63c989f325f45dfa5762582584f" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0-alpha.0.tgz#8379158d4c76d63c989f325f45dfa5762582584f"