Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.
Server-side Rendering of JavaScript
in PHP
Nacho Martín
Nacho Martín
I write code at Limenius.
We build tailor-made projects,
and provide consultancy and formation.
We are very h...
What is the problem that
Server Side Rendering
adresses?
Nacho Martín
nacho@limenius.com
@nacmartin
A long time ago in a galaxy far, far away
Server
Nacho Martín
nacho@limenius.com
@nacmartin
A long time ago in a galaxy far, far away
Server
HTML
</>
HTML
</> Client
Nacho Martín
nacho@limenius.com
@nacmartin
Very simple model that works
But to show a change we have to do a full
page rel...
Nacho Martín
nacho@limenius.com
@nacmartin
Even for things like this
New password
New Submit
Nacho Martín
nacho@limenius.com
@nacmartin
Even for things like this
New password
New Submit
****
Nacho Martín
nacho@limenius.com
@nacmartin
Even for things like this
New password
New Submit
****
Your password must conta...
Nacho Martín
nacho@limenius.com
@nacmartin
Adding dynamic elements
HTML
</>
Client
HTML
</>
Server
Nacho Martín
nacho@limenius.com
@nacmartin
Adding dynamic elements
HTML
</>
Client
HTML
</>
JS JS
Server
Nacho Martín
nacho@limenius.com
@nacmartin
Step 1: Client uses JS to modify the DOM
Client
HTML
</>
JS
$( "p" ).addClass( ...
Nacho Martín
nacho@limenius.com
@nacmartin
With DOM modification
We can now modify the document reacting to
user interactio...
Nacho Martín
nacho@limenius.com
@nacmartin
Example
1 2 3 4 5
Nacho Martín
nacho@limenius.com
@nacmartin
Adding dynamic content
HTML
</>
Client
HTML
</>
JS JS
Server
Nacho Martín
nacho@limenius.com
@nacmartin
Adding dynamic content
HTML
</>
Client
HTML
</>
JS JS
Server
API
Nacho Martín
nacho@limenius.com
@nacmartin
Step 2: Dynamic content
Client
HTML
</>
JS
$(“#grid").load( “api/page2.html“ );...
Nacho Martín
nacho@limenius.com
@nacmartin
Step 2: Dynamic content
Client
HTML
</>
JS
$(“#grid").load( “api/page2.html“ );...
Nacho Martín
nacho@limenius.com
@nacmartin
DOM Manipulation
HTML
</>
This happens in the Browser
Element
<body>
Element
<d...
Nacho Martín
nacho@limenius.com
@nacmartin
DOM Manipulation
This happens in the Browser
Element
<body>
Element
<div id=“gr...
Nacho Martín
nacho@limenius.com
@nacmartin
DOM Manipulation
This happens in the Browser
Element
<body>
Element
<div id=“gr...
Nacho Martín
nacho@limenius.com
@nacmartin
DOM Manipulation
This happens in the Browser
Element
<body>
Element
<div id=“gr...
Nacho Martín
nacho@limenius.com
@nacmartin
Problem: duplication of work
HTML
</>
We need a mechanism in the server to buil...
Nacho Martín
nacho@limenius.com
@nacmartin
Problem: duplication of work
…and another to update the DOM in the client
API
$...
Nacho Martín
nacho@limenius.com
@nacmartin
Possible solution: don’t render the content in HTML
HTML
</>
<div id=“grid”>
Nacho Martín
nacho@limenius.com
@nacmartin
And on document load do an API call
<div id=“grid”>
API
$(“#grid”).html(renderP...
Nacho Martín
nacho@limenius.com
@nacmartin
This means that the first thing the user sees is this
…and also crawlers :(
Nacho Martín
nacho@limenius.com
@nacmartin
Slow page loads in mobile users
https://www.doubleclickbygoogle.com/articles/mo...
Nacho Martín
nacho@limenius.com
@nacmartin
When are these problems worse
Apps. Bearable.
Content pages. Probably unbearabl...
Concurrent problem:
DOM manipulation
vs
State based JS libraries
Nacho Martín
nacho@limenius.com
@nacmartin
We want to build a TODO list
Pour eggs in the pan
How to cook an omelette
Buy e...
Nacho Martín
nacho@limenius.com
@nacmartin
We want to build a TODO list
Pour eggs in the pan
Beat eggs
How to cook an omel...
Nacho Martín
nacho@limenius.com
@nacmartin
Options
Nacho Martín
nacho@limenius.com
@nacmartin
Options
1: Re-render everything.
Nacho Martín
nacho@limenius.com
@nacmartin
Options
1: Re-render everything. Simple
Nacho Martín
nacho@limenius.com
@nacmartin
Options
1: Re-render everything. Simple Not efficient
Nacho Martín
nacho@limenius.com
@nacmartin
Options
2: Find in the DOM where to
insert elements, what to move,
what to remo...
Nacho Martín
nacho@limenius.com
@nacmartin
Options
2: Find in the DOM where to
insert elements, what to move,
what to remo...
Nacho Martín
nacho@limenius.com
@nacmartin
Options
2: Find in the DOM where to
insert elements, what to move,
what to remo...
Nacho Martín
nacho@limenius.com
@nacmartin
Options
2: Find in the DOM where to
insert elements, what to move,
what to remo...
Nacho Martín
nacho@limenius.com
@nacmartin
Fundamental premise
Give me a state and a render() method that depends
on it an...
Nacho Martín
nacho@limenius.com
@nacmartin
Let’s write a React component
Click me! Clicks: 0
Nacho Martín
nacho@limenius.com
@nacmartin
Let’s write a React component
Click me! Clicks: 1Click me!
Nacho Martín
nacho@limenius.com
@nacmartin
Let’s write a React component
import React, { Component } from 'react';
class C...
Nacho Martín
nacho@limenius.com
@nacmartin
Let’s write a React component
import React, { Component } from 'react';
class C...
Nacho Martín
nacho@limenius.com
@nacmartin
Let’s write a React component
import React, { Component } from 'react';
class C...
Nacho Martín
nacho@limenius.com
@nacmartin
Let’s write a React component
import React, { Component } from 'react';
class C...
Nacho Martín
nacho@limenius.com
@nacmartin
Working with state
constructor(props) {
super(props);
this.state = {count: 1};
...
Nacho Martín
nacho@limenius.com
@nacmartin
Working with state
constructor(props) {
super(props);
this.state = {count: 1};
...
Nacho Martín
nacho@limenius.com
@nacmartin
render() and JSX
render() {
return (
<div className="App">
<button onClick={thi...
Nacho Martín
nacho@limenius.com
@nacmartin
render() and JSX
render() {
return (
<div className="App">
<button onClick={thi...
Nacho Martín
nacho@limenius.com
@nacmartin
render() and JSX
render() {
return (
<div className="App">
<button onClick={thi...
Nacho Martín
nacho@limenius.com
@nacmartin
render() and JSX
render() {
return (
<div className="App">
<button onClick={thi...
Nacho Martín
nacho@limenius.com
@nacmartin
render() and JSX
render() {
return (
<div className="App">
<button onClick={thi...
Nacho Martín
nacho@limenius.com
@nacmartin
Components hierarchy
Nacho Martín
nacho@limenius.com
@nacmartin
Components hierarchy
Nacho Martín
nacho@limenius.com
@nacmartin
Components hierarchy: props
class CounterGroup extends Component {
render() {
r...
Nacho Martín
nacho@limenius.com
@nacmartin
Components hierarchy: props
render() {
return (
<div className="App">
<button o...
Nacho Martín
nacho@limenius.com
@nacmartin
Components hierarchy: props
render() {
return (
<div className="App">
<button o...
Nacho Martín
nacho@limenius.com
@nacmartin
Everything depends on the state, therefore we can:
Nacho Martín
nacho@limenius.com
@nacmartin
Everything depends on the state, therefore we can:
•Reproduce states,
Nacho Martín
nacho@limenius.com
@nacmartin
Everything depends on the state, therefore we can:
•Reproduce states,
•Rewind,
Nacho Martín
nacho@limenius.com
@nacmartin
Everything depends on the state, therefore we can:
•Reproduce states,
•Rewind,
...
Nacho Martín
nacho@limenius.com
@nacmartin
Everything depends on the state, therefore we can:
•Reproduce states,
•Rewind,
...
Nacho Martín
nacho@limenius.com
@nacmartin
Everything depends on the state, therefore we can
Nacho Martín
nacho@limenius.com
@nacmartin
Everything depends on the state, therefore we can
render the initial state to a...
Server side rendering
Nacho Martín
nacho@limenius.com
@nacmartin
First page load
HTML
</>
Client
HTML
</>
JS JS
Server
API
Includes initial state
Nacho Martín
nacho@limenius.com
@nacmartin
When we need more data
Client
Server
APIAPI
API calls to update the state
and t...
Nacho Martín
nacho@limenius.com
@nacmartin
ReactDOMServer.renderToString(element)
ReactDOM.hydrate(element, container[, ca...
Nacho Martín
nacho@limenius.com
@nacmartin
ReactDOMServer.renderToString(<MyApp/>)
SSR in React. 1) In the server:
<div da...
Nacho Martín
nacho@limenius.com
@nacmartin
SSR in React. 2) insert in our template
<html>
…
<body>
<div id=“root”>
<div da...
Nacho Martín
nacho@limenius.com
@nacmartin
ReactDOM.hydrate(
<MyApp/>,
document.getElementById('root')
)
SSR in React. 1) ...
Nacho Martín
nacho@limenius.com
@nacmartin
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(ht...
Nacho Martín
nacho@limenius.com
@nacmartin
React, Vue, but does my library support this?
If what is rendered depends on th...
Nacho Martín
nacho@limenius.com
@nacmartin
Problematic Example
Page
Nacho Martín
nacho@limenius.com
@nacmartin
Problematic Example
Page
API
My React Component
Nacho Martín
nacho@limenius.com
@nacmartin
Problematic Example
Page
API
My React Component
API
“React Component” that rend...
Nacho Martín
nacho@limenius.com
@nacmartin
Problematic Example
Page
API
My React Component
API
“React Component” that rend...
SSR in PHP
Nacho Martín
nacho@limenius.com
@nacmartin
Why?
SSR in JavaScript (node.js) is more natural.
But it may not be the right c...
Nacho Martín
nacho@limenius.com
@nacmartin
We need
Nacho Martín
nacho@limenius.com
@nacmartin
WebpackAssets
JS JS
TS SASS
PNG JPEG
JS
Client App
Nacho Martín
nacho@limenius.com
@nacmartin
WebpackAssets
JS JS
TS SASS
PNG JPEG
JS
Client App
JS
Server side App
Nacho Martín
nacho@limenius.com
@nacmartin
JS Code to execute
Server side JS App
+
Component and state that we want to ren...
Options
Nacho Martín
nacho@limenius.com
@nacmartin
This is what we would do for SSR in JS
Client
JS
Front
PHP
API
JS
Client side A...
Nacho Martín
nacho@limenius.com
@nacmartin
This is what we would do for SSR in JS
Client
JS
Front
PHP
API
JS
Client side A...
Nacho Martín
nacho@limenius.com
@nacmartin
This is what we would do for SSR in JS
Client
JS
Front
PHP
API
JS
Client side A...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 1: node.js as subprocess
Client
PHP
App
Node.js
JS
Client side App
Serve...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 1: node.js as subprocess
Client
PHP
App
Node.js
JS
Client side App
Serve...
Nacho Martín
nacho@limenius.com
@nacmartin
Make a call to node.js using Symfony Process component
* Easy to setup.
* Slow....
Nacho Martín
nacho@limenius.com
@nacmartin
Option 2: V8JS
Client
PHP
App
V8js
JS
Client side App
Server side JS App
+
Comp...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 2: V8JS
Client
PHP
App
V8js
JS
Client side App
Server side JS App
+
Comp...
Nacho Martín
nacho@limenius.com
@nacmartin
But we can cache
public function createContext($code, $cachename = null)
{
if (...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 2: V8JS
Client
PHP
App
V8js
JS
Client side App
Server side JS App
Compon...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 2: V8JS
Client
PHP
App
V8js
JS
Client side App
Server side JS App
Compon...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 2: V8JS
Client
PHP
App
V8js
JS
Client side App
Server side JS App
Compon...
Nacho Martín
nacho@limenius.com
@nacmartin
Use PHP extension v8js
* Fast.
* Need to compile v8, v8js, find Docker images… (...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 3: External JS server
Client
PHP
App
JS
renderer
JS
Client side App
Serv...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 3: External JS server
Client
PHP
App
JS
renderer
JS
Client side App
Serv...
Nacho Martín
nacho@limenius.com
@nacmartin
We have “stupid” node.js server used only to render components.
It has <100 LoC...
ReactRenderer &
ReactBundle
Nacho Martín
nacho@limenius.com
@nacmartin
Some libraries
phpexecjsReactRendererReactBundle
node.js
v8js
…
Twig extension
...
Nacho Martín
nacho@limenius.com
@nacmartin
Options 1 & 2
$renderer = new PhpExecJsReactRenderer(‘path_to/server-bundle.js’...
Nacho Martín
nacho@limenius.com
@nacmartin
Option 3: external renderer
$renderer = new ExternalServerReactRenderer(‘../som...
Nacho Martín
nacho@limenius.com
@nacmartin
JS side part: React on Rails
https://github.com/shakacode/react_on_rails
Used a...
Nacho Martín
nacho@limenius.com
@nacmartin
JS side part: React on Rails
{{ react_component('RecipesApp', {'props': props})...
Nacho Martín
nacho@limenius.com
@nacmartin
Redux integration
Nacho Martín
nacho@limenius.com
@nacmartin
Redux integration
Redux
Store
Nacho Martín
nacho@limenius.com
@nacmartin
Redux integration
import ReactOnRails from 'react-on-rails'
import RecipesAppRe...
Nacho Martín
nacho@limenius.com
@nacmartin
Share store between components
Nacho Martín
nacho@limenius.com
@nacmartin
React
React
React
Twig
Twig
React
By sharing store they
can share state
Twig
Sh...
Things to consider
Context
Nacho Martín
nacho@limenius.com
@nacmartin
Context variables. PHP
[
'serverSide' => $serverSide,
'href' => $request ->getS...
Nacho Martín
nacho@limenius.com
@nacmartin
Context in the JS side
export default (initialProps, context) => {
if (context....
Header tags
Nacho Martín
nacho@limenius.com
@nacmartin
Extracting headers
react-helmet (vue-helmet)
import { Helmet } from "react-helm...
Nacho Martín
nacho@limenius.com
@nacmartin
Extracting headers
export default (initialProps, context) => ({
renderedHtml: {...
Nacho Martín
nacho@limenius.com
@nacmartin
Then in Twig
{% set recipes = react_component_array('RecipesApp', {'props': pro...
Make reality checks
Nacho Martín
nacho@limenius.com
@nacmartin
Better to try it soon
Certain JS code doesn’t make sense in SSR:
• Timers: SetT...
Summary:
What is SSR and what is it for
Ways to do it in PHP (pros & cons)
Some libraries
Tips & practical problems
Thanks!
Questions?
Nacho Martín
nacho@limenius.com
@nacmartin
Próxima SlideShare
Cargando en…5
×

Server Side Rendering of JavaScript in PHP

700 visualizaciones

Publicado el

What is SSR, which problems does it solve, why do it in PHP, what options do we have for it, libraries that are available and tips and tricks. Practical code examples for Symfony and React.js, but the fundamental points can be taken away to use in other stacks like Vue and Laravel.

Publicado en: Software
  • Sé el primero en comentar

Server Side Rendering of JavaScript in PHP

  1. 1. Server-side Rendering of JavaScript in PHP Nacho Martín
  2. 2. Nacho Martín I write code at Limenius. We build tailor-made projects, and provide consultancy and formation. We are very happy with React, and have been dealing with how to integrate with PHP for some time now & publishing libraries about it.
  3. 3. What is the problem that Server Side Rendering adresses?
  4. 4. Nacho Martín nacho@limenius.com @nacmartin A long time ago in a galaxy far, far away Server
  5. 5. Nacho Martín nacho@limenius.com @nacmartin A long time ago in a galaxy far, far away Server HTML </> HTML </> Client
  6. 6. Nacho Martín nacho@limenius.com @nacmartin Very simple model that works But to show a change we have to do a full page reload. Even for things like “your password must have at least 6 characters”.
  7. 7. Nacho Martín nacho@limenius.com @nacmartin Even for things like this New password New Submit
  8. 8. Nacho Martín nacho@limenius.com @nacmartin Even for things like this New password New Submit ****
  9. 9. Nacho Martín nacho@limenius.com @nacmartin Even for things like this New password New Submit **** Your password must contain 6 characters!
  10. 10. Nacho Martín nacho@limenius.com @nacmartin Adding dynamic elements HTML </> Client HTML </> Server
  11. 11. Nacho Martín nacho@limenius.com @nacmartin Adding dynamic elements HTML </> Client HTML </> JS JS Server
  12. 12. Nacho Martín nacho@limenius.com @nacmartin Step 1: Client uses JS to modify the DOM Client HTML </> JS $( "p" ).addClass( “myClass" );
  13. 13. Nacho Martín nacho@limenius.com @nacmartin With DOM modification We can now modify the document reacting to user interaction. What about loading new content based on content interaction?
  14. 14. Nacho Martín nacho@limenius.com @nacmartin Example 1 2 3 4 5
  15. 15. Nacho Martín nacho@limenius.com @nacmartin Adding dynamic content HTML </> Client HTML </> JS JS Server
  16. 16. Nacho Martín nacho@limenius.com @nacmartin Adding dynamic content HTML </> Client HTML </> JS JS Server API
  17. 17. Nacho Martín nacho@limenius.com @nacmartin Step 2: Dynamic content Client HTML </> JS $(“#grid").load( “api/page2.html“ ); API
  18. 18. Nacho Martín nacho@limenius.com @nacmartin Step 2: Dynamic content Client HTML </> JS $(“#grid").load( “api/page2.html“ ); API $.get( “api/page2.json“, function(data) { $(“#grid”).html(renderPage(data)); } );
  19. 19. Nacho Martín nacho@limenius.com @nacmartin DOM Manipulation HTML </> This happens in the Browser Element <body> Element <div id=“grid”> Element <h1> Text “Hi there” … Element <div> Element <div>
  20. 20. Nacho Martín nacho@limenius.com @nacmartin DOM Manipulation This happens in the Browser Element <body> Element <div id=“grid”> Element <h1> Text “Hi there” … Element <div> Element <div> API $.get( “api/page2.json“, function(data) { $(“#grid”).html(renderPage(data)); } );
  21. 21. Nacho Martín nacho@limenius.com @nacmartin DOM Manipulation This happens in the Browser Element <body> Element <div id=“grid”> Element <h1> Text “Hi there” API $.get( “api/page2.json“, function(data) { $(“#grid”).html(renderPage(data)); } );
  22. 22. Nacho Martín nacho@limenius.com @nacmartin DOM Manipulation This happens in the Browser Element <body> Element <div id=“grid”> Element <h1> Text “Hi there” … Element <div> Element <div> API $.get( “api/page2.json“, function(data) { $(“#grid”).html(renderPage(data)); } );
  23. 23. Nacho Martín nacho@limenius.com @nacmartin Problem: duplication of work HTML </> We need a mechanism in the server to build the initial HTML…
  24. 24. Nacho Martín nacho@limenius.com @nacmartin Problem: duplication of work …and another to update the DOM in the client API $(“#grid”).html(renderPage(data));
  25. 25. Nacho Martín nacho@limenius.com @nacmartin Possible solution: don’t render the content in HTML HTML </> <div id=“grid”>
  26. 26. Nacho Martín nacho@limenius.com @nacmartin And on document load do an API call <div id=“grid”> API $(“#grid”).html(renderPage(data));
  27. 27. Nacho Martín nacho@limenius.com @nacmartin This means that the first thing the user sees is this …and also crawlers :(
  28. 28. Nacho Martín nacho@limenius.com @nacmartin Slow page loads in mobile users https://www.doubleclickbygoogle.com/articles/mobile-speed-matters/ • Average load time over 3G: 19 seconds. • 53% of sites that take longer than 3s are abandoned. • Going from 19s to 5s means: • 25% more impressions of ads. • 70% longer sessions. • 35% lower bounce race. • 2x ad revenue.
  29. 29. Nacho Martín nacho@limenius.com @nacmartin When are these problems worse Apps. Bearable. Content pages. Probably unbearable.
  30. 30. Concurrent problem: DOM manipulation vs State based JS libraries
  31. 31. Nacho Martín nacho@limenius.com @nacmartin We want to build a TODO list Pour eggs in the pan How to cook an omelette Buy eggs Break eggs
  32. 32. Nacho Martín nacho@limenius.com @nacmartin We want to build a TODO list Pour eggs in the pan Beat eggs How to cook an omelette Buy eggs Break eggs
  33. 33. Nacho Martín nacho@limenius.com @nacmartin Options
  34. 34. Nacho Martín nacho@limenius.com @nacmartin Options 1: Re-render everything.
  35. 35. Nacho Martín nacho@limenius.com @nacmartin Options 1: Re-render everything. Simple
  36. 36. Nacho Martín nacho@limenius.com @nacmartin Options 1: Re-render everything. Simple Not efficient
  37. 37. Nacho Martín nacho@limenius.com @nacmartin Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple Not efficient
  38. 38. Nacho Martín nacho@limenius.com @nacmartin Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple Complex Not efficient
  39. 39. Nacho Martín nacho@limenius.com @nacmartin Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple EfficientComplex Not efficient
  40. 40. Nacho Martín nacho@limenius.com @nacmartin Options 2: Find in the DOM where to insert elements, what to move, what to remove… 1: Re-render everything. Simple EfficientComplex Not efficient React allows us to do 1, although it does 2 behind the scenes
  41. 41. Nacho Martín nacho@limenius.com @nacmartin Fundamental premise Give me a state and a render() method that depends on it and forget about how and when to render.
  42. 42. Nacho Martín nacho@limenius.com @nacmartin Let’s write a React component Click me! Clicks: 0
  43. 43. Nacho Martín nacho@limenius.com @nacmartin Let’s write a React component Click me! Clicks: 1Click me!
  44. 44. Nacho Martín nacho@limenius.com @nacmartin Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter;
  45. 45. Nacho Martín nacho@limenius.com @nacmartin Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Initial state
  46. 46. Nacho Martín nacho@limenius.com @nacmartin Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Set new state Initial state
  47. 47. Nacho Martín nacho@limenius.com @nacmartin Let’s write a React component import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Set new state render(), called by React Initial state
  48. 48. Nacho Martín nacho@limenius.com @nacmartin Working with state constructor(props) { super(props); this.state = {count: 1}; } Initial state
  49. 49. Nacho Martín nacho@limenius.com @nacmartin Working with state constructor(props) { super(props); this.state = {count: 1}; } Initial state this.setState({count: this.state.count + 1}); Assign state
  50. 50. Nacho Martín nacho@limenius.com @nacmartin render() and JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); It is not HTML, it is JSX. React transforms it internally to HTML elements. Good practice: make render() as clean as possible, only a return.
  51. 51. Nacho Martín nacho@limenius.com @nacmartin render() and JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); }
  52. 52. Nacho Martín nacho@limenius.com @nacmartin render() and JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } Here we don’t modify the state
  53. 53. Nacho Martín nacho@limenius.com @nacmartin render() and JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } Here we don’t make Ajax calls
  54. 54. Nacho Martín nacho@limenius.com @nacmartin render() and JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Click me!</button> <span>Clicks: {this.state.count}</span> </div> ); } Here we don’t calculate decimals of PI and send an e-mail with the result
  55. 55. Nacho Martín nacho@limenius.com @nacmartin Components hierarchy
  56. 56. Nacho Martín nacho@limenius.com @nacmartin Components hierarchy
  57. 57. Nacho Martín nacho@limenius.com @nacmartin Components hierarchy: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  58. 58. Nacho Martín nacho@limenius.com @nacmartin Components hierarchy: props render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Click me! {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } and in Counter… class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  59. 59. Nacho Martín nacho@limenius.com @nacmartin Components hierarchy: props render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Click me! {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } and in Counter… class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  60. 60. Nacho Martín nacho@limenius.com @nacmartin Everything depends on the state, therefore we can:
  61. 61. Nacho Martín nacho@limenius.com @nacmartin Everything depends on the state, therefore we can: •Reproduce states,
  62. 62. Nacho Martín nacho@limenius.com @nacmartin Everything depends on the state, therefore we can: •Reproduce states, •Rewind,
  63. 63. Nacho Martín nacho@limenius.com @nacmartin Everything depends on the state, therefore we can: •Reproduce states, •Rewind, •Log state changes,
  64. 64. Nacho Martín nacho@limenius.com @nacmartin Everything depends on the state, therefore we can: •Reproduce states, •Rewind, •Log state changes, •…
  65. 65. Nacho Martín nacho@limenius.com @nacmartin Everything depends on the state, therefore we can
  66. 66. Nacho Martín nacho@limenius.com @nacmartin Everything depends on the state, therefore we can render the initial state to a HTML string
  67. 67. Server side rendering
  68. 68. Nacho Martín nacho@limenius.com @nacmartin First page load HTML </> Client HTML </> JS JS Server API Includes initial state
  69. 69. Nacho Martín nacho@limenius.com @nacmartin When we need more data Client Server APIAPI API calls to update the state and therefore update its representation (UI)
  70. 70. Nacho Martín nacho@limenius.com @nacmartin ReactDOMServer.renderToString(element) ReactDOM.hydrate(element, container[, callback]) SSR in React
  71. 71. Nacho Martín nacho@limenius.com @nacmartin ReactDOMServer.renderToString(<MyApp/>) SSR in React. 1) In the server: <div data-reactroot=""> This is some <span>server-generated</span> <span>HTML.</span> </div>
  72. 72. Nacho Martín nacho@limenius.com @nacmartin SSR in React. 2) insert in our template <html> … <body> <div id=“root”> <div data-reactroot=""> This is some <span>server-generated</span> <span>HTML.</span> </div> </div> …
  73. 73. Nacho Martín nacho@limenius.com @nacmartin ReactDOM.hydrate( <MyApp/>, document.getElementById('root') ) SSR in React. 1) In the client: <div id=“root”> <div data-reactroot=""> This is some <span>server-generated</span> <span>HTML.</span> </div> </div> … The client takes control over it
  74. 74. Nacho Martín nacho@limenius.com @nacmartin renderer.renderToString(app, (err, html) => { if (err) throw err console.log(html) // => <div data-server-rendered="true">Hello World</div> }) var app = new Vue({ el: ‘#root', data: { message: 'Hello Vue!' } }) Similar, in Vue
  75. 75. Nacho Martín nacho@limenius.com @nacmartin React, Vue, but does my library support this? If what is rendered depends on the state then we are probably good. If your JS depends on having the DOM loaded and manipulate it, then we are not good.
  76. 76. Nacho Martín nacho@limenius.com @nacmartin Problematic Example Page
  77. 77. Nacho Martín nacho@limenius.com @nacmartin Problematic Example Page API My React Component
  78. 78. Nacho Martín nacho@limenius.com @nacmartin Problematic Example Page API My React Component API “React Component” that renders CkEditor, d3, or other lib that relies on DOM manipulation
  79. 79. Nacho Martín nacho@limenius.com @nacmartin Problematic Example Page API My React Component API “React Component” that renders CkEditor, d3, or other lib that relies on DOM manipulation
  80. 80. SSR in PHP
  81. 81. Nacho Martín nacho@limenius.com @nacmartin Why? SSR in JavaScript (node.js) is more natural. But it may not be the right choice for the project. Maybe the team has expertise in PHP. Maybe the project already exists. Maybe we want to combine it with sections rendered from PHP. The Real World is not as simple as tutorials.
  82. 82. Nacho Martín nacho@limenius.com @nacmartin We need
  83. 83. Nacho Martín nacho@limenius.com @nacmartin WebpackAssets JS JS TS SASS PNG JPEG JS Client App
  84. 84. Nacho Martín nacho@limenius.com @nacmartin WebpackAssets JS JS TS SASS PNG JPEG JS Client App JS Server side App
  85. 85. Nacho Martín nacho@limenius.com @nacmartin JS Code to execute Server side JS App + Component and state that we want to render A few bytes, Changes between requests As big as your app. Doesn’t change between requests
  86. 86. Options
  87. 87. Nacho Martín nacho@limenius.com @nacmartin This is what we would do for SSR in JS Client JS Front PHP API JS Client side App Server side JS App + Component and state
  88. 88. Nacho Martín nacho@limenius.com @nacmartin This is what we would do for SSR in JS Client JS Front PHP API JS Client side App Server side JS App + Component and state
  89. 89. Nacho Martín nacho@limenius.com @nacmartin This is what we would do for SSR in JS Client JS Front PHP API JS Client side App Server side JS App + Component and state
  90. 90. Nacho Martín nacho@limenius.com @nacmartin Option 1: node.js as subprocess Client PHP App Node.js JS Client side App Server side JS App + Component and state
  91. 91. Nacho Martín nacho@limenius.com @nacmartin Option 1: node.js as subprocess Client PHP App Node.js JS Client side App Server side JS App + Component and state
  92. 92. Nacho Martín nacho@limenius.com @nacmartin Make a call to node.js using Symfony Process component * Easy to setup. * Slow. Library: https://github.com/nacmartin/phpexecjs Option 1: Call a node.js subprocess
  93. 93. Nacho Martín nacho@limenius.com @nacmartin Option 2: V8JS Client PHP App V8js JS Client side App Server side JS App + Component and state
  94. 94. Nacho Martín nacho@limenius.com @nacmartin Option 2: V8JS Client PHP App V8js JS Client side App Server side JS App + Component and state
  95. 95. Nacho Martín nacho@limenius.com @nacmartin But we can cache public function createContext($code, $cachename = null) { if ($cachename) { $cacheItem = $this->cache->getItem($cachename); if ($cacheItem->isHit()) { $snapshot = $cacheItem->get(); } else { $snapshot = V8Js::createSnapshot($code); $cacheItem->set($snapshot); $this->cache->save($cacheItem); } } else { $snapshot = V8Js::createSnapshot($code); } $this->v8 = new V8Js('PHP', [], [], true, $snapshot); }
  96. 96. Nacho Martín nacho@limenius.com @nacmartin Option 2: V8JS Client PHP App V8js JS Client side App Server side JS App Component and state
  97. 97. Nacho Martín nacho@limenius.com @nacmartin Option 2: V8JS Client PHP App V8js JS Client side App Server side JS App Component and state 📷
  98. 98. Nacho Martín nacho@limenius.com @nacmartin Option 2: V8JS Client PHP App V8js JS Client side App Server side JS App Component and state 📷
  99. 99. Nacho Martín nacho@limenius.com @nacmartin Use PHP extension v8js * Fast. * Need to compile v8, v8js, find Docker images… (this problem is not small). Library: https://github.com/nacmartin/phpexecjs Option 2: v8js PHP extension
  100. 100. Nacho Martín nacho@limenius.com @nacmartin Option 3: External JS server Client PHP App JS renderer JS Client side App Server side JS App Component and state
  101. 101. Nacho Martín nacho@limenius.com @nacmartin Option 3: External JS server Client PHP App JS renderer JS Client side App Server side JS App Component and state
  102. 102. Nacho Martín nacho@limenius.com @nacmartin We have “stupid” node.js server used only to render components. It has <100 LoC, and it doesn’t know anything about our logic. * Fast. There is an example a dummy JS server for this purpose at https://github.com/Limenius/symfony-react-sandbox Option 3: External node.js server
  103. 103. ReactRenderer & ReactBundle
  104. 104. Nacho Martín nacho@limenius.com @nacmartin Some libraries phpexecjsReactRendererReactBundle node.js v8js … Twig extension External renderer Selects JS runner Runs it Uses snapshots if available Integration with Symfony
  105. 105. Nacho Martín nacho@limenius.com @nacmartin Options 1 & 2 $renderer = new PhpExecJsReactRenderer(‘path_to/server-bundle.js’); $ext = new ReactRenderExtension($renderer, 'both'); $twig->addExtension($ext); phpexecjs detects the presence of the extension v8js, if not, calls node.js
  106. 106. Nacho Martín nacho@limenius.com @nacmartin Option 3: external renderer $renderer = new ExternalServerReactRenderer(‘../some_path/node.sock’); $ext = new ReactRenderExtension($renderer, 'both'); $twig->addExtension($ext);
  107. 107. Nacho Martín nacho@limenius.com @nacmartin JS side part: React on Rails https://github.com/shakacode/react_on_rails Used among others by
  108. 108. Nacho Martín nacho@limenius.com @nacmartin JS side part: React on Rails {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  109. 109. Nacho Martín nacho@limenius.com @nacmartin Redux integration
  110. 110. Nacho Martín nacho@limenius.com @nacmartin Redux integration Redux Store
  111. 111. Nacho Martín nacho@limenius.com @nacmartin Redux integration import ReactOnRails from 'react-on-rails' import RecipesAppRedux from './RecipesApp' import configureStore from ' ../store/RecipesStore' const recipesStore = configureStore ReactOnRails.registerStore({ recipesStore }) ReactOnRails.register({ RecipesAppRedux }) Twig: JavaScript: {{ redux_store(‘recipesStore’, initialState) }} {{ react_component('RecipesAppRedux') }}
  112. 112. Nacho Martín nacho@limenius.com @nacmartin Share store between components
  113. 113. Nacho Martín nacho@limenius.com @nacmartin React React React Twig Twig React By sharing store they can share state Twig Share store between components
  114. 114. Things to consider
  115. 115. Context
  116. 116. Nacho Martín nacho@limenius.com @nacmartin Context variables. PHP [ 'serverSide' => $serverSide, 'href' => $request ->getSchemeAndHttpHost() . $request ->getRequestUri(), 'location' => $request ->getRequestUri(), 'scheme' => $request ->getScheme(), 'host' => $request ->getHost(), 'port' => $request ->getPort(), 'base' => $request ->getBaseUrl(), 'pathname' => $request ->getPathInfo(), 'search' => $request ->getQueryString(), ];
  117. 117. Nacho Martín nacho@limenius.com @nacmartin Context in the JS side export default (initialProps, context) => { if (context.serverSide) { return <StaticRouter basename={context.base} location={context.location} />; } else { return <BrowserRouter basename={context.base} />; } };
  118. 118. Header tags
  119. 119. Nacho Martín nacho@limenius.com @nacmartin Extracting headers react-helmet (vue-helmet) import { Helmet } from "react-helmet"; class Application extends React.Component { render() { return ( <div className="application"> <Helmet> <meta charSet="utf-8" /> <title>My Title </title> <link rel="canonical" href="http: //mysite.com/example" /> </Helmet> ... </div> ); } }
  120. 120. Nacho Martín nacho@limenius.com @nacmartin Extracting headers export default (initialProps, context) => ({ renderedHtml: { componentHtml: renderToString( <StaticRouter basename={context.base} location={context.location} context={{}}> <App initialProps={initialProps} appContext={context} /> </StaticRouter> ), title: Helmet.renderStatic().title.toString() } }); Return array instead of component
  121. 121. Nacho Martín nacho@limenius.com @nacmartin Then in Twig {% set recipes = react_component_array('RecipesApp', {'props': props}) %} {% block title %} {{ recipes.title is defined ? recipes.title | raw : '' }} {% endblock title %} {% block body %} {{ recipes.componentHtml | raw }}{{ redux_store('recipesStore', initialState) }} {% endblock body %}
  122. 122. Make reality checks
  123. 123. Nacho Martín nacho@limenius.com @nacmartin Better to try it soon Certain JS code doesn’t make sense in SSR: • Timers: SetTimeout, setInterval. • Access to window, or document objects. • Access to the DOM. • First render that depends on weird things like API calls (this is a smell). Don’t wait until the last moment to check SSR if it is in the roadmap
  124. 124. Summary: What is SSR and what is it for Ways to do it in PHP (pros & cons) Some libraries Tips & practical problems
  125. 125. Thanks! Questions? Nacho Martín nacho@limenius.com @nacmartin

×