Moonshot

The future of the Weedmaps Front End

3/30/18

Charlie King

Who am I?

Staff Engineer / Architect

Topics

  • Principles

  • Architecture

  • React

  • UI library

  • Testing

  • Next.js

  • Styled Components

Principles

Learn once, write everywhere (React team)

Get content to the users as fast as possible

Mobile performance first, then enhance

Architecture

Server side templates

Enhance with jQuery

Enhance with Backbone / Angular 1.x

Core

State is easy, just refresh

Client gets real HTML on render, SEO is easy. I81n, accessibility is easier

Less bandwidth, less assets delivered to client

As views become complex, structure falls apart

Harder to create interactive experiences

Increased CPU pressure on server to render complex views

Single-page App

Move all rendering/routing client side

Cache forever, serve statically

Ionic

Developer Experience is better, single paradigm, modern JS (compiled)

Route transitions are immediate, no waiting on the server

Subsequent page visits are extremely quick

Initial page load is very slow

User downloads more data than they use

Consumes a large amount of user memory

SEO? Harder, needs third party services

Create render "middle" teir 

Generate HTML from

JS server side 

Bootstrap HTML with client side JS (async)

Cache when possible

Moonshot

Developer Experience is awesome, same code on client and server (including routes and data fetching)

Route transitions are immediate* (when the client is downloaded)

First page visits are Fast, subsequent visits are still fast

Server CPU pressure to render templates

Longer Time to First Byte (TTFB)

Architecture

Core

WMAPI

Moonshot

Client

📄 HTML / Assets

NodeJS

Rails

React/Next.js

NGINX

ReactJs

Why?

- Fast learning curve

- Easy reusable components

- Clean abstraction

- Control over patterns/tech

- Rich DevTools

- Tests with Jest and snapshots

ReactJs

WM UI Library

What's the big idea?

What's the big idea?

Header

ActionButton

PaginationControl

Composable and shareable

Semantic Release

What does the version mean?

- Based on actual changes

- Upgrade confidently

- Fully automated releases

- NPM

What is in the Library?

ProjectX

ProjectY

Testing

import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
  ).toJSON();
  expect(tree).toMatchSnapshot();
});

Jest + Snapshots = Awesome

