Skip to content

Instantly share code, notes, and snippets.

@lmbuffetti
Last active December 6, 2019 16:03
Show Gist options
  • Save lmbuffetti/ce4484ebf0e5bbe07255df260df5bbaf to your computer and use it in GitHub Desktop.
Save lmbuffetti/ce4484ebf0e5bbe07255df260df5bbaf to your computer and use it in GitHub Desktop.
Server Side Rendering
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';
var request = require('request');
var fs = require("fs");
const app = express();
const PORT = 46130;
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
// app.use(bodyParser.json({ type: 'application/vnd.api+json' }));
// 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}!`));
const targetEntry = `http://localhost:${PORT}/`;
console.log(targetEntry);
});
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';
import manifest from '../dist/manifest.json';
import { getEnviroment, getConfig } from '../src/controllers/checkEnvironment';
import { config } from '../src/constants/global';
const analitycs = (env) => {
const gaId = config()[getConfig(env)].googleAnalyticsId;
return `
<script>
(function(i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function() {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', '${gaId}', 'auto');
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
</script>`;
};
const fbPixel = (env) => {
const fbPixelID = config()[getConfig(env)].facebookPixel;
return `
<script>
! function(f, b, e, v, n, t, s) {
if (f.fbq) return;
n = f.fbq = function() {
n.callMethod ?
n.callMethod.apply(n, arguments) : n.queue.push(arguments)
};
if (!f._fbq) f._fbq = n;
n.push = n;
n.loaded = !0;
n.version = '2.0';
n.queue = [];
t = b.createElement(e);
t.async = !0;
t.src = v;
s = b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t, s)
}(window,
document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '${fbPixelID}');
fbq('track', 'PageView');
</script>
<noscript><img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id=1803717239894086&ev=PageView&noscript=1"
/></noscript>`;
};
const transifex = (env, lang) => {
const transifexID = config()[getConfig(env)].transifexId;
return `
<script type="text/javascript">
window.liveSettings = {
api_key: '${transifexID}',
picker: "#transifex-selector",
detectlang: function() {
return '${lang}';
},
autocollect: true,
dynamic: true
};
</script>
<script type="text/javascript" src="//cdn.transifex.com/live.js"></script>`;
};
const extraScript = (env, lang) => `${transifex(env, lang)}${fbPixel(env)}${analitycs(env)}`;
const injectHTML = (val, {
htmlAttrs, bodyAttrs, content, title, meta, link, scripts, state, script, styles,
}) => {
let data = val.replace(/<script.*?>.*?<\/script>/ig, '');
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 (styles) data = data.replace('</head>', `${styles}</head>`);
if (scripts) data = data.replace('</body>', `${scripts.join('')}</body>`);
if (script) data = data.replace('</body>', `${script}</body>`);
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('./')) > -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(
// eslint-disable-next-line react/jsx-filename-extension
<Loadable.Capture report={(m) => modules.push(m)}>
<Provider store={store}>
<StaticRouter location={req.path} context={context}>
<Frontload isServer>
<App />
</Frontload>
</StaticRouter>
</Provider>
</Loadable.Capture>,
)).then((content) => {
if (context.url) {
res.redirect(context.url);
return res.end();
}
const extraChunks = extractAssets(manifest, modules, extraScript).map(
(c) => `<script type='text/javascript' src='/${c.replace(/^\//, '')}'></script>`,
);
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: extraChunks,
state: JSON.stringify(store.getState()).replace(/</g, '\\u003c'),
script: extraScript(getEnviroment(req.headers.host, req).env, 'en'),
});
res.send(html);
return null;
});
};
process.env.NODE_ENV = process.argv[process.argv.length - 1];
process.env.BABEL_ENV = process.argv[process.argv.length - 1];
const path = require('path');
const webpack = require('webpack');
const NodemonPlugin = require('nodemon-webpack-plugin');
const PreloadWebpackPlugin = require('preload-webpack-plugin');
const cssModuleRegex = /\.module\.css$/;
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const CopyPlugin = require('copy-webpack-plugin');
// const devMode = false;
const ROOT_DIR = path.resolve(__dirname, './');
const BUILD_DIR = path.join(ROOT_DIR, '../dist');
const devMode = process.argv[process.argv.length - 1] === 'development';
const clientConfig = {
mode: 'client',
devtool: devMode ? 'inline-source-map' : 'cheap-source-map',
entry: [
'@babel/polyfill',
'./src/index.jsx',
'./src/styles/SCSS/general/main.scss',
],
output: {
path: BUILD_DIR,
filename: 'js/[name].bundle.js',
chunkFilename: 'js/[name].[hash].js',
publicPath: '/',
hotUpdateChunkFilename: 'hot/hot-update.[hash].js',
hotUpdateMainFilename: 'hot/hot-update.[hash].json',
globalObject: 'this'
},
stats: {
all: devMode,
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: devMode,
},
},
{
loader: 'css-loader',
options: {
sourceMap: devMode,
},
},
{
loader: 'resolve-url-loader',
options: {
debug: true,
root: __dirname,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: devMode,
},
},
],
},
{
test: /\.jsx?/,
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',
'react-loadable/babel',
],
},
},
},
{
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
},
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
},
},
],
},
{
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
},
}],
},
{
test: /\.(jpe?g|png|gif)$/i,
use: [{
loader: 'file-loader?name=images/[name].[ext]',
options: {
esModule: false,
},
}],
},
{
type: 'javascript/auto',
exclude: /(node_modules|bower_components|public)/,
test: /\.json$/,
use: [
{
loader: 'file-loader',
options: {
name: 'json/[name].[ext]',
},
},
],
},
],
},
plugins: [
new ExtractCssChunks({
hot: true,
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? 'css/[name].css' : 'css/[name].[hash].css',
chunkFilename: devMode ? 'css/[id].css' : 'css/[id].[hash].css',
}),
new CleanWebpackPlugin({
dry: false,
verbose: true,
cleanOnceBeforeBuildPatterns: ['*', '!manifest.json'],
dangerouslyAllowCleanPatternsOutsideProject: true,
}),
// new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
hash: true,
filename: './index.html',
template: './public/index.html',
minify: {
collapseWhitespace: true,
},
}),
new webpack.DefinePlugin({
NODE_ENV: JSON.stringify(devMode ? 'development' : 'production'),
'process.env': {
NODE_ENV: JSON.stringify(devMode ? 'development' : 'production'),
},
'process.env.client': true,
'process.env.BROWSER': JSON.stringify(true)
}),
new WebpackAssetsManifest(),
],
resolve: {
extensions: ['.js', '.jsx'],
},
optimization: {
namedModules: true,
namedChunks: true,
nodeEnv: devMode ? 'development' : 'production',
flagIncludedChunks: true,
occurrenceOrder: true,
sideEffects: true,
usedExports: true,
concatenateModules: true,
minimize: !devMode,
runtimeChunk: {
name: 'runtime',
},
splitChunks: {
chunks: 'async',
minSize: 500,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
automaticNameMaxLength: 30,
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
},
},
removeAvailableModules: !devMode,
noEmitOnErrors: !devMode,
checkWasmTypes: false,
minimizer: [
new TerserPlugin({
cache: !devMode,
parallel: true,
sourceMap: devMode,
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
map: {
inline: !devMode,
annotation: true,
},
},
}),
],
},
};
module.exports = [clientConfig];
process.env.NODE_ENV = process.argv[process.argv.length - 1];
process.env.BABEL_ENV = process.argv[process.argv.length - 1];
const path = require('path');
const webpack = require('webpack');
const NodemonPlugin = require('nodemon-webpack-plugin');
const PreloadWebpackPlugin = require('preload-webpack-plugin');
const cssModuleRegex = /\.module\.css$/;
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const WebpackAssetsManifest = require('webpack-assets-manifest');
const CopyPlugin = require('copy-webpack-plugin');
// const devMode = false;
const ROOT_DIR = path.resolve(__dirname, './');
const BUILD_DIR = path.join(ROOT_DIR, '../dist');
const devMode = process.argv[process.argv.length - 1] === 'development';
const serverConfig = {
mode: 'server',
entry: ['@babel/polyfill', `${__dirname}/index.js`],
target: 'node',
node: {
__filename: false,
__dirname: false,
},
stats: {
all: devMode,
},
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'server.js',
chunkFilename: 'js/server/[name].js',
publicPath: '/',
globalObject: 'this'
},
plugins: [
new NodemonPlugin({
verbose: true,
watch: path.resolve('./dist'),
script: './dist/server.js',
// Extensions to watch
ext: 'js,njk,json',
}),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new webpack.DefinePlugin({
__CLIENT__: true,
NODE_ENV: JSON.stringify(devMode ? 'development' : 'production'),
'process.env': {
NODE_ENV: JSON.stringify(devMode ? 'development' : 'production'),
},
'process.env.client': false,
}),
],
resolve: {
extensions: ['.js', '.jsx'],
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
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',
'react-loadable/babel',
],
},
},
},
{
test: cssModuleRegex,
use: [
{
loader: 'css-loader/locals',
options: {
modules: true,
getLocalIdent: getCSSModuleLocalIdent,
},
},
],
},
{ test: /\.css$/, exclude: /\.module.css$/, loader: 'ignore-loader' },
{
test: /\.svg$/,
use: ['@svgr/webpack', 'url-loader'],
},
{
test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/',
},
}],
},
{
test: /\.(jpe?g|png|gif)$/i,
use: [
'file-loader?name=images/[name].[ext]',
],
},
{
// Loads the javacript into html template provided.
// Entry point is set below in HtmlWebPackPlugin in Plugins
test: /\.html$/,
use: [
{
loader: 'html-loader',
},
],
},
],
},
};
module.exports = [serverConfig];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment