Skip to content
This repository was archived by the owner on May 4, 2019. It is now read-only.

Commit f3a6121

Browse files
author
mattjstar
committed
wip
1 parent 63a9da6 commit f3a6121

5 files changed

+255
-5
lines changed

README.md

+18-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ If you're completely new to Jekyll, I recommend checking out the documentation a
1212

1313
### Installing Jekyll
1414

15-
If you don't have Jekyll already installed, you will need to go ahead and do that.
15+
If you don't have Jekyll already installed, you will need to go ahead and do that. I've had issues with 2.5.6 and the major ^3.0.1 upgrades.
1616

1717
```
18-
$ gem install jekyll
18+
$ gem install jekyll -v 2.4.0
1919
```
2020

2121
#### Verify your Jekyll version
@@ -42,6 +42,22 @@ NOTE: passing the --drafts flag will also load all posts inside of the _drafts f
4242
useful when you are working on a post but are not ready to publish it yet.
4343

4444

45+
## Writing a Post
46+
47+
Make sure to have all proper markup filled out at the top of your post to get that SEO boost.
48+
49+
Here's a good example:
50+
```
51+
---
52+
layout: post
53+
title: Rabbits, Bunnies and Threads
54+
author: Sai Wong
55+
summary: When writing Ruby, we sometimes take advantage of the single threaded nature of the environment and forget some of the pitfalls of being thread safe. When using servers such as Puma that allow us to take advantage of thread to maximize on performance, we found an issue with our Bunny implementation. The issue was identified as a documented inability for Bunny channels to be shared across threads and we developed a solution to address the issue.
56+
image: http://res.cloudinary.com/wework/image/upload/s--GnhXQxhq--/c_scale,q_jpegmini:1,w_1000/v1445269362/engineering/shutterstock_262325693.jpg
57+
categories: ruby rails bunny rabbitmq threads concurrency puma errors
58+
---
59+
```
60+
4561
### Need a Cool Photo?
4662

4763
To keep some visual consistency on our blog, it is recommended to use a photo by this illustrator.