exports[`renders correctly 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;

Next.js

Convention driven Universal React Framework 

Handles all the routing, server rendering and re-conciliation on the client

Routing

Pages are routes, map to physical routes in the file system

📄 index.js

/

📄 deals.js

/deals

Pages split JS into bundles, client only loads the JS for each route

Data fetching

getInitialProps

class LocationProvider extends Component {
  static async getInitialProps(props) {
    const { res, req, jsonPageRes } = props;
    const isServer = !!req;
    const locationStore = LocationStore.initStore();
    // fetch location
    if (!locationStore.location) {
      await locationStore.getLocation();
    }
    // dehydrate the state
    const initialLocationState = isServer 
    ? locationStore.dehydrate() 
    : null;
    // return the initial state
    return {
      isServer,
      storeState: { locationStore: initialLocationState }
    };
  }

Runs once on the server, and then again during client route transitions

Reconciliation

<script type="text/javascript">
    window.__NEXT_DATA__ = "{\"dataFromServer\": \"someData\"}"
</script>

Reconciles server data and re-mounts the React application with the data as Props

Prefetching / Caching

<PageLayout className="page--home">
    <Head>
        <title>{config.social.siteTitle}</title>
    </Head>
    <PageHead/>
    <PageContent>
    <Link route='privacy' prefetch>
        <a className='button'>
            Privacy
        </a>
    </Link>
      
    <Link route='terms' prefetch>
         <a className='button'>
             Terms
         </a>
    </Link>
    </PageContent>
<Footer/>
</PageLayout>

<Link> elements with "prefetch" attribute prefetch the route JS code with service worker.

Routes and bundles hashed and cached

  SASS

Ionic / Core

Bluedream Core, mostly hand-rolled artisanal CSS 

Lots of un-used CSS 

Scoping issues and regressions galore

Moonshot

Foundation 6 

Import only the classes, mixins that we need, resulting in a smaller CSS bundle

No need to re-write basic "util" like behaviors

BEM 

Addresses regressions by making it harder to clash with styles and not have aggressive specific selectors

<LegalLayout className='legal-layout--privacy'>
    <Head>
        <title>{title}</title>
        <meta property='og:title' content={title}/>
        <meta property='twitter:title' content={title}/>
        <meta property='og:url' content={url}/>
    </Head>
<PageHead/>
<PageContent className='page__content--legal'>
    <LegalSubheader pageTitle={pageTitle} displayDate={displayDate} url={url}/>
    <div className='page__content__container row'>
        <LegalSidebar className='small-12 medium-4 large-3 columns' tableOfContents={tableOfContents}/>
        <LegalContent className='small-12 medium-8 large-9 columns' type='privacy' sections={sections}/>
    </div>
</PageContent>
<Footer/>
</LegalLayout>

Module Scoped CSS

Addresses regressions by making promoting (not enforcing) modular CSS imports

import { withStyles } from '@ghostgroup/universal-sass-loader/runtime';
// import a footer SCSS file
import FooterStyles from './footer';
// create the footer component
const Footer = () => <footer></footer>
// export a styled component
export default withStyles(FooterStyles)(Footer);
<style type="text/css" 
data-universal-sass-checksum="08328bc48d07d5d937a0bc70d4c69c58" 
data-universal-sass-component-name="WithStyles(Footer)" 
id="190">
/* content here */
</style>

Flushed CSS

Node Server flushes all rendered stylesheets into the <head>, no unused CSS

import { withStyles } from '@ghostgroup/universal-sass-loader/runtime';
// import some SCSS files
import GlobalStyles from './styles/global';

// create our "page" with global stuff, e.g. Foundation
const PageLayout = withStyles(GlobalStyles,() => <div></div>);

// import some deals styles
import DealsStyles from './deals_style';

// create the deals document
const Deals = () =>
    <PageLayout className="page--deal">
        <Head>
          <title>Deal</title>
        </Head>
        <PageHead/>
        <PageContent>
          <div className='row'>
            Deal {id}
          </div>
        </PageContent>
        <Footer/>
      </PageLayout>


// export styled deals
export default withStyles(DealsStyles)(Footer);

No Flash of Unstyled Content! (FOUC)

Flushed CSS cont.

<head>
    <style id="global-styles">
    /* global content */
    </style>
    <style type="text/css" data-universal-sass-checksum="8148888fbb3a584fcc71b9757eccbeb9" 
    data-universal-sass-component-name="WithStyles(LegalLayout)" id="739">
    /* content */
    </style>
    <style type="text/css" data-universal-sass-checksum="061ab024b1ebe55213db95b952ce1a98" 
    data-universal-sass-component-name="WithStyles(GlobalHeader)" id="191">
    /* content */
    </style>
    <style type="text/css" data-universal-sass-checksum="13cd8d8fa67b07ce0934dae922ce2a37" 
    data-universal-sass-component-name="WithStyles(PageSection)" id="741">
    /* content */
    </style>
    <style type="text/css" data-universal-sass-checksum="a99dbd098159453a731931354bd5d57c" 
    data-universal-sass-component-name="WithStyles(LegalSidebar)" id="740">
    /* content */
    </style>
    <style type="text/css" data-universal-sass-checksum="4ace5e1d4dd314862ebcd476cb03f0ce" 
    data-universal-sass-component-name="WithStyles(LegalContent)" id="738">
    /* content */
    </style>
    <style type="text/css" data-universal-sass-checksum="08328bc48d07d5d937a0bc70d4c69c58" 
    data-universal-sass-component-name="WithStyles(Footer)" id="712">
    /* content */
    </style>
</head>

Questions / Comments?

Moonshot Final

By Charles King

Moonshot Final

Introduction to the new Weedmaps Front End, Moonshot

  • 854