“Hello, World!”

Lets start with the “Hello, World” and build on that example.

Our example is a React application. It uses react-dom to render ReactElement and append the resulting DOM to the #app element.

/src/app/index.js

import React from 'react';

import ReactDOM from 'react-dom'; const app = <div>Hello, World!</div>; ReactDOM.render(app, document.getElementById('app'));

webpack is configured to use babel-loader to load the JavaScript files.

/src/webpack.config.js

import path from 'path'; export default {

context: __dirname,

entry: {

app: [

path.resolve(__dirname, './app')

]

},

module: {

loaders: [

{

include: path.resolve(__dirname, './app'),

loader: 'babel-loader',

test: /\.js$/

}

]

},

output: {

filename: '[name].js',

path: path.resolve(__dirname, './dist')

}

};

A server-side script is using webpack to compile the application and express to server the contents.

/src/bin/server.js

Note: webpack-dev-middleware is not a dependency of isomorphic-webpack . Here it is used only to serve client-side application.

import express from 'express';

import webpack from 'webpack';

import webpackDevMiddleware from 'webpack-dev-middleware';

import webpackConfiguration from '../webpack.configuration'; const compiler = webpack(webpackConfiguration); const app = express(); app.use(webpackDevMiddleware(compiler, {

noInfo: false,

publicPath: '/static',

quiet: false,

stats: 'minimal'

})); app.get('/', (req, res) => {

res.send(`

<!doctype html>

<html>

<head></head>

<body>

<div id='app'></div> <script src='/static/app.js'></script>

</body>

</html>

`);

}); app.listen(8000);

This isn’t an isomorphic application. Making an HTTP request simply responds with the string hard-coded in the server-side script.

An isomorphic application would evaluate the React application code and respond with the rendered application.

If you want to just checkout the code, use the following commands:



$ cd isomorphic-webpack-demo

$ git reset --hard f66783c89040c0fc19a19df961cbb2633f27348d

$ npm install

$ npm start $ git clone git@github.com :gajus/isomorphic-webpack-demo.git$ cd isomorphic-webpack-demo$ git reset --hard f66783c89040c0fc19a19df961cbb2633f27348d$ npm install$ npm start

Isomorphic “Hello, World!”

What does it take to make the above application isomorphic?

The following changes need to be made to our code base:

Install isomorphic-webpack Setup isomorphic-webpack using the webpack configuration Export the application as a module. Use react-dom/server renderToString to render the application.

Here is how that changes our example application:

/src/app/index.js needs to export the application:

import React from 'react';

import ReactDOM from 'react-dom'; const app = <div>Hello, World!</div>; if (typeof ISOMORPHIC_WEBPACK === 'undefined') {

ReactDOM.render(app, document.getElementById('app'));

} export default app;

ISOMORPHIC_WEBPACK is a constant used to differentiate between Node.js and browser environment. Presence of the constant indicates that it is a Node.js environment.

The server-side script needs to initialise the createIsomorphicWebpack compiler and use react-dom/server renderToString to render the contents of the application:

import express from 'express';

import webpack from 'webpack';

import webpackDevMiddleware from 'webpack-dev-middleware';

import {

renderToString

} from 'react-dom/server';

import {

createIsomorphicWebpack

} from 'isomorphic-webpack';

import webpackConfiguration from '../webpack.configuration'; const compiler = webpack(webpackConfiguration); createIsomorphicWebpack(webpackConfiguration); const app = express(); app.use(webpackDevMiddleware(compiler, {

noInfo: false,

publicPath: '/static',

quiet: false,

stats: 'minimal'

})); const renderFullPage = (body) => {

return `

<!doctype html>

<html>

<head></head>

<body>

<div id='app'>${body}</div> <script src='/static/app.js'></script>

</body>

</html>

`;

}; app.get('/', (req, res) => {

const appBody = renderToString(require('../app').default); res

.send(renderFullPage(appBody));

}); app.listen(8000);

createIsomorphicWebpack overrides Node.js module resolution system, i.e. all require() calls that refer to resources that are part of the webpack bundle will be handled by isomorphic-webpack .

This made our application isomorphic. Making an HTTP request responds with the rendered React application:

Free isomorphism.

If you want to just checkout the code, use the following commands:

$ git reset --hard 4fb6c11d488405a7c9b7f5a7cda4abec2396be00

