I have an isomorphic react app. It has a server.js file inside app/src/ directory.
Server.js
import path from 'path';
import express from 'express';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import expressJwt, { UnauthorizedError as Jwt401Error } from 'express-jwt';
import nodeFetch from 'node-fetch';
import React from 'react';
import ReactDOM from 'react-dom/server';
import PrettyError from 'pretty-error';
import App from './components/App';
import Html from './components/Html';
import { ErrorPageWithoutStyle } from './routes/error/ErrorPage';
import errorPageStyle from './routes/error/ErrorPage.css';
import createFetch from './createFetch';
import router from './router';
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
import configureStore from './store/configureStore';
import { setRuntimeVariable } from './actions/runtime';
import config from './config';
const app = express();
//
// Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the
// user agent is not known.
// -----------------------------------------------------------------------------
global.navigator = global.navigator || {};
global.navigator.userAgent = global.navigator.userAgent || 'all';
//
// Register Node.js middleware
// -----------------------------------------------------------------------------
app.use(express.static(path.resolve(__dirname, 'public')));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//
// Authentication
// -----------------------------------------------------------------------------
app.use(
expressJwt({
secret: config.auth.jwt.secret,
credentialsRequired: false,
getToken: req => req.cookies.id_token,
}),
);
// Error handler for express-jwt
app.use((err, req, res, next) => {
// eslint-disable-line no-unused-vars
if (err instanceof Jwt401Error) {
console.error('[express-jwt-error]', req.cookies.id_token);
// `clearCookie`, otherwise user can't use web-app until cookie expires
res.clearCookie('id_token');
}
next(err);
});
if (__DEV__) {
app.enable('trust proxy');
}
//
// Register server-side rendering middleware
// -----------------------------------------------------------------------------
app.get('*', async (req, res, next) => {
try {
const css = new Set();
// Universal HTTP client
const fetch = createFetch(nodeFetch, {
baseUrl: config.api.serverUrl,
cookie: req.headers.cookie,
});
const initialState = {
user: req.user || null,
};
const store = configureStore(initialState, {
fetch,
// I should not use `history` on server.. but how I do redirection? follow universal-router
});
store.dispatch(
setRuntimeVariable({
name: 'initialNow',
value: Date.now(),
}),
);
// Global (context) variables that can be easily accessed from any React component
// https://facebook.github.io/react/docs/context.html
const context = {
// Enables critical path CSS rendering
// https://github.com/kriasoft/isomorphic-style-loader
insertCss: (...styles) => {
// eslint-disable-next-line no-underscore-dangle
styles.forEach(style => css.add(style._getCss()));
},
fetch,
// You can access redux through react-redux connect
store,
storeSubscription: null,
};
const route = await router.resolve({
...context,
pathname: req.path,
query: req.query,
});
if (route.redirect) {
res.redirect(route.status || 302, route.redirect);
return;
}
const data = { ...route };
data.children = ReactDOM.renderToString(
<App context={context} store={store}>
{route.component}
</App>,
);
data.styles = [{ id: 'css', cssText: [...css].join('') }];
data.scripts = [assets.vendor.js];
if (route.chunks) {
data.scripts.push(...route.chunks.map(chunk => assets[chunk].js));
}
data.scripts.push(assets.client.js);
data.app = {
apiUrl: config.api.clientUrl,
state: context.store.getState(),
};
const html = ReactDOM.renderToStaticMarkup(<Html {...data} />);
res.status(route.status || 200);
res.send(`<!doctype html>${html}`);
} catch (err) {
next(err);
}
});
//
// Error handling
// -----------------------------------------------------------------------------
const pe = new PrettyError();
pe.skipNodeFiles();
pe.skipPackage('express');
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
console.error(pe.render(err));
const html = ReactDOM.renderToStaticMarkup(
<Html
title="Internal Server Error"
description={err.message}
styles={[{ id: 'css', cssText: errorPageStyle._getCss() }]} // eslint-disable-line no-underscore-dangle
>
{ReactDOM.renderToString(<ErrorPageWithoutStyle error={err} />)}
</Html>,
);
res.status(err.status || 500);
res.send(`<!doctype html>${html}`);
});
//
// Launch the server
// -----------------------------------------------------------------------------
if (!module.hot) {
app.listen(config.port, () => {
console.info(`The server is running at http://localhost:${config.port}/`);
});
}
//
// Hot Module Replacement
// -----------------------------------------------------------------------------
if (module.hot) {
app.hot = module.hot;
module.hot.accept('./router');
}
export default app;
I want to deploy my app using firebase. For that I have setup firebase.json like so -
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "build",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"function": "app"
}
]
}
}
where app is a firebase function defined inside app/functions/ i.e. src and functions have same parent directory.
Functions directory has node_modules, properly configured and its working when tested for other functions.
Problem -
My index.js file inside app/functions is like so -
import app from '../src/server';
import functions from 'firebase-functions';
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.app = functions.https.onRequest(app);
Firebase is complaining that it doesnt support ES6. How do I get this function to work with ES6? I cannot simply change functions/index.js file to ES5 and hope things will work as when requires are resolved, they'd expect everything inside to be ES5 as well which is not the case as my entire codebase is in ES6.
I found the solution to this problem.
Make a directory in functions/ say 'build'.
Build your project
Copy all the contents of your ./dist or ./build (wherever your build files are present) to functions/build
Modify functions/index.js as follows -
const app = require('./build/bundle.js').default;
const functions = require('firebase-functions');
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.app = functions.https.onRequest(app);
Works like a charm
Related
I have created a react (TS) using babel + webpack. It is working fine. Now I am converting it to a PWA. I followed this webpack article to updated webpack.config.js. To register the service worker, instead of doing it in index.html, I created a service-worker.tsx and referencing it in index.tsx.
I am getting this error
service-worker.ts:22 Uncaught ReferenceError: process is not defined
at register (service-worker.ts:22:33)
at eval (index.tsx:14:60)
at ./src/index.tsx (main.js:219:1)
at webpack_require (main.js:253:41)
at main.js:322:37
at main.js:324:12
I have added 3 files, where I changed the configuration to integrate workbox, for registering and un registering.
webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const path = require("path");
// const htmlPlugin = new HtmlWebPackPlugin({
// template: "./src/index.html",
// filename: "./index.html",
// });
const htmlWebPackPlugin = new HtmlWebPackPlugin({
title: "PWA Webpack Demo",
template: "./src/index.html",
filename: "./index.html",
});
const workBoxPlugin_Generate = new WorkboxPlugin.GenerateSW({
// these options encourage the ServiceWorkers to get in there fast
// and not allow any straggling "old" SWs to hang around
clientsClaim: true,
skipWaiting: true,
});
module.exports = {
entry: "./src/index.tsx",
resolve: {
extensions: [".ts", ".tsx", ".js"],
},
output: {
// NEW
path: path.join(__dirname, "dist"),
filename: "[name].js",
}, // NEW Ends
plugins: [htmlWebPackPlugin, workBoxPlugin_Generate],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: ["ts-loader"],
},
// css rules
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
};
service-worker.tsx (error at export default function register)
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
),
);
export default function register() {
if ("serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
process.env.PUBLIC_URL!,
window.location.toString(),
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
"This web app is being served cache-first by a service " +
"worker.",
);
});
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl: string) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker) {
installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a 'New content is
// available; please refresh.' message in your web app.
console.log("New content is available; please refresh.");
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// 'Content is cached for offline use.' message.
console.log("Content is cached for offline use.");
}
}
};
}
};
})
.catch(error => {
console.error("Error during service worker registration:", error);
});
}
function checkValidServiceWorker(swUrl: string) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get("content-type")!.indexOf("javascript") === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
"No internet connection found. App is running in offline mode.",
);
});
}
export function unregister() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
index.tsx
import { createRoot } from 'react-dom/client';
import App from "./app";
//import * as serviceWorker from './serviceWorker';
import registerServiceWorker from './service-worker'
const container = document.getElementById('root')!
const root = createRoot(container)
root.render(<App />)
console.log('Registering service worker', 'Starting...')
registerServiceWorker()
After a little bit of research I found out that I needed to update the webpack.config.js with the following process definitions. It was not a problem of export as I was initially thinking. In my service worker I am using process.env entities.
In fact you can add other process.env parameters, I got my information from this article and this stackoverflow
In the webpack.config.js add the following:
const webpack = require("webpack");
const processEnvPlugin = new webpack.DefinePlugin({"process.env.PUBLIC_URL": JSON.stringify(process.env.PUBLIC_URL)});
plugins: [htmlWebPackPlugin, workBoxPlugin_Generate, processEnvPlugin]
Firebase version: 9.6.6
firebase-messaging.js - inside a src folder
import firebase from "firebase/compat";
import "firebase/messaging";
const firebaseConfig = {
//api
};
firebase.initializeApp(firebaseConfig);
const messaging = firebase.messaging();
export const requestForToken = async () => {
const swRegistration = await navigator.serviceWorker.register(`${process.env.PUBLIC_URL}/firebase-messaging-sw.js`);
const token = await messaging.getToken({
serviceWorkerRegistration: swRegistration,
});
return token;
};
export const onMessageListener = () =>
new Promise((resolve) => {
messaging.onMessage((payload) => {
resolve(payload);
});
});
firebase-messaging-sw.js - inside a public folder
import { initializeApp } from "firebase/app";
import { getMessaging } from "firebase/messaging/sw";
const firebaseApp = initializeApp({
//api
});
const messaging = getMessaging(firebaseApp);
Inside App.js
useEffect(()=> {
if (!fcm_token) {
requestForToken()
.then(r => {
console.log("token: ", r);
dispatch(setFCMToken(r));
})
.catch(error => {
console.log("error while receiver fcm token from firebase: ", error)
});
} else {
console.log("fcm token: ", fcm_token);
}
}, [])
onMessageListener()
.then(value => console.log("notification msg: ", value))
.catch(err => console.log("error: ", err))
When I run the project
Failed to register a ServiceWorker for scope ('http://localhost:3000/build/')
with script ('http://localhost:3000/build/firebase-messaging-sw.js'):
ServiceWorker script evaluation failed
this error shows up, even though the code is correct according to documentation.
Error persists even if I change the way I import inside firebase-messaging-sw.js file to "importScripts(' ')".
Only after removing everything in firebase-messaging-sw.js file, that error do not show up and I can receive the FCM token.
However, when I send test messages from firebase console to FCM token received in my react app, no messages are showing up in console.
Question: How should I change my code to be able to receive notifications and show them in console or as a notification in browser.
If you're using latest version there is alittle bit modifications in registering service worker file, this is complete process of notification configuration
register service worker as below in your service worker file:
export const registerServiceWorker = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('firebase-messaging-sw.js')
.then(function (registration) {
return registration.scope;
})
.catch(function (err) {
return err;
});
}
};
import service worker, and call function in your index.js File:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { registerServiceWorker } from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
registerServiceWorker();
After this configuration, you will see fire base notification in console, and if firebase-messaging.js file is configured well with no error, your feature will work
I'm using react-boilerplate for my App (Using SSR branch). For some reason we need server side rendering. Also we have some rest API and we need to call one API before all API (for register something). I thinks for initial state I need to call this (first API that need data for registration ) API on the server and save response data into store and return store to client. In react-boilerplate for create store:
/**
* Create the store with asynchronously loaded reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
const sagaMiddleware = createSagaMiddleware();
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
];
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
/* eslint-disable no-underscore-dangle */
const composeEnhancers =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
/* eslint-enable */
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers)
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.asyncReducers = {}; // Async reducer registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
import('./reducers').then((reducerModule) => {
const createReducers = reducerModule.default;
const nextReducers = createReducers(store.asyncReducers);
store.replaceReducer(nextReducers);
});
});
}
return store;
}
and also for making initial store defined :
function renderAppToStringAtLocation(url, { webpackDllNames = [], assets, lang }, callback) {
const memHistory = createMemoryHistory(url);
const store = createStore({}, memHistory);
syncHistoryWithStore(memHistory, store);
const routes = createRoutes(store);
const sagasDone = monitorSagas(store);
store.dispatch(changeLocale(lang));
match({ routes, location: url }, (error, redirectLocation, renderProps) => {
if (error) {
callback({ error });
} else if (redirectLocation) {
callback({ redirectLocation: redirectLocation.pathname + redirectLocation.search });
} else if (renderProps) {
renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames })
.then((html) => {
const notFound = is404(renderProps.routes);
callback({ html, notFound });
})
.catch((e) => callback({ error: e }));
} else {
callback({ error: new Error('Unknown error') });
}
});
}
and for filling the initial state I do some change:
async function fetches (hostname) {
const domain = hostname.replace('.myExample.com', '').replace('www.', '');
const options = {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/example.api.v1.0+json',
}
};
const uri ='https://api.example.com/x/' + domain + '/details';
const shopDetail = await fetch(uri, options);
return shopDetail.json();
}
function renderAppToStringAtLocation(hostname ,url, { webpackDllNames = [], assets, lang }, callback) {
const memHistory = createMemoryHistory(url);
console.log('url :', hostname);
fetches(hostname).then( data => {
const store = createStore(data, memHistory);
syncHistoryWithStore(memHistory, store);
const routes = createRoutes(store);
const sagasDone = monitorSagas(store);
store.dispatch(changeLocale(lang));
match({ routes, location: url }, (error, redirectLocation, renderProps) => {
if (error) {
callback({ error });
} else if (redirectLocation) {
callback({ redirectLocation: redirectLocation.pathname + redirectLocation.search });
} else if (renderProps) {
renderHtmlDocument({ store, renderProps, sagasDone, assets, webpackDllNames })
.then((html) => {
const notFound = is404(renderProps.routes);
callback({ html, notFound });
})
.catch((e) => callback({ error: e }));
} else {
callback({ error: new Error('Unknown error') });
}
});
});
and then in console I get this error:
Unexpected properties "code", "data" found in initialState argument
passed to createStore. Expected to find one of the known reducer
property names instead: "route", "global", "language". Unexpected
properties will be ignored.
how to fix it?
I thinks for initial state I need to call this (first API that need data for registration ) API on the server and save response data into store and return store to client
There are two different solutions, dependent on side, on which API call should be performed.
If it's just server-side call, HTTP response and subsequent SSR phase should be delayed, until fetch is done. It can be solved in express by wrapping into middleware function. Usually such schema is used when integrating with external authorization services (Auth0, Passport, etc), but it's better to wrap authorization information into JWT and not into INITIAL_STATE.
If API call can be done from client side, just use redux-saga. It can spawn dedicated process, which will catch all redux actions before API call is done, and then play them respectively. In this case initialState object should contain structure-like fields without data, which will be filled later after API call.
I'm using react.js and express.js and getting 404 error on my fetch request.
I'm simply trying to have my routes return testing express.js...
[app.js]
'use strict';
const NODE_ENV = process.env.NODE_ENV;
const PORT = process.env.PORT;
const next = require('next');
const express = require('express');
const api = require('./api');
const client = next({ dev: NODE_ENV === 'development' });
const clientHandler = client.getRequestHandler();
const app = express();
client.prepare().then(() => {
app.use('/api', api);
app.get('*', (req, res) => clientHandler(req, res));
});
const listener = app.listen(PORT, err => {
if (err) throw err;
console.log('listening on port: %d', listener.address().port); //eslint-disable-line
});
[/api/index.js]
'use strict';
const express = require('express');
const app = express();
app.get('/api/test', function (req, res) {
res.send('testing express.js...');
});
module.exports = app;
[Body.js]
import React from 'react';
export default class Body extends React.Component {
constructor(props) {
super(props);
this.fetchContacts = this.fetchContacts.bind(this);
}
componentDidMount() {
this.fetchContacts();
}
async fetchContacts() {
const res = await fetch('/api/test');
const contacts = await res.json();
log(contacts);
this.setState({ contacts });
}
render() {
return <div>hello world!</div>;
}
}
Question: Why am I getting a 404 error?
To make your /api/test route work properly, you need to change this:
app.get('/api/test', function (req, res) {
res.send('testing express.js...');
});
to this:
app.get('/test', function (req, res) {
res.send('testing express.js...');
});
Your router is already looking at /api so when you then put a route on the router for /api/test, you were actually making a route for /api/api/test. To fix it, make the above change.
Also, your index.js file should not be using an app object. It should be using an express.Router() object, though an app object is also a router so it might kind of work, but it's not the way it should be done.
That is not the way to load a react JS file on nodejs, follow this basic (React + NodeJS) guide:
https://blog.yld.io/2015/06/10/getting-started-with-react-and-node-js/#.Wd7zSBiWbyg
Or use "create-react-app" :
https://medium.com/#patriciolpezjuri/using-create-react-app-with-react-router-express-js-8fa658bf892d
While studying Reactjs Server Side Rendering, on a node Express web server, and Webpack middleware, I'm finding hard to understand why the root route "/" matching component is not passed into the html, while nested "/foobar" works fine (and you see the parents too).
If the webpack middleware's removed, the path "/" returns the match reactjs route.
Please find the source below, have in mind that there's a lot of testing and seeing and it's not quality code.
The Webpack development configuration file:
var path = require('path')
var webpack = require('webpack')
var HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
devtool: 'inline-source-map',
context: path.resolve(__dirname, 'src'),
entry: [
'react-hot-loader/patch',
'webpack/hot/dev-server',
'webpack-hot-middleware/client',
'babel-polyfill',
'./js/index.js'
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
'babel-loader'
]
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
use: [
'file-loader'
]
},
{
test: /\.(jpg|png|gif|svg)$/i,
use: [
'file-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: path.join(__dirname, '/src/index.html'),
filename: 'index.html'
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
// enable HMR globally
new webpack.HotModuleReplacementPlugin(),
// prints more readable module names in the browser console on HMR updates
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
}
The Reactjs Router related components.
The Rootjs:
import React from 'react'
import { Router } from 'react-router'
import { Provider } from 'react-redux'
import routes from './routes'
const Root = ({store, history}) => {
return (
<Provider store={store}>
<Router history={history}>
{ routes }
</Router>
</Provider>
)
}
export default Root
The Routes:
import React from 'react'
import { Route } from 'react-router'
import App from './containers/app'
import Foobar from './containers/foobar'
export default (
<Route path='/' component={App}>
<Route path='foobar' component={Foobar} />
</Route>
)
The Server.js:
import express from 'express'
import path from 'path'
import superagent from 'superagent'
import chalk from 'chalk'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { Provider } from 'react-redux'
import { match, RouterContext } from 'react-router'
import routes from './src/js/routes'
import configureStore from './src/js/store'
import App from './src/js/containers/app'
const app = express()
const router = express.Router()
const port = process.env.PORT ? process.env.PORT : 3000
var serverInstance = null
var dist = path.join(__dirname, ('dist' + (process.env.NODE_ENV ? '/' + process.env.NODE_ENV : 'staging')))
var config = null
var fs = require('fs')
var htmlTemplateString = ''
/**
* Environment settings
*/
if (['staging', 'production'].indexOf(process.env.NODE_ENV) > -1) {
console.log('break 1')
dist = path.resolve(__dirname, process.env.NODE_ENV)
config = require('../config')
htmlTemplateString = fs.readFileSync(dist + '/index.html', 'utf-8')
} else {
console.log('break 2')
config = require('./config')
htmlTemplateString = fs.readFileSync('./dist/production/index.html', 'utf-8')
}
/**
* Process error handling
*/
process.on('uncaughtException', (err) => {
throw err
})
process.on('SIGINT', () => {
serverInstance.close()
process.exit(0)
})
/**
* The Cross origin resource sharing rules
*/
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
res.setHeader('Access-Control-Allow-Credentials', true)
next()
})
/**
* Health check
*/
app.use('/healthcheck', (req, res) => {
res.json({
'env': {
'NODE_ENV': process.env.NODE_ENV
}
})
res.end()
})
router.use('/api/test', (req, res) => {
superagent
.get('https://jsonip.com/')
.end((err, response) => {
if (err) {
console.log('api test err', err)
}
res.send(response.body)
})
})
// HMR only in development
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'staging') {
console.log('Development environment: Starting webPack middleware...')
const webpack = require('webpack')
const webpackHotMiddleware = require('webpack-hot-middleware')
const webpackDevConfig = require('./webpack.dev.config')
const compiler = webpack(webpackDevConfig)
var webpackDevMiddleware = require('webpack-dev-middleware')
var devMiddleware = webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackDevConfig.output.publicPath,
stats: {
colors: true
}
})
router.use(devMiddleware)
router.use(webpackHotMiddleware(compiler, {
log: console.log
}))
// Production needs physical files! (built via separate process)
router.use('/assets', express.static(dist))
// any other is mapped here
router.get('*', (req, res, next) => {
console.log('req.url: ', req.url)
match({ routes, location: req.url }, (err, redirect, props) => {
if (props) {
const preloadedState = {'foobar': 1}
// Create a new Redux store instance
const store = configureStore(preloadedState)
// Render the component to a string
const myAppHtml = renderToString(<RouterContext {...props} />)
// Grab the initial state from our Redux store
const finalState = store.getState()
// Send the rendered page back to the client
let html = htmlTemplateString.replace('<div id="app">', '<div id="app">' + myAppHtml)
// Paste the state into the html
const preloadedStateScript = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(finalState).replace(/</g, '\\x3c')}</script>`
html = html.replace('</head>', preloadedStateScript)
res.send(html)
} else {
res.status(404).send('Not found')
}
})
})
}
app.disable('x-powered-by')
app.use('/', router)
serverInstance = app.listen(port, (error) => {
if (error) {
console.log(error) // eslint-disable-line no-console
}
console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!'))
})
I initially thought that had something to do with where the webpack middleware's set, but there's a typo somewhere in the initial code, so had to refactor and it works server side now:
import express from 'express'
import path from 'path'
import superagent from 'superagent'
import chalk from 'chalk'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from './src/js/routes'
import configureStore from './src/js/store'
const app = express()
const port = process.env.PORT ? process.env.PORT : 3000
var serverInstance = null
var dist = path.join(__dirname, ('dist' + (process.env.NODE_ENV ? '/' + process.env.NODE_ENV : 'staging')))
var config = null
var fs = require('fs')
var htmlTemplateString = ''
const webpack = require('webpack')
const webpackHotMiddleware = require('webpack-hot-middleware')
const webpackDevConfig = require('./webpack.dev.config')
const compiler = webpack(require('./webpack.dev.config'))
var webpackDevMiddleware = require('webpack-dev-middleware')
config = require('./config')
htmlTemplateString = fs.readFileSync('./dist/production/index.html', 'utf-8')
/**
* Process error handling
*/
process.on('uncaughtException', (err) => {
throw err
})
process.on('SIGINT', () => {
serverInstance.close()
process.exit(0)
})
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackDevConfig.output.publicPath,
stats: {
colors: true
}
}))
app.use(webpackHotMiddleware(compiler, {
log: console.log
}))
/**
* The Cross origin resource sharing rules
*/
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
res.setHeader('Access-Control-Allow-Credentials', true)
next()
})
/**
* Health check
*/
app.use('/healthcheck', (req, res) => {
res.json({
'env': {
'NODE_ENV': process.env.NODE_ENV
}
})
res.end()
})
app.use('/api/test', (req, res) => {
superagent
.get('https://jsonip.com/')
.end((err, response) => {
if (err) {
console.log('api test err', err)
}
res.send(response.body)
})
})
app.use('/assets', express.static(dist))
// any other is mapped here
app.get('*', (req, res, next) => {
console.log('req.url', req.url)
match({ routes: routes, location: req.url }, (error, redirectLocation, props) => {
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (props) {
const preloadedState = {'foobar': 1}
// Create a new Redux store instance
const store = configureStore(preloadedState)
// Render the component to a string
const myAppHtml = renderToString(<RouterContext {...props} />)
// Grab the initial state from our Redux store
const finalState = store.getState()
// Send the rendered page back to the client
let html = htmlTemplateString.replace('<div id="app">', '<div id="app">' + myAppHtml)
// Paste the state into the html
const preloadedStateScript = `<script>window.__PRELOADED_STATE__ = ${JSON.stringify(finalState).replace(/</g, '\\x3c')}</script>`
html = html.replace('</head>', preloadedStateScript)
res.status(200).send(html)
} else {
res.status(404).send('Not found')
}
})
})
serverInstance = app.listen(port, (error) => {
if (error) {
console.log(error) // eslint-disable-line no-console
}
console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!'))
})