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

Modernize client/components #725

Open
wants to merge 1 commit into
base: bundlephobia
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 12 additions & 15 deletions client/components/AutocompleteInputBox/AutocompleteInputBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React from 'react'
import cx from 'classnames'

import { WithClassName } from '../../../types'
Expand All @@ -8,18 +8,15 @@ type AutocompleteInputBoxProps = React.PropsWithChildren &
footer?: React.ReactNode
}

class AutocompleteInputBox extends Component<AutocompleteInputBoxProps> {
render() {
const { children, footer, className } = this.props
return (
<div className={cx('autocomplete-input-box', className)}>
{children}
{footer && (
<div className="autocomplete-input-box__footer">{footer}</div>
)}
</div>
)
}
export function AutocompleteInputBox({
children,
footer,
className,
}: AutocompleteInputBoxProps) {
return (
<div className={cx('autocomplete-input-box', className)}>
{children}
{footer && <div className="autocomplete-input-box__footer">{footer}</div>}
</div>
)
}

export default AutocompleteInputBox
4 changes: 1 addition & 3 deletions client/components/AutocompleteInputBox/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import AutocompleteInputBox from './AutocompleteInputBox'

export default AutocompleteInputBox
export { AutocompleteInputBox } from './AutocompleteInputBox'
265 changes: 119 additions & 146 deletions client/components/BarGraph/BarGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,70 @@
import React, { PureComponent } from 'react'
import React, { useMemo } from 'react'

import { formatSize } from '../../../utils'
import TreeShakeIcon from '../Icons/TreeShakeIcon'
import SideEffectIcon from '../Icons/SideEffectIcon'
import { BarVersion } from '../BarVersion/BarVersion'
import { BarVersion } from '../BarVersion'
import { Reading } from './types'
import { useBarGraph } from './hooks/useBarGraph'

export type Reading = {
version: string
size: number
gzip: number
disabled: boolean
hasSideEffects: boolean
hasJSModule: boolean
hasJSNext: boolean
isModuleType: boolean
}

type BarGraphProps = {
interface BarGraphProps {
readings: Reading[]
onBarClick: (reading: Reading) => void
}

export default class BarGraph extends PureComponent<BarGraphProps> {
getScale = () => {
const { readings } = this.props

const gzipValues = readings
.filter(reading => !reading.disabled)
.map(reading => reading.gzip)

const sizeValues = readings
.filter(reading => !reading.disabled)
.map(reading => reading.size)

const maxValue = Math.max(...[...gzipValues, ...sizeValues])
return 100 / maxValue
}

getFirstSideEffectFreeIndex = () => {
const { readings } = this.props
const sideEffectFreeIntroducedRecently = !readings.every(
reading => !reading.hasSideEffects
)
const firstSideEffectFreeIndex = readings.findIndex(
reading => !(reading.disabled || reading.hasSideEffects)
)

return sideEffectFreeIntroducedRecently ? firstSideEffectFreeIndex : -1
}

getFirstTreeshakeableIndex = () => {
const { readings } = this.props
const treeshakingIntroducedRecently = !readings.every(
reading => reading.hasJSModule
)
const firstTreeshakingIndex = readings.findIndex(
reading =>
!reading.disabled &&
(reading.hasJSModule || reading.hasJSNext || reading.isModuleType)
)
export function BarGraph({ onBarClick, readings }: BarGraphProps) {
const { firstSideEffectFreeIndex, firstTreeshakeableIndex, graphScale } =
useBarGraph({ readings })

return (
<div className="bar-graph-container">
<figure className="bar-graph">
{readings.map((reading, index) =>
reading.disabled ? (
<DisabledBar
key={reading.version}
reading={reading}
onBarClick={onBarClick}
/>
) : (
<ActiveBar
key={reading.version}
reading={reading}
graphScale={graphScale}
options={{
isFirstTreeshakeable: index === firstTreeshakeableIndex,
isFirstSideEffectFree: index === firstSideEffectFreeIndex,
}}
onBarClick={onBarClick}
/>
)
)}
</figure>
<div className="bar-graph__legend">
<div className="bar-graph__legend__bar1">
<div className="bar-graph__legend__colorbox" />
Min
</div>
<div className="bar-graph__legend__bar2">
<div className="bar-graph__legend__colorbox" />
GZIP
</div>
</div>
</div>
)
}

return treeshakingIntroducedRecently ? firstTreeshakingIndex : -1
}
interface DisabledBarProps {
reading: Reading
onBarClick: (reading: Reading) => void
}

renderDisabledBar = (reading: Reading) => (
function DisabledBar({ reading, onBarClick }: DisabledBarProps) {
return (
<div
key={reading.version}
className="bar-graph__bar-group bar-graph__bar-group--disabled"
onClick={() => this.props.onBarClick(reading)}
onClick={() => onBarClick(reading)}
>
<div
className="bar-graph__bar"
Expand All @@ -77,99 +74,75 @@ export default class BarGraph extends PureComponent<BarGraphProps> {
<BarVersion version={reading.version} />
</div>
)
}

renderActiveBar = (
reading: Reading,
scale: number,
options: { isFirstTreeshakeable: boolean; isFirstSideEffectFree: boolean }
) => {
const getTooltipMessage = (reading: Reading) => {
const formattedSize = formatSize(reading.size)
const formattedGzip = formatSize(reading.gzip)
return `Minified: ${parseFloat(formattedSize.size).toFixed(1)}${
formattedSize.unit
} | Gzipped: ${parseFloat(formattedGzip.size).toFixed(1)}${
formattedGzip.unit
}`
}

return (
<div
onClick={() => this.props.onBarClick(reading)}
key={reading.version}
className="bar-graph__bar-group"
>
<div className="bar-graph__bar-symbols">
{options.isFirstTreeshakeable && (
<div
data-balloon={`ES2015 exports introduced. ${
reading.hasSideEffects
? 'Not side-effect free yet, hence limited tree-shake ability.'
: ''
}`}
className="bar-graph__bar-symbol"
>
<TreeShakeIcon />
</div>
)}
{options.isFirstSideEffectFree && (
<div
data-balloon={`Was marked side-effect free. ${
reading.hasJSNext || reading.hasJSModule || reading.isModuleType
? 'Supports ES2015 exports also, hence fully tree-shakeable'
: "Doesn't export ESM yet, limited tree-shake ability"
}`}
className="bar-graph__bar-symbol"
>
<SideEffectIcon />
</div>
)}
</div>

<div
className="bar-graph__bar"
style={{ height: `${(reading.size - reading.gzip) * scale}%` }}
data-balloon={getTooltipMessage(reading)}
/>
<div
className="bar-graph__bar2"
style={{ height: `${reading.gzip * scale}%` }}
data-balloon={getTooltipMessage(reading)}
/>
<BarVersion version={reading.version} />
</div>
)
}

render() {
const { readings } = this.props
const graphScale = this.getScale()
const firstTreeshakeableIndex = this.getFirstTreeshakeableIndex()
const firstSideEffectFreeIndex = this.getFirstSideEffectFreeIndex()
interface ActiveBarProps {
reading: Reading
graphScale: number
options: { isFirstTreeshakeable: boolean; isFirstSideEffectFree: boolean }
onBarClick: (reading: Reading) => void
}

return (
<div className="bar-graph-container">
<figure className="bar-graph">
{readings.map((reading, index) =>
reading.disabled
? this.renderDisabledBar(reading)
: this.renderActiveBar(reading, graphScale, {
isFirstTreeshakeable: index === firstTreeshakeableIndex,
isFirstSideEffectFree: index === firstSideEffectFreeIndex,
})
)}
</figure>
<div className="bar-graph__legend">
<div className="bar-graph__legend__bar1">
<div className="bar-graph__legend__colorbox" />
Min
function ActiveBar({
reading,
options,
graphScale,
onBarClick,
}: ActiveBarProps) {
const tooltipMessage = useMemo(() => {
const formattedSize = formatSize(reading.size)
const formattedGzip = formatSize(reading.gzip)
return `Minified: ${parseFloat(formattedSize.size).toFixed(1)}${
formattedSize.unit
} | Gzipped: ${parseFloat(formattedGzip.size).toFixed(1)}${
formattedGzip.unit
}`
}, [reading])

return (
<div
onClick={() => onBarClick(reading)}
key={reading.version}
className="bar-graph__bar-group"
>
<div className="bar-graph__bar-symbols">
{options.isFirstTreeshakeable && (
<div
data-balloon={`ES2015 exports introduced. ${
reading.hasSideEffects
? 'Not side-effect free yet, hence limited tree-shake ability.'
: ''
}`}
className="bar-graph__bar-symbol"
>
<TreeShakeIcon />
</div>
<div className="bar-graph__legend__bar2">
<div className="bar-graph__legend__colorbox" />
GZIP
)}
{options.isFirstSideEffectFree && (
<div
data-balloon={`Was marked side-effect free. ${
reading.hasJSNext || reading.hasJSModule || reading.isModuleType
? 'Supports ES2015 exports also, hence fully tree-shakeable'
: "Doesn't export ESM yet, limited tree-shake ability"
}`}
className="bar-graph__bar-symbol"
>
<SideEffectIcon />
</div>
</div>
)}
</div>
)
}

<div
className="bar-graph__bar"
style={{ height: `${(reading.size - reading.gzip) * graphScale}%` }}
data-balloon={tooltipMessage}
/>
<div
className="bar-graph__bar2"
style={{ height: `${reading.gzip * graphScale}%` }}
data-balloon={tooltipMessage}
/>
<BarVersion version={reading.version} />
</div>
)
}
43 changes: 43 additions & 0 deletions client/components/BarGraph/hooks/useBarGraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useMemo } from 'react'
import { Reading } from '../types'

export function useBarGraph({ readings }: { readings: Reading[] }) {
const graphScale = useMemo(() => {
const gzipValues = readings
.filter(reading => !reading.disabled)
.map(reading => reading.gzip)

const sizeValues = readings
.filter(reading => !reading.disabled)
.map(reading => reading.size)

const maxValue = Math.max(...[...gzipValues, ...sizeValues])
return 100 / maxValue
}, [readings])

const firstSideEffectFreeIndex = useMemo(() => {
const sideEffectFreeIntroducedRecently = !readings.every(
reading => !reading.hasSideEffects
)
const firstSideEffectFreeIndex = readings.findIndex(
reading => !(reading.disabled || reading.hasSideEffects)
)

return sideEffectFreeIntroducedRecently ? firstSideEffectFreeIndex : -1
}, [readings])

const firstTreeshakeableIndex = useMemo(() => {
const treeshakingIntroducedRecently = !readings.every(
reading => reading.hasJSModule
)
const firstTreeshakingIndex = readings.findIndex(
reading =>
!reading.disabled &&
(reading.hasJSModule || reading.hasJSNext || reading.isModuleType)
)

return treeshakingIntroducedRecently ? firstTreeshakingIndex : -1
}, [readings])

return { graphScale, firstSideEffectFreeIndex, firstTreeshakeableIndex }
}
4 changes: 1 addition & 3 deletions client/components/BarGraph/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import BarGraph from './BarGraph'

export default BarGraph
export { BarGraph } from './BarGraph'
10 changes: 10 additions & 0 deletions client/components/BarGraph/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type Reading = {
version: string
size: number
gzip: number
disabled: boolean
hasSideEffects: boolean
hasJSModule: boolean
hasJSNext: boolean
isModuleType: boolean
}
1 change: 1 addition & 0 deletions client/components/BarVersion/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { BarVersion } from './BarVersion'
Loading