4/14/17
Charlie King
Alan Tirado
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
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
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)
Core
WMAPI
Moonshot
Client
📄 HTML / Assets
NodeJS
Rails
React/Next.js
NGINX
ReactJs
- 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>
`;
Handles all the routing, server rendering and re-conciliation on the client
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
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
<script type="text/javascript">
window.__NEXT_DATA__ = "{\"dataFromServer\": \"someData\"}"
</script>
Reconciles server data and re-mounts the React application with the data as Props
<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
Bluedream Core, mostly hand-rolled artisanal CSS
Lots of un-used CSS
Scoping issues and regressions galore
Import only the classes, mixins that we need, resulting in a smaller CSS bundle
No need to re-write basic "util" like behaviors
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>
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>
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)
<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>