Skip to content

Instantly share code, notes, and snippets.

@wwiechorek
Last active January 4, 2019 15:04
Show Gist options
  • Save wwiechorek/2dea042aa0924c606abfe14678e9d9d7 to your computer and use it in GitHub Desktop.
Save wwiechorek/2dea042aa0924c606abfe14678e9d9d7 to your computer and use it in GitHub Desktop.
SSR - React com create-react-app
import bodyParser from 'body-parser';
import compression from 'compression';
import express from 'express';
import cookieParser from 'cookie-parser';
import Loadable from 'react-loadable';
import render from './render';
const app = express();
const PORT = process.env.PORT || 3000;
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
//bloqueia o acesso ao arquivo de servidor
app.use('/server.js', (req, res) => {
res.status(404).send();
});
//bloqueia o static renderizar o index.html antes do ssr
app.get('/', render)
//arquivos estaticos
app.use(express.static(__dirname + '/'));
//renderiza react para todas outras urls
app.use(render)
Loadable.preloadAll().then(() => {
app.listen(PORT, console.log(`App listening on port ${PORT}!`));
})
[...]
//scripts
"server-dev": "webpack --w --config ./server/webpack.config.js --mode=development",
"server-build": "webpack --config ./server/webpack.config.js --mode=production"
//Dev dependences:
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"body-parser": "^1.18.3",
"compression": "^1.7.3",
"cookie-parser": "^1.4.3",
"express": "^4.16.4",
"ignore-loader": "^0.1.2",
"nodemon-webpack-plugin": "^4.0.7",
"react-helmet": "^5.2.0",
"react-loadable": "^5.5.0",
"react-router-dom": "^4.3.1",
"webpack-cli": "^3.1.2"
[...]
import fs from 'fs'
import path from 'path'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
import Helmet from 'react-helmet'
import Loadable from 'react-loadable'
import { Provider } from "react-redux"
import { Frontload, frontloadServerRender } from 'react-frontload'
import configureStore from "../src/store"
import App from '../src/app.js'
import manifest from '../build/asset-manifest.json'
const injectHTML = (data, { htmlAttrs, bodyAttrs, content, title, meta, link, scripts, styles, state }) => {
if(htmlAttrs)
data = data.replace(/<html .*?>/, `<html ${htmlAttrs}>`);
if(title)
data = data.replace(/<title>.*?<\/title>/g, title);
if(bodyAttrs)
data = data.replace("<body>", `<body ${bodyAttrs}>`);
if(meta)
data = data.replace('</head>', `${meta}</head>`);
if(link)
data = data.replace('</head>', `${link}</head>`);
if(scripts)
data = data.replace('</body>', scripts.join('') + '</body>');
if(styles)
data = data.replace('</head>', styles.join('') + `</head>`);
data = data.replace(
'<div id="root"></div>',
`<div id="root">${content}${state ? `<script>window.__initialData__ = ${state}</script>` : ''}</div>`
);
return data;
};
const htmlData = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf8')
const extractAssets = (assets, chunks) =>
Object.keys(assets)
.filter(asset => chunks.indexOf(asset.replace('.js', '').replace('.css', '')) > -1)
.map(k => assets[k]);
export default (req, res) => {
const context = {}
const modules = []
const store = configureStore()
//isomorphic cookie
global.document = {
cookie: req.headers.cookie
}
frontloadServerRender(() =>
renderToString(
<Loadable.Capture report={m => modules.push(m)}>
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<Frontload isServer={true}>
<App />
</Frontload>
</StaticRouter>
</Provider>
</Loadable.Capture>
)
).then(content => {
//verifica se tem redirecionamento de url
if(context.url) {
res.redirect(context.url)
return res.end()
}
const styleChunks = []
const scriptChunks = []
extractAssets(manifest, modules).map(
c => {
if(/(.*)\.js/.test(c))
scriptChunks.push(`<script type="text/javascript" src="/${c.replace(/^\//, '')}"></script>`)
if(/(.*)\.css/.test(c))
styleChunks.push(`<link rel="stylesheet" href="/${c.replace(/^\//, '')}">`)
return;
}
);
const helmet = Helmet.renderStatic();
const htmlAttrs = helmet.htmlAttributes.toString();
const bodyAttrs = helmet.bodyAttributes.toString();
const html = injectHTML(htmlData, {
htmlAttrs,
bodyAttrs,
content,
title: helmet.title.toString(),
meta: helmet.meta.toString(),
link: helmet.link.toString(),
scripts: scriptChunks,
styles: styleChunks,
state: JSON.stringify(store.getState()).replace(/</g, '\\u003c')
})
res.send(html)
})
}
process.env.NODE_ENV = 'production'
process.env.BABEL_ENV = 'production'
const path = require('path')
const webpack = require('webpack')
const NodemonPlugin = require('nodemon-webpack-plugin')
const cssModuleRegex = /\.module\.css$/;
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
module.exports = {
entry: ["@babel/polyfill", __dirname + "/index.js"],
target: "node",
node: {
__filename: false,
__dirname: false
},
output: {
path: path.resolve(__dirname, '../build'),
filename: "server.js"
},
plugins: [
new NodemonPlugin(),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
}),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-syntax-dynamic-import",
'dynamic-import-node',
"react-loadable/babel"
],
},
}
},
{
test: cssModuleRegex,
use: [
{
loader: "css-loader/locals",
options: {
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
}
},
]
},
{ test: /\.css$/, exclude: /\.module.css$/, loader: 'ignore-loader' },
{
test: [/\.bmp$/, /\.gif$/, /\.svg$/, /\.jpe?g$/, /\.png$/],
loader: 'file-loader',
options: {
name: '/static/media/[name].[hash:8].[ext]',
emitFile: false //não copiar os arquivos, apenas colocar o caminho dos mesmos
},
}
],
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment