Slide 1

Slide 1 text

Increase the Performance of your Site with Lazy‑Loading and Code‑ Splitting

Slide 2

Slide 2 text

About me I'm Jose and I work as Engineering Manager in Spotify I like sites (building/using) with good performance @jmperezperez on Twitter

Slide 3

Slide 3 text

The era of components

Slide 4

Slide 4 text

What we'll talk about

Slide 5

Slide 5 text

Compositional Patterns

Slide 6

Slide 6 text

High Order Components const MyComponent = props => (
{props.id} - {props.name}
); // ... const ConnectedComponent = connect( mapStateToProps, mapDispatchToProps )(MyComponent);

Slide 7

Slide 7 text

Function as Child Component aka Render Callback const MyComponent = () => ( {matches => matches ? (

The document is less than 600px wide.

) : (

The document is at least 600px wide.

) } );

Slide 8

Slide 8 text

Improving performance of our sites by loading only what is needed

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Most times you would include all the scripts and CSS needed to render all sections as soon as the user visits the page. Until recently it was difficult to define a module’s dependencies, and load what was needed.

Slide 11

Slide 11 text

How likely is it for the user to see the header? What about the map?

Slide 12

Slide 12 text

Yahoo's YUI Loader Facebook's Haste, Bootloader and Primer

Slide 13

Slide 13 text

Lazy‑Loading has trade‑offs too

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Invisible content in some scenarios printing the page RSS readers SEO

Slide 17

Slide 17 text

A small component to detect when an area is visible

Slide 18

Slide 18 text

Function as Child Component aka Render Callback class Observer extends Component { constructor() { super(); this.state = { isVisible: false }; this.io = null; this.container = null; } componentDidMount() { this.io = new IntersectionObserver([entry] => { this.setState({ isVisible: entry.isIntersecting }); }, {}); this.io.observe(this.container); } componentWillUnmount() { if (this.io) { this.io.disconnect(); } } render() { return ( // we create a div to get a reference. // It's possible to use findDOMNode() to avoid

Slide 19

Slide 19 text

Using it const Page = () => {
{isVisible => } {isVisible => }
; };

Slide 20

Slide 20 text

make sure that you reserve the area for the lazy‑loaded component

Slide 21

Slide 21 text

class Map extends Component { constructor() { super(); this.state = { initialized: false }; this.map = null; } initializeMap() { this.setState({ initialized: true }); // loadScript loads an external script, its definition is not included here. loadScript('https://maps.google.com/maps/api/js?key=', () => { const latlng = new google.maps.LatLng(38.34, -0.48); const myOptions = { zoom: 15, center: latlng }; const map = new google.maps.Map(this.map, myOptions); }); } componentDidMount() { if (this.props.isVisible) { this.initializeMap(); } } componentWillReceiveProps(nextProps) { if (!this state initialized && nextProps isVisible) {

Slide 22

Slide 22 text

class Gallery extends Component { constructor() { super(); this.state = { hasBeenVisible: false }; } componentDidMount() { if (this.props.isVisible) { this.setState({ hasBeenVisible: true }); } } componentWillReceiveProps(nextProps) { if (!this.state.hasBeenVisible && nextProps.isVisible) { this.setState({ hasBeenVisible: true }); } } render() { return (

Some pictures

Picture 1 {this.state.hasBeenVisible ? ( ) : (

Slide 23

Slide 23 text

Stateless Child Components

Slide 24

Slide 24 text

const Gallery = ({ isVisible }) => (

Some pictures

Picture 1 {isVisible ? ( ) : (
)} Picture 2 {isVisible ? ( ) : (
)}

Slide 25

Slide 25 text

const Page = () => { ... {(isVisible, hasBeenVisible) => // Gallery can be now stateless } ... }

Slide 26

Slide 26 text

constructor() { super(); this.state = { hasBeenVisible: false }; this.io = null; this.container = null; } componentDidMount() { this.io = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { this.setState({ hasBeenVisible: true }); this.io.disconnect(); } }); }, {}); this.io.observe(this.container); } componentWillUnmount() { if (this.io) { this.io.disconnect(); } } render() { return (
{ this.container = div; }} > {Array.isArray(this.props.children) ? this.props.children.map(child => child(this.state.hasBeenVisible)) : this.props.children(this.state.hasBeenVisible)}
);

Slide 27

Slide 27 text

More use cases

Slide 28

Slide 28 text

super(); this.state = { progress: 0 }; this.interval = null; this.animationDuration = 2000; this.startAnimation = null; } componentWillReceiveProps(nextProps) { if ( !this.props.isVisible && nextProps.isVisible && this.state.progress !== 1 ) { this.startAnimation = Date.now(); const tick = () => { const progress = Math.min( 1, (Date.now() - this.startAnimation) / this.animationDuration ); this.setState({ progress: progress }); if (progress < 1) { requestAnimationFrame(tick); } }; tick(); } } render() { return (
{Math.floor(this.state.progress * 3)} days · {Math.floor(this.state.progress * 21)} talks · {Math.floor(this.state.progress * 4)} workshops · {Math.floor(this.state.progress * 350)} attendees

Slide 29

Slide 29 text

Polyfilling IntersectionObserver on‑demand

Slide 30

Slide 30 text

Disabling lazy‑loading if IntersectionObserver is not supported class Observer extends Component { constructor() { super(); // isVisible is initialized to true if the browser // does not support IntersectionObserver API this.state = { isVisible: !(window.IntersectionObserver) }; this.io = null; this.container = null; } componentDidMount() { // only initialize the IntersectionObserver if supported if (window.IntersectionObserver) { this.io = new IntersectionObserver(entries => { ... } } } }

Slide 31

Slide 31 text

Requesting a polyfill on demand class Observer extends Component { ... componentDidMount() { (window.IntersectionObserver ? Promise.resolve() : import('intersection-observer') ).then(() => { this.io = new window.IntersectionObserver(entries => { entries.forEach(entry => { this.setState({ isVisible: entry.isIntersecting }); }); }, {}); this.io.observe(this.container); }); } ... }

Slide 32

Slide 32 text

Safari requests the polyfill for intersection‑observer on demand. No need to ship it to browsers that support it natively.

Slide 33

Slide 33 text

Code Splitting and CSS‑in‑JS react‑router and Next.js have made code‑splitting easy to implement lazy‑loading can be applied to other resources (SVGs, CSS) With CSS‑in‑JS we take code splitting further, loading CSS on demand.

Slide 34

Slide 34 text

Useful implementations thebuilder/react‑intersection‑observer researchgate/react‑intersection‑observer

Slide 35

Slide 35 text

Conclusion Componentization makes code‑splitting and loading assets on‑demand easier than ever!