Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/reduce bundle size #106

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
78fa2fc
feat: break into smaller bundles based on pages
icyJoseph Jul 3, 2020
7b95d18
feat: lazy load Hexapod and use semantic routing to display components
icyJoseph Jul 6, 2020
bb32e8d
chore: Add jsdom-sixteen and bump up @testing-library/react
icyJoseph Jul 6, 2020
971eb20
test: Use waitFor to deal with Suspenseful modules
icyJoseph Jul 6, 2020
fd2f65b
test: Update test, the plot no longer renders hidden on landing page
icyJoseph Jul 6, 2020
66fd770
feat: Start to load Plotly before first DOM commit
icyJoseph Jul 6, 2020
d178edb
chore: Configure jest collectCoverageFrom
icyJoseph Jul 7, 2020
fd9e0e9
feat: Implement a sleep util w/ test
icyJoseph Jul 7, 2020
f53cc02
test: Pass testId to mocked Plotly div
icyJoseph Jul 7, 2020
9af7154
feat: Lazy load Plotly with retry option
icyJoseph Jul 7, 2020
c10bfd4
Merge branch 'master' into feat/reduce-bundle-size
icyJoseph Jul 7, 2020
2f5ccd4
refactor: Shrink App's render method
icyJoseph Jul 7, 2020
f0c087e
test: fix coverage collect globbing pattern
icyJoseph Jul 7, 2020
429a4a9
feat: render HexapodPlot ASAP, but hide it if not needed
icyJoseph Jul 7, 2020
195cb69
Merge branch 'master' into feat/reduce-bundle-size
icyJoseph Jul 7, 2020
ffc108a
fix: Update ikParams and PatternPose function names
icyJoseph Jul 7, 2020
da2d6f5
feat: Redirect back to Root on 404
icyJoseph Jul 8, 2020
5ad2a60
feat: Less aggressive preloading
icyJoseph Jul 8, 2020
057a30d
Merge branch 'master' into feat/reduce-bundle-size
icyJoseph Jul 8, 2020
1f40226
refactor: Remove duplicated function names
icyJoseph Jul 8, 2020
c077bf2
refactor: Various fixes, and code quality improvements
icyJoseph Jul 8, 2020
f9b9bb6
refactor: Build a hooks -> render props -> HoC pattern to increase co…
icyJoseph Jul 8, 2020
7d9f154
refactor: Hoist handlers Provider to App
icyJoseph Jul 8, 2020
3dea501
refactor: LegPattern Page does not need params props anymore
icyJoseph Jul 8, 2020
a4c652a
refactor: Breakout HexapodParams into a Provider pattern
icyJoseph Jul 8, 2020
84d7856
chore: Merge master including global state shrinking
icyJoseph Jul 8, 2020
30a110c
fix: Update PoseTable source of props and AlertBox message body
icyJoseph Jul 8, 2020
edeb6e3
refactor: Turn App into a functional component
icyJoseph Jul 8, 2020
a51f61f
refactor: Move away custom hooks
icyJoseph Jul 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/react": "^10.4.4",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bumping this up 👍 💯

"@testing-library/user-event": "^7.1.2",
"jest-canvas-mock": "^2.2.0",
"plotly.js-gl3d-dist-min": "^1.54.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-ga": "^3.0.0",
"react-icons": "^3.10.0",
"react-markdown": "^4.3.1",
"react-plotly.js": "^2.4.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
Expand All @@ -22,7 +21,8 @@
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "react-scripts test --env=jest-environment-jsdom-sixteen",
"test:coverage": "react-scripts test --env=jest-environment-jsdom-sixteen --collectCoverage --watchAll=false",
"eject": "react-scripts eject"
},
"eslintConfig": {
Expand All @@ -41,6 +41,15 @@
]
},
"devDependencies": {
"jest-environment-jsdom-sixteen": "^1.0.3",
"prettier": "2.0.5"
},
"jest": {
"collectCoverageFrom": [
"src/{components,hexapod}/**/*.{js,jsx,ts,tsx}",
"src/loadables.js",
"src/App.js",
"src/routes.js"
]
}
}
134 changes: 33 additions & 101 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,11 @@ import { BrowserRouter as Router, Route, Switch } from "react-router-dom"
import ReactGA from "react-ga"
import { VirtualHexapod, getNewPlotParams } from "./hexapod"
import * as defaults from "./templates"
import { SECTION_NAMES, PATHS } from "./components/vars"
import {
PoseTable,
Nav,
NavDetailed,
HexapodPlot,
DimensionsWidget,
AlertBox,
} from "./components"
import {
ForwardKinematicsPage,
InverseKinematicsPage,
LandingPage,
LegPatternPage,
} from "./components/pages"
import { SECTION_NAMES, PATH_LINKS } from "./components/vars"
import { Nav } from "./components"
import NoMatch from "./components/pages/NoMatch"