$ npm install

$ npm start

Using Webpack loaders

Loaders allow you to preprocess files as you require() or “load” them. [..] Loaders can transform files from a different language like, CoffeeScript to JavaScript, or inline images as data URLs. Loaders even allow you to do things like require() css files right in your JavaScript!

– https://webpack.github.io/docs/loaders.html

For the purpose of this demonstration, I am going to show how to use style-loader with css-loader .

First, we need to update our webpack configuration.

/src/webpack.config.js

import path from 'path'; export default {

context: __dirname,

entry: {

app: [

path.resolve(__dirname, './app')

]

},

module: {

loaders: [

{

include: path.resolve(__dirname, './app'),

loader: 'babel-loader',

test: /\.js$/

},

{

loaders: [

{

loader: 'style-loader',

query: {

sourceMap: 1

}

},

{

loader: 'css-loader',

query: {

importLoaders: 1,

localIdentName: '[path]___[name]___[local]',

modules: 1

}

}

],

test: /\.css$/

}

]

},

output: {

filename: '[name].js',

path: path.resolve(__dirname, './dist')

}

};

Note: There is nothing isomorphic-webpack specific in the above configuration. I am including the configuration only for completeness of the example.

Next, create a style sheet.

/src/app/style.css

.greetings {

color: #f00;

}

Update the application to use the style sheet:

import React from 'react';

import ReactDOM from 'react-dom';

import style from './style.css'; const app = <div className={style.greetings}>Hello, World!</div>; if (typeof ISOMORPHIC_WEBPACK === 'undefined') {

ReactDOM.render(app, document.getElementById('app'));

} export default app;

Finally, restart the application and make an HTTP request.

As you see, the server responds with the evaluated value of the class attribute, app-___style___greetings .

If it feels like you haven’t learned anything new in this section, then thats because there isn’t anything isomorphic-webpack specific. There are no isomorphic-webpack specific changes to the configuration or the application. That is a truly universal code base.

You just won all the loaders!

If you want to just checkout the code, use the following commands:

$ git reset --hard 90d6e2708719a1727f2f8afd06f8b47432707b88

$ npm install

$ npm start

Routes

This section describes an experimental implementation. I have not tested this in production. Proceed with caution. It is pretty cool, though.

react-router documentation already includes a section about server rendering. You could follow that path… (but it requires to write server-side specific code) or you could trick react-router into thinking that the script is running in a browser and avoid making any changes to your application.

By default, the createIsomorphicWebpack does not evaluate scripts in node_modules directory. This is done for performance reasons: few scripts depend on the browser environment. However, react-router (and history ) do depend on browser environment.

I am going to tell createIsomorphicWebpack to evaluate react-router (and history ) as if it is running in a browser. This is done using nodeExternalsWhitelist configuration.

createIsomorphicWebpack(webpackConfiguration, {

nodeExternalsWhitelist: [

/^react\-router/,

/^history/

]

});

Now react-router and history packages are included in the webpack bundle and will be executed using the faux browser environment.

However, we aren’t done yet. We need to tell what is the window URL when evaluating the code. Bundle code can be evaluated using evalCode function ( evalCode is a property of the createIsomorphicWebpack result), e.g.

const {

evalCode

} = createIsomorphicWebpack(webpackConfiguration, {

nodeExternalsWhitelist: [

/^react\-router/,

/^history/

]

}); app.get('/*', (req, res) => {

evalCode(req.protocol + '://' + req.get('host') + req.originalUrl);

const appBody = renderToString(require('../app').default);

res.send(renderFullPage(appBody));

});

Now, lets make some requests:

It takes a magician to know a magician.

If you want to just checkout the code, use the following commands:

$ git reset --hard 2959593fe217abada30d6ebe2c510e07a477c76b

$ npm install

$ npm start

Conclusion

There are already many articles that discuss the pros and cons of server-side rendering (e.g. You’re Missing the Point of Server-Side Rendered JavaScript Apps). In my specific case, I needed server-side rendering to enable Edge Side Includes (ESI). I have achieved this by first writing the client-side application and then using isomorphic-webpack to render the application server-side.

Evaluate the pros and cons of server-side rendering and if the pros outweigh the cons, then consider using isomorphic-webpack to make your application render server-side.

Where to go next?