Implement react server side rendering and ditch prerendering with puppeteer
This commit is contained in:
+34
-43
@@ -2,59 +2,50 @@
|
||||
const {resolve} = require("path");
|
||||
const {CheckerPlugin} = require("awesome-typescript-loader");
|
||||
const StyleLintPlugin = require("stylelint-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const Dotenv = require("dotenv-webpack");
|
||||
|
||||
module.exports = {
|
||||
resolve: {
|
||||
module.exports = function (env, argv) {
|
||||
const config = {};
|
||||
|
||||
config.resolve = {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx"]
|
||||
},
|
||||
context: resolve(__dirname, "../../src"),
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["babel-loader", "source-map-loader"],
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: ["babel-loader", "awesome-typescript-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", {loader: "css-loader", options: {importLoaders: 1}}, "postcss-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
loaders: [
|
||||
"style-loader",
|
||||
{loader: "css-loader", options: {importLoaders: 1}},
|
||||
"postcss-loader",
|
||||
"sass-loader"
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loaders: [
|
||||
"file-loader?hash=sha512&digest=hex&name=img/[hash].[ext]",
|
||||
"image-webpack-loader?bypassOnDebug&optipng.optimizationLevel=7&gifsicle.interlaced=false"
|
||||
]
|
||||
}
|
||||
};
|
||||
config.context = resolve(__dirname, "../../src");
|
||||
config.module = {
|
||||
rules: [],
|
||||
};
|
||||
config.module.rules.push({
|
||||
test: /\.js$/,
|
||||
use: ["babel-loader", "source-map-loader"],
|
||||
exclude: /node_modules/
|
||||
});
|
||||
config.module.rules.push({
|
||||
test: /\.tsx?$/,
|
||||
use: ["babel-loader", "awesome-typescript-loader"]
|
||||
});
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
loaders: [
|
||||
"file-loader?hash=sha512&digest=hex&name=assets/img/[hash].[ext]",
|
||||
"image-webpack-loader?bypassOnDebug&optipng.optimizationLevel=7&gifsicle.interlaced=false"
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
});
|
||||
|
||||
config.plugins = [
|
||||
new CheckerPlugin(),
|
||||
new StyleLintPlugin(),
|
||||
new HtmlWebpackPlugin({template: "index.html.ejs"}),
|
||||
new Dotenv({
|
||||
path: "./.env.sample"
|
||||
}),
|
||||
new Dotenv({
|
||||
path: "./.env"
|
||||
})
|
||||
],
|
||||
performance: {
|
||||
}),
|
||||
];
|
||||
|
||||
config.performance = {
|
||||
hints: false
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
+30
-11
@@ -2,18 +2,20 @@
|
||||
const merge = require("webpack-merge");
|
||||
const webpack = require("webpack");
|
||||
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
|
||||
const commonConfig = require("./common.js");
|
||||
|
||||
module.exports = merge(commonConfig, {
|
||||
mode: "development",
|
||||
entry: [
|
||||
module.exports = function (env, argv) {
|
||||
const base = commonConfig(env, argv);
|
||||
base.mode = "development";
|
||||
base.entry = [
|
||||
"react-hot-loader/patch", // Activate HMR for React
|
||||
"webpack-dev-server/client?http://0.0.0.0:8080", // Bundle the client for webpack-dev-server and connect to the provided endpoint
|
||||
"webpack/hot/only-dev-server", // Bundle the client for hot reloading, only- means to only hot reload for successful updates
|
||||
"./index.tsx" // The entry point of our app
|
||||
],
|
||||
devServer: {
|
||||
];
|
||||
base.devServer = {
|
||||
hot: true, // Enable HMR on the server
|
||||
historyApiFallback: true,
|
||||
host: '0.0.0.0',
|
||||
@@ -41,9 +43,9 @@ module.exports = merge(commonConfig, {
|
||||
// watchOptions: {
|
||||
// ignored: /node_modules/
|
||||
// }
|
||||
},
|
||||
devtool: "cheap-module-eval-source-map",
|
||||
plugins: [
|
||||
};
|
||||
base.devtool = "cheap-module-eval-source-map";
|
||||
base.plugins = base.plugins.concat([
|
||||
new FaviconsWebpackPlugin({
|
||||
logo: "./assets/img/favicon.png",
|
||||
icons: {
|
||||
@@ -60,6 +62,23 @@ module.exports = merge(commonConfig, {
|
||||
}
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(), // Enable HMR globally
|
||||
new webpack.NamedModulesPlugin() // Prints more readable module names in the browser console on HMR updates
|
||||
]
|
||||
});
|
||||
new webpack.NamedModulesPlugin(), // Prints more readable module names in the browser console on HMR updates
|
||||
new HtmlWebpackPlugin({template: "index.html.ejs"}),
|
||||
]);
|
||||
|
||||
base.module.rules.push({
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", {loader: "css-loader", options: {importLoaders: 1}}, "postcss-loader"]
|
||||
});
|
||||
base.module.rules.push({
|
||||
test: /\.scss$/,
|
||||
loaders: [
|
||||
"style-loader",
|
||||
{loader: "css-loader", options: {importLoaders: 1}},
|
||||
"postcss-loader",
|
||||
"sass-loader"
|
||||
]
|
||||
});
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
+100
-35
@@ -3,48 +3,113 @@ const path = require("path");
|
||||
const merge = require("webpack-merge");
|
||||
|
||||
const resolve = path.resolve;
|
||||
const DynamicCdnWebpackPlugin = require("dynamic-cdn-webpack-plugin");
|
||||
const PrerenderSPAPlugin = require("prerender-spa-plugin");
|
||||
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
|
||||
const nodeExternals = require('webpack-node-externals');
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const WebpackCdnPlugin = require('webpack-cdn-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
/* NOTE: This is a list of all routes that are prerendered for production use.
|
||||
Please list all routes that contain search engine accessible content, i.e.,
|
||||
stuff that you would like to find with a Google Search. */
|
||||
const PRERENDERED_ROUTES = ["/", "/404"];
|
||||
|
||||
const commonConfig = require("./common");
|
||||
|
||||
module.exports = merge(commonConfig, {
|
||||
mode: "production",
|
||||
entry: "./index.tsx",
|
||||
output: {
|
||||
filename: "js/bundle.[hash].min.js",
|
||||
module.exports = function (env, argv) {
|
||||
const base = commonConfig(env, argv);
|
||||
base.mode = "production";
|
||||
base.entry = "./index.tsx";
|
||||
base.output = {
|
||||
path: resolve(__dirname, "../../dist"),
|
||||
publicPath: "/"
|
||||
},
|
||||
devtool: "source-map",
|
||||
plugins: [
|
||||
new FaviconsWebpackPlugin({
|
||||
logo: "./assets/img/favicon.png",
|
||||
icons: {
|
||||
android: true,
|
||||
appleIcon: true,
|
||||
appleStartup: true,
|
||||
coast: true,
|
||||
favicons: true,
|
||||
firefox: true,
|
||||
opengraph: true,
|
||||
twitter: true,
|
||||
yandex: true,
|
||||
windows: true
|
||||
}
|
||||
publicPath: "/",
|
||||
};
|
||||
base.devtool = "source-map";
|
||||
base.plugins.push(
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
// both options are optional
|
||||
filename: "assets/styles.css?[hash]",
|
||||
}),
|
||||
new DynamicCdnWebpackPlugin(),
|
||||
new PrerenderSPAPlugin({
|
||||
// Required - The path to the webpack-outputted app to prerender.
|
||||
staticDir: resolve(__dirname, "../../dist"),
|
||||
// Required - Routes to render.
|
||||
routes: PRERENDERED_ROUTES
|
||||
})
|
||||
]
|
||||
});
|
||||
);
|
||||
|
||||
if (env.platform === 'server') {
|
||||
base.entry = "./server/index.ts";
|
||||
base.output.filename = 'js/server.js?[hash]';
|
||||
base.target = 'node';
|
||||
base.externals = [nodeExternals()];
|
||||
|
||||
base.module.rules.push({
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{loader: "css-loader", options: {importLoaders: 1}},
|
||||
"postcss-loader"
|
||||
],
|
||||
});
|
||||
|
||||
base.module.rules.push({
|
||||
test: /\.scss$/,
|
||||
loaders: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{loader: "css-loader", options: {importLoaders: 1}},
|
||||
"postcss-loader",
|
||||
"sass-loader"
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
else if (env.platform === 'client') {
|
||||
base.entry = './client/index.ts';
|
||||
base.output.filename = 'js/client.js?[hash]';
|
||||
base.target = 'web';
|
||||
|
||||
base.module.rules.push({
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{loader: "css-loader", options: {importLoaders: 1}},
|
||||
"postcss-loader"
|
||||
],
|
||||
});
|
||||
|
||||
base.module.rules.push({
|
||||
test: /\.scss$/,
|
||||
loaders: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{loader: "css-loader", options: {importLoaders: 1}},
|
||||
"postcss-loader",
|
||||
"sass-loader"
|
||||
]
|
||||
});
|
||||
|
||||
base.plugins.push(
|
||||
new HtmlWebpackPlugin({
|
||||
template: "index.html.ejs",
|
||||
}),
|
||||
new FaviconsWebpackPlugin({
|
||||
logo: "./assets/img/favicon.png",
|
||||
icons: {
|
||||
android: true,
|
||||
appleIcon: true,
|
||||
appleStartup: true,
|
||||
coast: true,
|
||||
favicons: true,
|
||||
firefox: true,
|
||||
opengraph: true,
|
||||
twitter: true,
|
||||
yandex: true,
|
||||
windows: true
|
||||
}
|
||||
}),
|
||||
new WebpackCdnPlugin({
|
||||
modules: [
|
||||
{ name: 'react', var: 'React', path: `umd/react.${process.env.NODE_ENV}.min.js` },
|
||||
{ name: 'react-dom', var: 'ReactDOM', path: `umd/react-dom.${process.env.NODE_ENV}.min.js` },
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
||||
Generated
+532
-202
File diff suppressed because it is too large
Load Diff
+12
-8
@@ -16,15 +16,16 @@
|
||||
"license": "MIT",
|
||||
"homepage": "https://sik.ayy.fi",
|
||||
"scripts": {
|
||||
"build": "npm run clean-dist && webpack -p --config=configs/webpack/prod.js",
|
||||
"clean-dist": "rm -f -r -d dist",
|
||||
"build": "NODE_ENV=production npm-run-all build:client build:server",
|
||||
"build:server": "webpack -p --config=configs/webpack/prod.js --env.platform=server",
|
||||
"build:client": "webpack -p --config=configs/webpack/prod.js --env.platform=client",
|
||||
"lint": "npm run lint:ts && npm run lint:sass",
|
||||
"lint:ts": "tslint -p tsconfig.json --format stylish --exclude node_modules",
|
||||
"lint:ts:fix": "tslint -p tsconfig.json --format stylish --exclude node_modules --fix",
|
||||
"lint:sass": "stylelint ./src/**/**/*.scss ./src/**/*.scss ./src/*.scss",
|
||||
"start": "npm run start-dev",
|
||||
"start-dev": "webpack-dev-server --config=configs/webpack/dev.js",
|
||||
"serve": "serve -p 3000 dist",
|
||||
"serve": "node dist/js/server.js",
|
||||
"start-prod": "npm run build && npm run serve",
|
||||
"mock-backend": "json-server --watch db.json -p 1234",
|
||||
"test": "npm-run-all lint test:e2e",
|
||||
@@ -47,10 +48,11 @@
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-minify": "^0.4.3",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"css-loader": "^0.28.11",
|
||||
"dotenv-webpack": "^1.5.7",
|
||||
"dynamic-cdn-webpack-plugin": "^4.0.0-rc.1",
|
||||
"express": "^4.16.3",
|
||||
"favicons-webpack-plugin": "0.0.9",
|
||||
"file-loader": "^1.1.11",
|
||||
"fs-extra": "^6.0.1",
|
||||
@@ -58,13 +60,13 @@
|
||||
"husky": "^1.0.0-rc.9",
|
||||
"image-webpack-loader": "^4.3.0",
|
||||
"json-server": "^0.14.0",
|
||||
"mini-css-extract-plugin": "^0.4.1",
|
||||
"module-to-cdn": "^3.1.2",
|
||||
"morgan": "^1.9.0",
|
||||
"node-sass": "^4.9.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"plop": "^2.0.0",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"prerender-spa-plugin": "^3.2.1",
|
||||
"puppeteer": "^1.5.0",
|
||||
"react": "^16.4.0",
|
||||
"react-addons-test-utils": "^15.6.2",
|
||||
"react-dom": "^16.4.0",
|
||||
@@ -75,15 +77,17 @@
|
||||
"stylelint": "^9.2.1",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"stylelint-webpack-plugin": "^0.10.5",
|
||||
"testcafe": "^0.20.3",
|
||||
"testcafe": "^0.20.5",
|
||||
"testcafe-react-selectors": "^2.1.0",
|
||||
"tslint": "^5.10.0",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"webpack": "^4.11.1",
|
||||
"webpack-cdn-plugin": "^2.2.0",
|
||||
"webpack-cli": "^3.0.3",
|
||||
"webpack-dev-middleware": "^3.1.3",
|
||||
"webpack-dev-server": "^3.1.4",
|
||||
"webpack-merge": "^4.1.2"
|
||||
"webpack-merge": "^4.1.2",
|
||||
"webpack-node-externals": "^1.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import Routes from "../routes";
|
||||
|
||||
export default () => (
|
||||
<BrowserRouter>
|
||||
<Routes />
|
||||
</BrowserRouter>
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { hydrate } from "react-dom";
|
||||
import App from "./App";
|
||||
|
||||
const elem = document.getElementById("root");
|
||||
|
||||
hydrate(React.createElement(App), elem);
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
@import "./assets/scss/normalize";
|
||||
@import "./assets/scss/globals";
|
||||
|
||||
html,
|
||||
@@ -19,7 +20,7 @@ body {
|
||||
body {
|
||||
max-width: 1200px;
|
||||
padding: 0 0.5rem;
|
||||
margin: auto;
|
||||
margin: auto !important;
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
@@ -3,9 +3,7 @@ import {render} from "react-dom";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import {AppContainer} from "react-hot-loader";
|
||||
import Routes from "./routes";
|
||||
import "normalize.css";
|
||||
import "./index.scss";
|
||||
import "./index.d.ts";
|
||||
|
||||
const rootEl = document.getElementById("root");
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Switch, Route } from "react-router-dom";
|
||||
import FrontPage from "./pages/FrontPage";
|
||||
import NotFoundPage from "./pages/NotFoundPage";
|
||||
import CommonPage from "./pages/CommonPage";
|
||||
import "./index.scss";
|
||||
|
||||
const Routes = () => (
|
||||
<Switch>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { StaticRouter } from "react-router-dom";
|
||||
import Routes from "../routes";
|
||||
|
||||
export default ({ url }) => (
|
||||
<StaticRouter context={{}} location={url}>
|
||||
<Routes />
|
||||
</StaticRouter>
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import * as express from "express";
|
||||
import { renderToString } from "react-dom/server";
|
||||
import * as morgan from "morgan";
|
||||
|
||||
import App from "./App";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
const port = 3000;
|
||||
const server = express();
|
||||
|
||||
const indexHtml = fs.readFileSync(path.resolve("./dist/index.html"), "utf-8");
|
||||
const html = body => indexHtml.replace("<div id=\"root\"></div>", `<div id="root">${body}</div>`);
|
||||
|
||||
server.use(morgan("short"));
|
||||
server.use("/assets", express.static("dist/assets"));
|
||||
server.use("/js", express.static("dist/js"));
|
||||
|
||||
server.get("*", (req, res) => {
|
||||
const result = renderToString(React.createElement(App, { url: req.url }));
|
||||
res.send(
|
||||
html(result)
|
||||
);
|
||||
});
|
||||
|
||||
server.listen(3000, () => console.log("React SSR express server listening on port 3000!"));
|
||||
Reference in New Issue
Block a user