_drafts/react-static.markdown

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
---
2+
layout: post
3+
title: Building Static Sites with React, Redux, Webpack, and Roots
4+
author: Matt Star
5+
summary: Our main marketing site (wework.com) used to be a slow monolithic Rails app. This is how we converted it to use Roots, React, and Webpack and decreased our page load speed by over 50%.
6+
image: http://res.cloudinary.com/wework/image/upload/s--xpIlilub--/c_scale,q_jpegmini:1,w_1000/v1443207604/engineering/shutterstock_294201896.jpg
7+
categories: process
8+
---
9+
10+
The WeWork Engineering team is made up of mostly Rails engineers. When we made the decision over a year ago to bring [wework.com](https://www.wework.com) in house it was a no brainer to use Rails--we could get up and running more quickly and have all engineers contribute to the project. However, as we've grown our team, experimented with new technologies ([React and Redux](/process/2015/10/01/react-reflux-to-redux/)), and grown as a company in size and scope, not only has our Rails app balooned into a monolithic headache, but we're starting to see increasing page load times--and as a consequence declining SEO and poor user experience. We needed to move off of Rails.
11+
12+
We had three engineering requirements:
13+
14+
1. These pages need to be FAST.
15+
2. Create one off (potentially dynamic) landing pages as quickly as possible.
16+
3. Use React for our some of our more complicated views.
17+
18+
## Go Static - Roots.cx
19+
20+
For the first two requirements, we turned to the (Roots)[http://roots.cx/] static site generator. I'll let you dig into the docs, but we were able to create static compiled HTML/CSS/JS pages very quickly, and by having them hosted on a CDN (we use [Netlify](http://netlify.com/)) saw a massive page speed increase.
21+
22+
Roots takes all `.jade` files in your `/views` directory, and compiles them down to your `public` directory as raw html ready to be served.
23+
24+
You might be asking, wouldn't you see the same result by CDN caching the Rails app? Well, sorta! And that's what we did initially. However, as your site grows there are many issues that you'll start to run into.
25+
26+
#### The Asset Pipeline
27+
28+
This was our biggest issue by far. One of the biggest advantages of Rails is its ability to magically compile all your css/sass and js/coffeescript when you build your application up to heroku (or wherever you're hosting). However, what if you start building one off landing pages that don't need all the compiled css and js from application.css and application.js?
29+
30+
We started by creating multiple layout files that only include the necessary JS and CSS, but that's not really what Rails was built to support. Now you're adding `layout: :INSERT_LAYOUT_FILE_NAME` into your controller actions and contributing to overall bloat.
31+
32+
Our next solution was experimenting with integrating webpack and React. At that point, you should just be building a node app rather than retrofitting Rails to do somthing it didn't intend to do in the first place.
33+
34+
With Roots and static it was quite simple to only include the necessary Javascript and CSS to keep our pages as slim as possible.
35+
36+
#### Server Side Rendering
37+
38+
What if you want to use React in a Rails project? On our old Rails site we used React to build our [Locations flow](https://www.wework.com/locations/new-york-city/). The page coming back from the server would be blank (just header and footer) and then when the DOM was ready, react would kick in and load the page accordingly. All of our React logic was being compiled in Application.js so it was unfortunately only client side. This was probably the biggest reason we switched off of Rails and embraced static.
39+
40+
## Static React
41+
42+
This isn't quite server side rendering, isomorphic react, universal react, or whatever you want to call it because we don't have a server. However, this still uses the same concepts as server side rendering. Instead of allowing an Express server for example to render and serve the page, we use the same logic and pass it through a Webpack static site generator plugin to compile the html and save it to our public folder. We took a lot of cues from this excellent [tutorial on creating static pages with React components](http://jxnblk.com/writing/posts/static-site-generation-with-react-and-webpack/). We're still working on a more ideal implementation, but it boils down to the following:
43+
44+
* Roots is responsible for compiling all non-react static pages
45+
* Webpack is responsible for compiling all views that use react
46+
* webpack-dev-server is responsible for serving all pages (both roots and react) in development
47+
48+
When we compile to production, it's now as easy as running `roots compile -e production && npm run build`.
49+
50+
In a perfect world we'd have all the roots logic run through webpack as well. Luckily the team at carrot creative is currently working on the next implementation of Roots that does just that!
51+
52+
53+
## Using React, React Router, and Redux
54+
55+
First, let's assume the following in your webpack.config.js:
56+
57+
```js
58+
59+
var path = require("path");
60+
var oui = require('@wework/oui');
61+
var ExtractTextPlugin = require("extract-text-webpack-plugin");
62+
var StaticGeneratorFromUrlPlugin = require('./src/static-generator-plugin.js');
63+
64+
// Format url object for StaticGeneratorFromUrlPlugin plugin:
65+
var formatUrl = function(url, token) {
66+
return {
67+
url: {
68+
path: url,
69+
headers: { 'Authorization': 'Token token=' + token },
70+
}
71+
}
72+
};
73+
74+
var markets_api_url = process.env.DUBS_API + '/api/v1/markets'
75+
var markets_url_object = formatUrl(markets_api_url, process.env.DUBS_API_TOKEN)
76+
77+
var config = {
78+
entry: {
79+
'base': './src/base_entry.js',
80+
'main': './src/main_entry.js',
81+
'home': './src/home_entry.js',
82+
'react_base': './src/react_base_entry.js',
83+
'market_page': './src/market_page_entry.js',
84+
},
85+
86+
output: {
87+
path: path.join(__dirname, "public"),
88+
filename: "js/[name].bundle.js",
89+
libraryTarget: 'umd',
90+
},
91+
92+
externals: {
93+
"_": "_",
94+
"jquery": "jQuery",
95+
"Modernizr": "Modernizr",
96+
},
97+
98+
module: {
99+
loaders: [
100+
{
101+
test: /\.jade$/,
102+
loader: 'jade-loader',
103+
exclude: /node_modules/
104+
},
105+
{
106+
test: /\.jsx?$/,
107+
loader: 'transform?envify!babel',
108+
include: [
109+
path.resolve(__dirname, "node_modules/@wework/oui/src"),
110+
path.resolve(__dirname, "assets/js"),
111+
path.resolve(__dirname, "src"),
112+
],
113+
},
114+
{
115+
test: /\.css$/,
116+
loader: ExtractTextPlugin.extract('css-loader'),
117+
include: [
118+
path.resolve(__dirname, "node_modules/@wework/oui/src"),
119+
path.resolve(__dirname, "src"),
120+
],
121+
},
122+
{
123+
test: /\.styl$/,
124+
loader: ExtractTextPlugin.extract('css!stylus'),
125+
include: [
126+
path.resolve(__dirname, "node_modules/@wework/oui/src"),
127+
path.resolve(__dirname, "src"),
128+
path.resolve(__dirname, "assets"),
129+
],
130+
},
131+
{test: /\.json$/, loader: 'json', exclude: /node_modules/},
132+
]
133+
},
134+
135+
stylus: {
136+
'use': [oui({ implicit: false })],
137+
'include css': true,
138+
},
139+
140+
plugins: [
141+
new StaticGeneratorFromUrlPlugin('js/market_page.bundle.js', markets_url_object),
142+
new ExtractTextPlugin("/css/[name].styles.css"),
143+
]
144+
}
145+
146+
module.exports = config;
147+
148+
```
149+
150+
Let's take a look at our webpack entry file to see what's going on:
151+
152+
```js
153+
import React from 'react';
154+
import ReactDOM from 'react-dom/server';
155+
import { render as renderDOM } from 'react-dom';
156+
import { Router, Route, match, RoutingContext } from 'react-router';
157+
import createBrowserHistory from 'history/lib/createBrowserHistory';
158+
import { Provider } from 'react-redux';
159+
160+
import createApiClientStore from './redux/init';
161+
import MarketPage from './containers/MarketPage/MarketPage';
162+
163+
// TODO: Figure out why we need no / for
164+
// the static site version and the extra /
165+
// for the client side render:
166+
const routes = ([
167+
<Route path="/v2/locations/:market" component={MarketPage} />,
168+
<Route path="/v2/locations/:market/" component={MarketPage} />,
169+
]);
170+
171+
// Client Side Render:
172+
if (typeof document !== 'undefined') {
173+
// Fetch initial state from Server Rendered HTML:
174+
const initialState = JSON.parse(window.__INITIAL_STATE__.replace(/&quot;/g, '"'));
175+
const history = createBrowserHistory();
176+
const store = createApiClientStore(initialState);
177+
178+
renderDOM(
179+
<Provider store={store}>
180+
<Router children={routes} history={history} />
181+
</Provider>,
182+
document.getElementById('content')
183+
);
184+
}
185+
186+
// Use layout.jade from roots as main layout file for react pages:
187+
const defaultLocals = require('../lib/locals.json');
188+
const marketsByCountry = require('../lib/marketsByCountry.json');
189+
const template = require('../views/layout.react.jade');
190+
191+
// Exported static site renderer:
192+
module.exports = function render(locals, callback) {
193+
const initialState = {
194+
market: {
195+
loading: false,
196+
data: locals.data,
197+
},
198+
};
199+
200+
// React Router 1.0 server side syntax:
201+
// https://github.com/rackt/react-router/blob/master/docs/guides/advanced/ServerRendering.md
202+
match({ routes, location: locals.path }, (error, redirectLocation, renderProps) => {
203+
const store = createApiClientStore(initialState);
204+
const initialReduxState = JSON.stringify(store.getState());
205+
206+
const html = ReactDOM.renderToString(
207+
<Provider store={store}>
208+
<RoutingContext {...renderProps} />
209+
</Provider>
210+
);
211+
212+
defaultLocals._path = '';
213+
defaultLocals.appContent = html;
214+
defaultLocals.description = locals.data.seo_page_title;
215+
defaultLocals.title = locals.data.seo_page_title;
216+
defaultLocals.initialState = initialReduxState;
217+
defaultLocals.records = { marketsByCountry: marketsByCountry };
218+
219+
callback(null, template(defaultLocals));
220+
});
221+
};
222+
223+
```
224+
225+
226+
227+
228+
229+
230+
231+
232+
233+
234+

_posts/2015-04-29-caching-external-apis.markdown

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
layout: post
33
title: Caching External APIs in Rails for a Ginormous Speed Boost
44
author: Matt Star
5-
summary:
5+
summary: How to use Rails Fragment Caching to cache external APIs.
66
image: http://res.cloudinary.com/wework/image/upload/s--unWFH26o--/c_fill,fl_progressive,g_north,h_1000,q_jpegmini,w_1600/v1430251626/engineering/caching-external-apis.jpg
77
categories: engineering
88
---

_posts/2015-06-15-inside-weworks-tech-stack.markdown

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
layout: post
33
title: Inside WeWork's Tech Stack
44
author: Matt Star
5-
summary:
5+
summary: Take a tour through WeWork's tech stack from one of our Lead Software Engineers.
66
image: http://res.cloudinary.com/wework/image/upload/s--Y5EaKyFC--/c_scale,fl_progressive,q_jpegmini:2,w_1072/v1434404739/engineering/inside-weworks-tech-stack.jpg
77
categories: engineering
88
---

_posts/2015-10-01-react-reflux-to-redux.markdown

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
layout: post
33
title: React Tutorial - Converting Reflux to Redux
44
author: Matt Star
5-
summary:
5+
summary: We converted our React Reflux code to React Redux, and switched over to using ES6 in the process. We'll go over how to save the state of the ui in a store, fetch data from an external API to hydrate our store, and filter data that is already in our store.
66
image: http://res.cloudinary.com/wework/image/upload/s--xpIlilub--/c_scale,q_jpegmini:1,w_1000/v1443207604/engineering/shutterstock_294201896.jpg
77
categories: process
88
---

0 commit comments

Comments
 (0)