Skip to main content
  1. All Posts/

webpack-isomorphic-tools

Tools JavaScript

webpack-isomorphic-tools





THIS PACKAGE IS DEPRECATED. LOOK ELSEWHERE.
webpack-isomorphic-tools is a small helper module providing very basic support for isomorphic (universal) rendering when using Webpack. It was created a long time ago when Webpack was v1 and the whole movement was just starting. Therefore webpack-isomorphic-tools is a hacky solution. It allowed many projects to set up basic isomorphic (universal) rendering in the early days but is now considered deprecated and new projects shouldn’t use it. This library can still be found in legacy projects. For new projects use either universal-webpack or all-in-one frameworks like Next.js.

Topics

What it does

Suppose you have an application which is built using Webpack. It works in the web browser.
Should it be “isomorphic” (“universal”)? It’s better if it is. One reason is that search engines will be able to index your page. The other reason is that we live in a realtime mobile age which declared war on network latency, and so it’s always better to fetch an already rendered content than to first fetch the application code and only then fetch the content to render the page. Every time you release a client-side only website to the internet someone writes a frustrated blog post.
So, it’s obvious then that web applications should be “isomorphic” (“universal”), i.e. be able to render both on the client and the server, depending on circumstances. And it is perfectly possible nowadays since javascript runs everywhere: both in web browsers and on servers.
Ok, then one can just go ahead and run the web application in Node.js and its done. But, there’s one gotcha: a Webpack application will usually crash when tried to be run in Node.js straight ahead (you’ll get a lot of SyntaxErrors with Unexpected tokens).
The reason is that Webpack introduces its own layer above the standard javascript. This extra layer handles all require() calls magically resolving them to whatever it is configured to. For example, Webpack is perfectly fine with the code require()ing CSS styles or SVG images.
Bare Node.js doesn’t come with such trickery up its sleeve. Maybe it can be somehow enhanced to be able to do such things? Turned out that it can, and that’s what webpack-isomorphic-tools do: they inject that require() magic layer above the standard javascript in Node.js.
Still it’s a hacky solution, and a better way would be to compile server-side code with Webpack the same way it already compiles the client-side code. This is achieved via target: “node” configuration option, and that’s what universal-webpack library does. However, webpack-isomorphic-tools happened to be a bit simpler to set up, so they made their way into many now-legacy projects, so some people still use this library. It’s not being maintained anymore though, and in case of any issues people should just migrate to universal-webpack or something similar.
webpack-isomorphic-tools mimics (to a certain extent) Webpack’s require() magic when running application code on a Node.js server without Webpack. It basically fixes all those require()s of assets and makes them work instead of throwing SyntaxErrors. It doesn’t provide all the capabilities of Webpack (for example, plugins won’t work), but for the basic stuff, it works.

A simple example

For example, consider images. Images are require()d in React components and then used like this:

// alternatively one can use `import`, 
// but with `import`s hot reloading won't work
// import imagePath from '../image.png'

// Just `src` the image inside the `render()` method
class Photo extends React.Component
{
  render()
  {
    // When Webpack url-loader finds this `require()` call 
    // it will copy `image.png` to the build folder 
    // and name it something like `9059f094ddb49c2b0fa6a254a6ebf2ad.png`, 
    // because Webpack is set up to use the `[hash]` file naming feature
    // which makes browser asset caching work correctly.
    return <img src={ require('../image.png') }/>
  }
}

It works on the client-side because Webpack intelligently replaces all the require() calls with a bit of magic.
But it wouldn’t work on the server-side because Node.js only knows how to require() javascript modules. It would just throw a SyntaxError.
To solve this issue one can use webpack-isomorphic-tools. With the help of webpack-isomorphic-tools in this particular case the require() call will return the real path to the image on the disk. It would be something like ../../build/9059f094ddb49c2b0fa6a254a6ebf2ad.png. How did webpack-isomorphic-tools figure out this weird real file path? It’s just a bit of magic.
webpack-isomorphic-tools is extensible, and finding the real paths for assets is the simplest example of what it can do inside require() calls. Using custom configuration one can make require() calls (on the server) return anything (not just a String; it may be a JSON object, for example).
For example, if one is using Webpack css-loader modules feature (also referred to as “local styles”) one can make require(*.css) calls return JSON objects with generated CSS class names maps like they do in este and react-redux-universal-hot-example.

