A lot of people see isomorphic rendering as a holy grail given it gives advantages a SPAs lost compared to earlier solutions. The fact that you can provide initial markup has SEO and performance implications. It is a hard problem, though, as your tooling needs to support the approach well.
In this interview we'll discuss a solution known as isomorphic-webpack by Gajus Kuizinas.
My name is Gajus Kuizinas. I am a consultant software engineer, living in London. I advise companies on the subject of the software architecture, database design and DevOps. When not at work, I am spending a considerable amount of time contributing to the open-source.
It is a program that makes an application written for browser consumption render on the server-side.
Rendering an application server-side enables advanced page generation techniques (e.g. using ESI), critical rendering path optimization and it maintains the idea on which the internet was built – that pages are documents, and if you ask server for a document by URL, you get back the text of the document rather than a program that generates the document.
For the most part, isomorphic-webpack is an abstraction of the webpack API. isomorphic-webpack configures a new webpack compiler and sets it into a watch
mode. Since it is simply another webpack instance, the configuration can be shared between client-side and server-side.
However, isomorphic-webpack clones the configuration and makes a few amendments, e.g. for performance purposes, the isomorphic-webpack's webpack compiler instance is using in-memory file system, a DllPlugin
plugin is added, and by default all dependencies are externalized.
The DllPlugin
changes the behaviour of the webpack compiler. When using the DllPlugin
plugin, the resulting script bundle does not self-invoke. Instead, the resulting script bundle exports a function which can be used to require
modules.
webpack compiles the bundle and writes the resulting files to the in-memory file system. isomorphic-webpack then evaluates the resulting bundle using Node.js vm module. The V8 virtual machine is created using a context that emulates the browser environment.
To be specific, window
and document
objects are created using jsdom. These objects are required by various loaders and dependencies, e.g. style-loader requires access to document
, history requires access to window.history
, etc.
To access the code, isomorphic-webpack overrides Node.js module resolution logic using override-require. require()
queries that match an entry in the manifest file (generated by the DllPlugin
) are mapped to an entry in the resulting bundle, other requests are delegated to the native Node.js module loading system.
This is how when you require('./app')
we are able to render the entire application as if that code was executed in the browser.
Suppose that this is your existing webpack 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));
app.get('/', () => {
return `
<!doctype html>
<html>
<head></head>
<body>
<div id='app'></div>
<script src='/static/app.js'></script>
</body>
</html>
`;
});
app.listen(8000);
It doesn't matter whats in the webpack.configuration.js, because the same configuration is used for client- and server-side.
Note: webpack-dev-middleware is not a dependency of isomorphic-webpack. It is used here to deliver the client-side bundle.
To make the above webpack server render the application server-side, all you need is to create isomorphic-webpack compiler and render the application using react-dom/server
:
import express from 'express';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import {
createIsomorphicWebpack
} from 'isomorphic-webpack';
import {
renderToString
} from 'react-dom/server';
import webpackConfiguration from '../webpack.configuration';
const compiler = webpack(webpackConfiguration);
const app = express();
app.use(webpackDevMiddleware(compiler));
createIsomorphicWebpack(webpackConfiguration);
app.get('/', () => {
return `
<!doctype html>
<html>
<head></head>
<body>
<div id='app'>
${renderToString(require('../app').default)}
</div>
<script src='/static/app.js'></script>
</body>
</html>
`;
});
app.listen(8000);
I have added a comparison table to the main repository. The key difference is that isomorphic-webpack
does not require a separate configuration for client- and server-side code and all webpack
loaders and dependencies work out of the box.
The latter is an important condition: a true universal code base requires that the same code works across different platforms. If you start adding platform specific conditions to make the code work, it is no longer a universal code base.
It is the responsibility of the program running the code to enable isomorphism. In that sense, isomorphic-webpack is the only tool that enables re-use of the code base between client- and server-side without making modifications to the application code base.
I am consulting a company that utilise Edge Side Includes (ESI) to construct the page content. This company is migrating from a mixed bag of programming languages to JavaScript for client- and server-side development.
This was a unique challenge: I was working as part of a larger frontend team; the code will need to be maintained by the frontend team. Therefore, my focus has been to use frameworks known to frontend teams and avoid backend specific frameworks as much as possible. I have proceeded to develop the application using React and webpack.
I have had the application up and running client-side, but there was one problem – none of the existing isomorphic-rendering solutions worked out of the box with the code base. The existing solutions required obscure configuration, ran multiple Node processes (making it a pain to containerise the application), and didn’t work with all of the webpack loaders the project was using.
I have started with a project specific implementation. I have learned about the webpack API and progressed to develop a re-usable abstraction.
My priority is for isomorphic-webpack to pass the rule of three. I have approached a number of developers in senior positions at various companies asking for feedback and offering my help working with isomorphic-webpack. I am going to develop the project by responding to the real-life use cases arising from the wider project adoption.
I am seeing an increasing reliance on open-source projects and focus on a better developer experience (DX). JavaScript in particular has a thriving open-source module ecosystem with an avg. growth of 423 packages per day. This is an astonishing number, far ahead of the closest alternatives (2nd, Maven Central (Java) – 172/ day, 3rd Packagist (PHP) – 109/day). (Statistics from http://www.modulecounts.com/.)
Behind these numbers there hides the good and the bad: the good is that there are more developers that are willing to share their code with the community. The bad is that there are a lot of packages that perform the same function. For example, there are over 100 packages that implement an "event emitter" pattern! In addition, you can find many low quality packages, as well as a lot of abandoned packages.
I hope to see more collaboration in the open-source world, more community driven development and maintenance of the existing projects.
Furthermore, JavaScript has a growing number of projects focused on the developer experience, e.g. webpack enables Hot Module Replacement (HMR), react-storybook sandboxes UI component development and redux-devtools enables inspection of Redux application state, just to name a few.
This is super important – after all, good DX is what "recruits" the new talents, it is what makes companies to adopt the language. However, I'd like to see a wider adoption of strict code styles, usage of immutable constructs and strict-type checking.
Avoid learning theory. Focus on building small programs. Learn as you iterate. Enjoy.
I am seeing so many developers who are learning theory by heart without applying it. Its just not right. You need to enjoy the learning process. A simple way to enjoy whatever you are doing is to observe the results. A simple way to observe the results is to start small ("Hello, World!"), iterate adding new features and researching theory as you proceed. Make your code open-source, promote it, seek feedback. I do this every day.
When I just started learning JavaScript, I have created a profile on a popular freelancing website. I have sought for small projects. Specifically, I was looking for: small scale, not time-sensitive projects with an existing technical leader.
I would contact the author and introduce myself as a "good overall developer with experience in XYZ, but no experience in JavaScript". I offered to deliver their project at a friction of the price at an agreed time frame. This was never about money. This was about me creating a product that someone will use, making connections, getting feedback from the professionals and learning from the real-life requirements.
Evgeny Poberezkin. He is doing revolutionary work in the JSON domain. His package Ajv is becoming an industry-wide standard for validating JSON schemes. Furthermore, I know that he is working on new and exciting things.
Thanks for the interview Gajus! It's more often than not that a package of yours finds its way to one of my projects. And isomorphic-webpack is definitely looking like a fresh approach.
Check out isomorphic-webpack on GitHub or read Gajus' step-by-step article on how to turn an existing application into an isomorphic one to learn more.