-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path20170418-isomorphic-react.html
420 lines (374 loc) · 12.9 KB
/
20170418-isomorphic-react.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ezekiel Kigbo - Isomorphic react</title>
<meta name="description" content="Isomorphic react js">
<meta name="author" content="Ezekiel Kigbo">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
<link rel="stylesheet" href="css/reveal.css">
<link rel="stylesheet" href="css/theme/black.css" id="theme">
<!-- Code syntax highlighting -->
<link rel="stylesheet" href="lib/css/zenburn.css">
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
<!--[if lt IE 9]>
<script src="lib/js/html5shiv.js"></script>
<![endif]-->
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section>
<h1>Isomorphic React</h1>
<h3>A whirlwind tour</h3>
<footer>
<small>18 April, 2017</small>
</footer>
</section>
<section>
<h2>What is this?</h2>
<aside class="notes">
<p>Isomorphic apps, are apps that can run on both the client and the server</p>
<p>I'll just skip over some of the pain points in ISO apps and provide some links for further investigation</p>
</aside>
</section>
<section>
<h2>Why?</h2>
<ul>
<li>People like client side apps - once the app is loaded it *feels responsive*, no need to refresh</li>
<li>SEO, crawlers - not all can properly index client side apps</li>
<li>Users dont have to wait for all the assets to load to start using the app</li>
<li>Shared codebase for front and back</li>
</ul>
</section>
<section>
<h2>Considerations</h2>
<ul>
<li>Rendering the view</li>
<li>Routing / State</li>
<li>Populating the head tag</li>
<li>Styling components</li>
</ul>
</section>
<section>
<section>
<h2>View - rendering</h2>
<p>`ReactDOM.renderToString` will render a react element to its initial HTML</p>
<p>This returns a HTML string and should only be used on the server</p>
<aside class="notes">
<p>On the client side, we want to render our react app into the DOM, but on the server, theres no concept of the DOM,
instead we return a html document that can be displayed by the browser. ReactDOM.renderToString will render the component to a html string.
</p>
</aside>
</section>
<section>
<pre>
<code data-noescape>
import App from 'Shared/app'
const appRender = (location, plainPartialState, routerContext = {}) => {
// Create store + redux provider
...
<mark>const appHtml = ReactDOMServer.renderToString(<App />)</mark>
return (
<!--HTML STRING TO RENDER -->
)
}
</code>
</pre>
<p>appRender.jsx</p>
</section>
<section>
<pre>
<code data-noescape>
<html>
<head>
${head.title}
${head.meta}
<link rel='stylesheet' href='${assetPath}/css/styles.css' />
</head>
<body>
<mark><div class="${APP_MOUNT_CONTAINER}">${appHtml}</div></mark>
<script src="${assetPath}/js/bundle.js"></script>
</body>
</html>
</code>
</pre>
<p>HTML to render</p>
</section>
</section>
<section>
<section>
<h2>Routing</h2>
<ul>
<li>Routing needs to be handled both on the client and the server</li>
<li>StaticRouter - a stateless router that doesnt change location</li>
<li>BrowserRouter - a router uses HTML5 history to keep the UI in sync with the url</li>
</ul>
<aside class="notes">
<p>There isn't too much needed for routing, we just need to ensure we handle routes on both the client and server, using the relevant router</p>
<p>We define our routes in a seperate file and can access those routes in our express router and in react-router on the front end</p>
<p>I didnt look too far under the hood</p>
</aside>
</section>
<section>
<p>Server</p>
<pre>
<code>
import App from 'Shared/app'
// location: the url the server received
// context: properties a component can add to store information
// passed to the component via the staticContext prop
ReactDOM.renderToString(
<StaticRouter location={} context={}>
<App />
</StaticRouter>
);
</code>
</pre>
</section>
<section>
<p>Client</p>
<pre>
<code data-trim>
import App from 'Shared/app'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
</code>
</pre>
</section>
<section>
<h2>Initial state - server</h2>
<p>On the server, we populate `window.__PRELOADED_STATE__`</p>
<pre>
<code>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(store.getState())}
</script>
</code>
</pre>
<p>appRender.html</p>
<aside class="notes">
<p>Not too much here, just need to make sure whatever initial state we are using includes any data we need for the route</p>
<p>window.__PRELOADED_STATE__ is just an arbitrary point to inject the inital state</p>
</aside>
</section>
<section>
<h2>Initial state - client</h2>
<p>On the server, we populate `window.__PRELOADED_STATE__`</p>
<pre>
<code>
// Grab our preloaded state that came from the server
const preloadedState = window.__PRELOADED_STATE__
// create our store using the preloaded state
const store = createStore(
combineReducers({ someStateSlice: someStateSliceReducer }),
{ someStateSlice: preloadedState.someStateSlice }
);
</code>
</pre>
<p>index.jsx</p>
<aside class="notes">
<p>Not too much here, just need to make sure whatever initial state we are using includes any data we need for the route</p>
<p>window.__PRELOADED_STATE__ is just an arbitrary point to inject the inital state</p>
</aside>
</section>
</section>
<section>
<section>
<h2>Populating HEAD tags - React Helmet</h2>
<ul>
<li>Manually manipulates your HEAD tag to reflect updated page details</li>
<li>Allows us to manipulate tags such as style, link, meta, title etc. from within our components</li>
</ul>
<aside class="notes">
<p>this was probably the simplest part, there seems to be one approach taken by the react community, React-Helmet</p>
</aside>
</section>
<section>
<p>Server</p>
<pre>
<code>
// grab parameters for our html head from the helmet component
const head = Helmet.rewind()
....
<head>
${head.title}
${head.meta}
</head>
....
</code>
</pre>
</section>
<section>
<p>Client</p>
<pre>
<code>
// TODO: move all this helmet shit to a HoC
import Helmet from 'react-helmet'
....
const title = 'Artist page'
const ArtistPage = () =>
<div>
<Helmet
title={title}
meta={[ { name: 'description', content: 'A tiny app' }, { property: 'og:title', content: title } ]}
/>
...
</div>
export default ArtistPage
</code>
</pre> <aside class="notes">
<p>On the server, we call Helmet.rewind() - after renderToString,
</aside>
</section>
</section>
<section>
<section>
<h2>Styling</h2>
<p>😅</p>
<ul>
<li>Inline Styles</li>
<li>Isomorphic style loader - extracts critical styles needed for rendering from the server</li>
<li>CSS Modules - modular approach that creates globally unique styles</li>
<ul>
<li>Conditional styling?</li>
<li>css-modules-require-hook</li>
</ul>
<li>JSS - styles written in JS, that are injected into your app</li>
<ul>
<li>🤢</li>
</ul>
</ul>
<aside class="notes">
<p>The challenge is how to apply styles to components coming from the server?</p>
<p>This isnt really a isomorphic react problem, more of a react community problem, nonetheless some options work better on the server</p>
</aside>
</section>
<section>
<h2>CSS modules</h2>
<pre>
<code data-noescape>
import React from 'react'
<mark>import styles from './ComponentStyles.css'</mark>
export default class StyledComponent extends React.Component {
...
render() {
return (
<header>
<h1 className={styles.someStyle}>🤙🏾🔥Yeaaaaah boiiiii</h1>
</header>
)
}
}
</code>
</pre>
<aside class="notes">
<p>Typically css modules look something like this</p>
<p>That line is the problem, node on its own doesnt know how to handle CSS files</p>
<p>One option is to use a seperate webpack config for your server code, helpers such as universal-webpack make this easier</p>
</aside>
</section>
<section>
<h2>CSS Modules - Conditional styling</h2>
<pre>
<code>
if ( process.env.BROWSER ) {
require('./StyledComponent.css');
}
export default class SomeComponent extends React.Component {
render() {
return (
<div className='StyledComponent'>{🤙🏾🤙🏾🤙🏾}</div>
);
}
}
</code>
</pre>
<aside class="notes">
<p>Another option is conditional styling, in this, we set up an env variable that can be checked, and choose whether or not to include styles</p>
<p>This also requires some additional configuration to your webpack file</p>
<p>A nicer approach is to use a higher order component such as withStyles to achieve this and just wrap your components in that</p>
</aside>
</section>
<section>
<h2>CSS Modules require hook</h2>
<code>
<pre>
const hook = require('css-modules-require-hook');
hook({
generateScopedName: '[name]_[local]__[hash:base64:5]',
rootDir: projectDir
});
</pre>
</code>
</section>
<aside class="notes">
<p>This seemed the simplest approach</p>
<p>Add it in and it extends the functionality of require statements to handle CSS files</p>
<p>Combining css modules with the ExtractTextWebpackPlugin, we can extract all our styles into a css file which we reference from our server view</p>
</aside>
</section>
<section>
<h2>Reality...</h2>
<ul>
<li>Universal redux</li>
<li>Isomorphic.net - resources on isomorphic apps</li>
<li>Airbnb - isomorphic javascript overview - https://medium.com/airbnb-engineering/isomorphic-javascript-the-future-of-web-apps-10882b7a2ebc</li>
</ul>
<aside class="notes">
<p>It's not too hard to get this working, but it does require a few choices along the way and a bit of fiddling around</p>
<p>If you just want to get something working, use a starter kit, adopt inline styles.</p>
</aside>
</section>
<section style="text-align: left;">
<h1>Useful Links</h1>
<ul>
<li>React-Helmet - https://github.com/nfl/react-helmet</li>
<li>Airbnb: isomorphic-javascript-the-future-of-web-apps - https://medium.com/airbnb-engineering/isomorphic-javascript-the-future-of-web-apps-10882b7a2ebc</li>
<li>JSS: CSS in JS - https://github.com/cssinjs/jss</li>
<li>CXS: another approach to css in js: https://github.com/jxnblk/cxs</li>
<li>A web application journey: https://medium.com/@LopezTech/a-web-application-journey-index-b212e60d2440</li>
<li>Zeit Next: https://zeit.co/blog/next2</li>
<li>Universal redux - https://github.com/bdefore/universal-redux</li>
<li>react starter kit - https://github.com/kriasoft/react-starter-kit</li>
</ul>
</section>
</div>
</div>
<script src="lib/js/head.min.js"></script>
<script src="js/reveal.js"></script>
<script>
// Full list of configuration options available at:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
transition: 'slide', // none/fade/slide/convex/concave/zoom
// Optional reveal.js plugins
dependencies: [
{ src: 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'plugin/highlight/highlight.js', async: true, condition: function() { return !!document.querySelector( 'pre code' ); }, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'plugin/zoom-js/zoom.js', async: true },
{ src: 'plugin/notes/notes.js', async: true }
]
});
</script>
</body>
</html>