Installation

webpack-isomorphic-tools are required both for development and production

$ npm install webpack-isomorphic-tools --save

Usage

First you add webpack-isomorphic-tools plugin to your Webpack configuration.

webpack.config.js

var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin')

var webpackIsomorphicToolsPlugin = 
  // webpack-isomorphic-tools settings reside in a separate .js file 
  // (because they will be used in the web server code too).
  new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools-configuration'))
  // also enter development mode since it's a development webpack configuration
  // (see below for explanation)
  .development()

// usual Webpack configuration
module.exports =
{
  context: '(required) your project path here',

  module:
  {
    loaders:
    [
      ...,
      {
        test: webpackIsomorphicToolsPlugin.regularExpression('images'),
        loader: 'url-loader?limit=10240', // any image below or equal to 10K will be converted to inline base64 instead
      }
    ]
  },

  plugins:
  [
    ...,

    webpackIsomorphicToolsPlugin
  ]

  ...
}

What does .development() method do? It enables development mode. In short, when in development mode, it disables asset caching (and enables asset hot reload), and optionally runs its own “dev server” utility (see port configuration setting). Call it in development webpack build configuration, and, conversely, don’t call it in production webpack build configuration.
For each asset type managed by webpack-isomorphic-tools there should be a corresponding loader in your Webpack configuration. For this reason webpack-isomorphic-tools/plugin provides a .regularExpression(assetType) method. The assetType parameter is taken from your webpack-isomorphic-tools configuration:

webpack-isomorphic-tools-configuration.js

import WebpackIsomorphicToolsPlugin from 'webpack-isomorphic-tools/plugin'

export default
{
  assets:
  {
    images:
    {
      extensions: ['png', 'jpg', 'gif', 'ico', 'svg']
    }
  }
}

That’s it for the client side. Next, the server side. You create your server side instance of webpack-isomorphic-tools in the very main server javascript file (and your web application code will reside in some server.js file which is require()d in the bottom)

main.js

var WebpackIsomorphicTools = require('webpack-isomorphic-tools')

// this must be equal to your Webpack configuration "context" parameter
var projectBasePath = require('path').resolve(__dirname, '..')

// this global variable will be used later in express middleware
global.webpackIsomorphicTools = new WebpackIsomorphicTools(require('./webpack-isomorphic-tools-configuration'))
// initializes a server-side instance of webpack-isomorphic-tools
// (the first parameter is the base path for your project
//  and is equal to the "context" parameter of you Webpack configuration)
// (if you prefer Promises over callbacks 
//  you can omit the callback parameter
//  and then it will return a Promise instead)
.server(projectBasePath, function()
{
  // webpack-isomorphic-tools is all set now.
  // here goes all your web application code:
  // (it must reside in a separate *.js file 
  //  in order for the whole thing to work)
  require('./server')
})

Then you, for example, create an express middleware to render your pages on the server

import React from 'react'

// html page markup
import Html from './html'

// will be used in express_application.use(...)
export function pageRenderingMiddleware(request, response)
{
  // clear require() cache if in development mode
  // (makes asset hot reloading work)
  if (process.env.NODE_ENV !== 'production')
  {
    webpackIsomorphicTools.refresh()
  }

  // for react-router example of determining current page by URL take a look at this:
  // https://github.com/catamphetamine/webapp/blob/master/code/server/webpage%20rendering.js
  const pageComponent = [determine your page component here using request.path]

  // for a Redux Flux store implementation you can see the same example:
  // https://github.com/catamphetamine/webapp/blob/master/code/server/webpage%20rendering.js
  const fluxStore = [initialize and populate your flux store depending on the page being shown]

  // render the page to string and send it to the browser as text/html
  response.send('<!doctype html>n' +
        React.renderToString(<Html assets={webpackIsomorphicTools.assets()} component={pageComponent} store={fluxStore}/>))
}

And finally you use the assets inside the Html component’s render() method

import React, {Component, PropTypes} from...