import Routes from "./routes"

ReactGA.initialize("UA-170794768-1", {
//debug: true,
Expand All @@ -28,8 +18,7 @@ ReactGA.initialize("UA-170794768-1", {
class App extends React.Component {
state = {
currentPage: SECTION_NAMES.LandingPage,
inHexapodPage: false,
showPoseMessage: true,
showPoseMessage: false,
showInfo: false,
info: {},

Expand All @@ -56,15 +45,15 @@ class App extends React.Component {

if (pageName === SECTION_NAMES.landingPage) {
this.setState({
inHexapodPage: false,
currentPage: pageName,
showInfo: false,
showPoseMessage: false,
})
return
}

this.setState({
inHexapodPage: true,
currentPage: pageName,
showInfo: false,
showPoseMessage: false,
hexapodParams: { ...this.state.hexapodParams, pose: defaults.DEFAULT_POSE },
Expand Down Expand Up @@ -123,92 +112,35 @@ class App extends React.Component {
updatePose = pose => this.updatePlot(this.state.hexapodParams.dimensions, pose)

/* * * * * * * * * * * * * *
* Control display of widgets
* Layout
* * * * * * * * * * * * * */

mightShowMessage = () =>
this.state.showInfo ? <AlertBox info={this.state.info} /> : null

mightShowDetailedNav = () => (this.state.inHexapodPage ? <NavDetailed /> : null)

mightShowPoseTable = () => {
if (this.state.showPoseMessage) {
return <PoseTable pose={this.state.hexapodParams.pose} />
render() {
const { plot, ...rest } = this.state
const routeProps = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could there be a better way to structure this than pushing about 15 props on route?

Maybe using contexts ?

Copy link
Contributor Author

@icyJoseph icyJoseph Jul 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I actually a branch where I've built the Provider pattern, so that'll come up next.

I just needed to understand correctly which pieces of state are running through the App. For instance in that branch I have it divided as hexapod params, plot data, and the rest of settings. And that results in 3 context providers.

Edit
Forgot to say here that the main reason I did this strange props arrangement was to keep the render method under 25 lines and pass the code checks.

...rest,
...plot,
revision: plot.revisionCounter,
onRelayout: this.logCameraView,
updatePose: this.updatePose,
updateIkParams: this.updateIkParams,
updatePatternPose: this.updatePatternPose,
updateDimensions: this.updateDimensions,
}
return (
<Router>
<Nav />
<Switch>
<Route path={PATH_LINKS.map(({ path }) => path)} exact>
<Routes {...routeProps} onPageLoad={this.onPageLoad} />
</Route>
<Route>
<NoMatch />
</Route>
</Switch>
</Router>
)
}

mightShowDimensions = () => {
if (this.state.inHexapodPage) {
return (
<DimensionsWidget
params={{ dimensions: this.state.hexapodParams.dimensions }}
onUpdate={this.updateDimensions}
/>
)
}
}

mightShowPlot = () => (
<div className={this.state.inHexapodPage ? "plot border" : "no-display"}>
<HexapodPlot
data={this.state.plot.data}
layout={this.state.plot.layout}
onRelayout={this.logCameraView}
revision={this.state.plot.revisionCounter}
/>
</div>
)

/* * * * * * * * * * * * * *
* Pages
* * * * * * * * * * * * * */

showPage = () => (
<Switch>
<Route path="/" exact>
<LandingPage onMount={this.onPageLoad} />
</Route>
<Route path={PATHS.forwardKinematics.path}>
<ForwardKinematicsPage
params={{ pose: this.state.hexapodParams.pose }}
onUpdate={this.updatePose}
onMount={this.onPageLoad}
/>
</Route>
<Route path={PATHS.inverseKinematics.path}>
<InverseKinematicsPage
params={{
dimensions: this.state.hexapodParams.dimensions,
}}
onUpdate={this.updateIk}
onMount={this.onPageLoad}
/>
</Route>
<Route path={PATHS.legPatterns.path}>
<LegPatternPage onUpdate={this.updatePose} onMount={this.onPageLoad} />
</Route>
</Switch>
)

/* * * * * * * * * * * * * *
* Layout
* * * * * * * * * * * * * */

render = () => (
<Router>
<Nav />
<div className="main content">
<div className="sidebar column-container cell">
{this.mightShowDimensions()}
{this.showPage()}
{this.mightShowPoseTable()}
{this.mightShowMessage()}
</div>
{this.mightShowPlot()}
</div>
{this.mightShowDetailedNav()}
</Router>
)
}

export default App
4 changes: 3 additions & 1 deletion src/__mocks__/react-plotly.js/factory.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from "react"

const createPlotlyComponent = () => ({ style }) => <div style={style}></div>
const createPlotlyComponent = () => ({ style, testId }) => (
<div style={style} data-testid={testId}></div>
)

export default createPlotlyComponent
76 changes: 67 additions & 9 deletions src/components/HexapodPlot.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,78 @@
import React from "react"
import Plotly from "plotly.js-gl3d-dist-min"
import React, { useCallback, useLayoutEffect, useState, useRef } from "react"
import createPlotlyComponent from "react-plotly.js/factory"
const Plot = createPlotlyComponent(Plotly)

const HexapodPlot = props => {
import { sleep } from "../utils"

const PlotlyPromise = () =>
import(
/* webpackChunkName: "Plotly-gl-3d", webpackPreload: true */ "plotly.js-gl3d-dist-min"
).then(Plotly => Plotly.default)

export const HexapodPlot = ({ data, layout, onRelayout, revision, promise }) => {
const ref = useRef()
const loadingRef = useRef()
const promiseRef = useRef(promise)
const [ready, setReady] = useState(false)
const [error, setError] = useState(false)

const loader = useCallback(() => {
setError(false)

promiseRef
.current()
.then(Plotly => {
ref.current = createPlotlyComponent(Plotly)
})
.then(sleep(250))
.then(() => {
if (loadingRef.current) {
setReady(true)
}
})
.catch(() => setError(true))
}, [])

useLayoutEffect(() => {
loader()
}, [loader])

if (error) {
return (
<div>
<h2>Error Loading Plotly</h2>
<button className="button" onClick={loader}>
Retry
</button>
</div>
)
}

if (!ready) {
return (
<div ref={loadingRef}>
<h2>Loading...</h2>
</div>
)
}

const Plot = ref.current

return (
<Plot
data={props.data}
layout={props.layout}
data={data}
layout={layout}
useResizeHandler={true}
style={{ height: "100%", width: "100%" }}
config={{ displaylogo: false, responsive: true }}
onRelayout={props.onRelayout}
revision={props.revision}
onRelayout={onRelayout}
revision={revision}
testId="plot"
/>
)
}

export default HexapodPlot
const HexapodPlotWithPlotlyPromise = props => (
<HexapodPlot {...props} promise={PlotlyPromise} />
)

export default HexapodPlotWithPlotlyPromise
18 changes: 9 additions & 9 deletions src/components/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { Link } from "react-router-dom"
const NAV_BULLETS_PREFIX = "navBullet"
const NAV_DETAILED_PREFIX = "navDetailed"

const BulletPageLink = ({ path, description }) => (
const BulletPageLink = React.memo(({ path, description }) => (
<li>
<Link to={path} className="link-icon">
<span>
{ICON_COMPONENTS.circle} {description}
</span>
</Link>
</li>
)
))

const BulletUrlLink = ({ path, description, icon }) => (
const BulletUrlLink = React.memo(({ path, description, icon }) => (
<li>
<a
href={path}
Expand All @@ -29,9 +29,9 @@ const BulletUrlLink = ({ path, description, icon }) => (
}
/>
</li>
)
))

const NavBullets = () => (
const NavBullets = React.memo(() => (
<ul className="row-container no-bullet top-bar">
{URL_LINKS.map(link => (
<BulletUrlLink
Expand All @@ -45,9 +45,9 @@ const NavBullets = () => (
<BulletPageLink key={NAV_BULLETS_PREFIX + link.path} path={link.path} />
))}
</ul>
)
))

const NavDetailed = () => (
const NavDetailed = React.memo(() => (
<footer>
<nav id="nav">
<ul className="column-container no-bullet">
Expand All @@ -70,8 +70,8 @@ const NavDetailed = () => (
</ul>
</nav>
</footer>
)
))

const Nav = () => <NavBullets />
const Nav = React.memo(() => <NavBullets />)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for using React.memo in Nav 😄


export { Nav, NavDetailed }
Loading