The Fastest Code

is the code you don't load

How not to get yelled at by Alex Russell

Smaller

Smarter

Overview

Tooling

  • Tree shaking
  • SSR
  • Code splitting

Environment

  • Less code
  • Browser Devtools
  • Browser APIs
  • Network features

Smaller

Smaller Frameworks

Smaller Frameworks

Smarter

Environment Edition

Browser Devtools

  • Inspector
  • Lighthouse

Inspector: CSS+JS Coverage

Browser Devtools

Inspector: CSS Coverage

Browser Devtools

Lighthouse

Browser Devtools

Browser APIs

  • Service Worker
  • IntersectionObserver

Intersection Observer

Browser APIs

Intersection Observer: Native DOM API

Browser APIs

var iObserver = new IntersectionObserver((entries) => {
  // If intersectionRatio is 0, the target is out of view
  if (entries[0].intersectionRatio <= 0) return;

  loadItems(10);
});

// start observing
iObserver.observe(
  document.querySelector('.scrollerFooter')
);

Intersection Observer: react-intersection-observer

Browser APIs

import Observer from 'react-intersection-observer'
 
<Observer>
  {(inView) => <h2>{`Header inside viewport ${inView}.`}</h2>}
</Observer>

Service Worker: offline-first pattern

Browser APIs

Service Worker: offline-first pattern

Browser APIs

Step 1: Configure plugin in webpack.config.js:

 

 

// webpack.config.js

var OfflinePlugin = require('offline-plugin');

module.exports = {
  // ...

  plugins: [
    // ... other plugins

    // Should usually be last
    new OfflinePlugin({
      ...
    })
  ]
}

Service Worker: offline-first pattern

Browser APIs

// main.entry.js

require('offline-plugin/runtime').install();

runtime.install({
  onUpdateReady: () => {   
    // Tells to new SW to take control immediately
    runtime.applyUpdate();
  },

  onUpdated: () => {
    // Reload page to load the new version
    window.location.reload();
  },

  onUpdateFailed: () => {
    console.log('SW Event:', 'onUpdateFailed');
  }
});

Step 2: Add the runtime into your entry file

Network features

  • HTTP2
  • Preload

Network features

H2

Network features

Server Push

https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/

Smarter

Tooling Edition

Code Splitting

  • Per page
  • Dynamic

Code splitting

Per entry-point

Dynamic - component-based

Code splitting

Code splitting

webpack-bundle-analyzer: stats.json

669kB

83kB

80kB

489kB

Code splitting

webpack-bundle-analyzer: gzipped

61kB

7kB

47kB

11kB

Code splitting

webpack-bundle-analyzer: parsed

151kB

48kB

209kB

25.4 kB

Code splitting: set-up

.babelrc

{
  "presets": [
    ["env", {"modules": false}],
    "react",
    "stage-2"
  ],
  "plugins": [
    "transform-flow-strip-types"
  ],
  "env": {
    "browser": {
      "presets": [
        ["env", {
          "targets": {"chrome": 57, "firefox": 52, "safari": 10},
          "modules": false,
          "useBuiltIns": true
        }],
        "react",
        "stage-2"
      ]
    }
  }
}

Code splitting: device-based

react-loadable

import Loadable from 'react-loadable';

const RadarLoader = Loadable({
  loader: () => import(/* webpackChunkName: "radar" */ './radar'),
  LoadingComponent: RadarSpinner
});

const RadarSpinner = ({ isLoading, error, pastDelay }: Props) => {
  if (isLoading) {
    return pastDelay ? <Spinner>Loading...</Spinner> : null;
  } else if (error) {
    return <div>Error! Component failed to load</div>;
  }
  return null;
};

export default class extends Component {
  render() {
    return (
      <Container>
        <Media query={d.mqs.tablet}>
          {matches => (matches ? <RadarLoader /> : null)}
        </Media>
      </Container>
    );
  }
}

1

2

Code splitting: interaction-based

react-loadable

let Db = Loadable({
  loader: () => import(/* webpackChunkName: "searchdb" */ './db'),
  LoadingComponent: DbShim
});

class Search extends Component {
  state = {initialised: false, query: ''};

  loadDB = () => {
    if (this.state.initialised) return;
    this.setState({ initialised: true });
    Database.preload();
  };

  updateQuery = (e) => this.setState({ query: e.target.value; });

  render() {
    const {query} = this.state;
    const {cards, submitSearch} = this.props;
    return (
      <Container>
        <Form query={query} onFocus={this.loadDB} onChange={this.updateQuery} />
        <Db cards={cards} term={query} onTermUpdated={submitSearch} />
      </Container>
    );
  }
}

3

1

2

Preload chunks

preload-webpack-plugin

Preload chunks

preload-webpack-plugin

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link rel="preload" as="script" href="/js/chunks/radar.js">
    <link rel="preload" as="script" href="/js/chunks/root.js">
    <link rel="preload" as="script" href="/js/chunks/searchdb.js">
    <link rel="preload" as="script" href="/js/chunks/smoothscroll.js">

    <style type="text/css">...</style>
    <link href="/css/app.css" rel="preload" as="style" onload="this.rel='stylesheet'">
  </head>
  <body>...</body>
</html>

👇

Thanks!

@oliverturner

Presentation to MancReact May 2017

By Oliver Turner

Presentation to MancReact May 2017

A breakdown of the various tools and techniques we can use to slim down our payloads and keep our users (and Alex Russell) happy.

  • 781