webpack-isomorphic-tools
Transforms CSS-alike text into a React style JSON object
Last updated 4 years ago by kuchumovn .
MIT · Repository · Bugs · Original npm · Tarball · package.json
$ cnpm install webpack-isomorphic-tools 
SYNC missed versions from official npm registry.

webpack-isomorphic-tools

npm version npm downloads build status coverage

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 'react'
import serialize from 'serialize-javascript'

export default class Html extends Component
{
  static propTypes =
  {
    assets    : PropTypes.object,
    component : PropTypes.object,
    store     : PropTypes.object
  }

  // a sidenote for "advanced" users:
  // (you may skip this)
  //
  // this file is usually not included in your Webpack build
  // because this React component is only needed for server side React rendering.
  //
  // so, if this React component is not `require()`d from anywhere in your client code,
  // then Webpack won't ever get here 
  // which means Webpack won't detect and parse any of the `require()` calls here,
  // which in turn means that if you `require()` any unique assets here 
  // you should also `require()` those assets somewhere in your client code,
  // otherwise those assets won't be present in your Webpack bundle and won't be found.
  //
  render()
  {
    const { assets, component, store } = this.props

    // "import" will work here too 
    // but if you want hot reloading to work while developing your project
    // then you need to use require()
    // because import will only be executed a single time 
    // (when the application launches)
    // you can refer to the "Require() vs import" section for more explanation
    const picture = require('../assets/images/cat.jpg')

    // favicon
    const icon = require('../assets/images/icon/32x32.png')

    const html = 
    (
      <html lang="en-us">
        <head>
          <meta charSet="utf-8"/>
          <title>xHamster</title>

          {/* favicon */}
          <link rel="shortcut icon" href={icon} />

          {/* styles (will be present only in production with webpack extract text plugin) */}
          {Object.keys(assets.styles).map((style, i) =>
            <link href={assets.styles[style]} key={i} media="screen, projection"
                  rel="stylesheet" type="text/css"/>)}

          {/* resolves the initial style flash (flicker) on page load in development mode */}
          { Object.keys(assets.styles).length === 0 ? <style dangerouslySetInnerHTML={{__html: require('../assets/styles/main_style.css')}}/> : null }
        </head>

        <body>
          {/* image requiring demonstration */}
          <img src={picture}/>

          {/* rendered React page */}
          <div id="content" dangerouslySetInnerHTML={{__html: React.renderToString(component)}}/>

          {/* Flux store data will be reloaded into the store on the client */}
          <script dangerouslySetInnerHTML={{__html: `window._flux_store_data=${serialize(store.getState())};`}} />

          {/* javascripts */}
          {/* (usually one for each "entry" in webpack configuration) */}
          {/* (for more informations on "entries" see https://github.com/petehunt/webpack-howto/) */}
          {Object.keys(assets.javascript).map((script, i) =>
            <script src={assets.javascript[script]} key={i}/>
          )}
        </body>
      </html>
    )

    return html
  }
}

assets in the code above are simply the contents of webpack-assets.json which is created by webpack-isomorphic-tools in your project base folder. webpack-assets.json (in the simplest case) keeps track of the real paths to your assets, e.g.

{
  "javascript":
  {
    "main": "/assets/main-d8c29e9b2a4623f696e8.js"
  },

  "styles":
  {
    "main": "/assets/main-d8c29e9b2a4623f696e8.css"
  },

  "assets":
  {
    "./assets/images/cat.jpg": "http://localhost:3001/assets/9059f094ddb49c2b0fa6a254a6ebf2ad.jpg",
    
    "./assets/images/icon/32x32.png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffBhcWAg6gFw6bAAAB60lEQVRIx+3UTUjUQRzG8c+u/n2BDe3lIJtQSuYhsPTQG+TFYLulguStoA5dPHYogoKigoi8dIsOCd0iiC4JFYFQBAVZEUgklWVQqam4vu1uF111d1310qWe0/yemfnyzPyG4b8KllQl6jWqNuX3nFNun/0qjJpYGRB1TkyRWu0C76Q0uKhOkT1aDfqSP0uxTpetR1i9e2Iq3HVUCQKt7tuWP0GDmDOGkfJd3GEbhFwzg6T3alR5lg0Ip0fVPhhKV2+UqfNcMu28sjlXggVAXEQoXZVKmlC2aGXETH5Ary3q026zPg8dtGnOKXPIi/x3MCJwUtyUqBN2uarXTi1+Cql1yqibuTKElsCaHBFBn1v6sU67RoGkHl3GciVYDNiuWVSphDEJYaSkRBSbNqLHI7PZgML0qNIFrz3OwqZAuQ6BB8KqRL01nA3YbdCVRW3L1KxGTx1zQMI3p01nAkqN5NnOkBrXJZw1qlOlj5mAlTQuqluXcRGTSrOPsJJeajOQzphaOyDucy47vGrAMvqLgCLlS97HmgH17mgRzFWhbEAq43/M1EYF2p1XoVAgMW8vdKFfmx0+LbO9WJNut3W44Ze4r/MTC6cKHBczutDhJSrxwyWDAntt9cRANoCwqLKcgJApAyZXfV//mP4AWg969geZ6qgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMDYtMjNUMjI6MDI6MTQrMDI6MDBG88r0AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE1LTA2LTIzVDIyOjAyOjE0KzAyOjAwN65ySAAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAASUVORK5CYII="
  }
}

And that's it, now you can require() your assets "isomorphically" (both on client and server).

A working example

webpack-isomorphic-tools are featured in react-redux-universal-hot-example. There it is used to require() images and CSS styles (in the form of CSS modules).

Also you may look at this sample project. There it is used to require() images and CSS styles (without using CSS modules feature).

Some source code guidance for the aforementioned project:

Configuration

Available configuration parameters:

{
  // debug mode.
  // when set to true, lets you see debugging messages in the console.
  //
  debug: true, // is false by default

  // (optional)
  // (recommended)
  //
  // when `port` is set, then this `port` is used
  // to run an HTTP server serving Webpack assets.
  // (`express` npm package must be installed in order for this to work)
  //
  // this way, in development mode, `webpack-assets.json` won't ever
  // be written to disk and instead will always reside in memory
  // and be served from memory (just as `webpack-dev-server` does).
  //
  // this `port` setting will take effect only in development mode.
  //
  // port: 8888, // is false by default

  // verbosity.
  //
  // when set to 'no webpack stats',
  // outputs no Webpack stats to the console in development mode.
  // this also means no Webpack errors or warnings will be output to the console.
  //
  // when set to 'webpack stats for each build',
  // outputs Webpack stats to the console 
  // in development mode on each incremental build.
  // (i guess no one is gonna ever use this setting)
  //
  // when not set (default), outputs Webpack stats to the console 
  // in development mode for the first build only.
  //
  // verbosity: ..., // is `undefined` by default

  // enables support for `require.context()` and `require.ensure()` functions.
  // is turned off by default 
  // to skip unnecessary code instrumentation
  // because not everyone uses it.
  //
  // patch_require: true, // is false by default

  // By default it creates 'webpack-assets.json' file at 
  // webpackConfiguration.context (which is your project folder).
  // You can change the assets file path as you wish
  // (therefore changing both folder and filename).
  //
  // (relative to webpackConfiguration.context which is your project folder)
  //
  webpack_assets_file_path: 'webpack-assets.json',

  // By default, when running in debug mode, it creates 'webpack-stats.json' file at 
  // webpack_configuration.context (which is your project folder).
  // You can change the stats file path as you wish
  // (therefore changing both folder and filename).
  //
  // (relative to webpack_configuration.context which is your project folder)
  //
  webpack_stats_file_path: 'webpack-stats.json',

  // Makes `webpack-isomorphic-tools` aware of Webpack aliasing feature
  // (if you use it)
  // https://webpack.github.io/docs/resolving.html#aliasing
  //
  // The `alias` parameter corresponds to `resolve.alias` 
  // in your Webpack configuration.
  //
  alias: webpackConfiguration.resolve.alias, // is {} by default

  // if you're using Webpack's `resolve.modulesDirectories`
  // then you should also put them here.
  //
  // modulesDirectories: webpackConfiguration.resolve.modulesDirectories // is ['node_modules'] by default

  // here you can define all your asset types
  //
  assets:
  {
    // keys of this object will appear in:
    //  * webpack-assets.json
    //  * .assets() method call result
    //  * .regularExpression(key) method call
    //
    pngImages: 
    {
      // which file types belong to this asset type
      //
      extension: 'png', // or extensions: ['png', 'jpg', ...],

      // [optional]
      // 
      // here you are able to add some file paths 
      // for which the require() call will bypass webpack-isomorphic-tools
      // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
      // (also supports regular expressions, e.g. /^\.\/node_modules\/*/, 
      //  and functions(path) { return true / false })
      //
      // exclude: [],

      // [optional]
      // 
      // here you can specify manually the paths 
      // for which the require() call will be processed by webpack-isomorphic-tools
      // (relative to the project base folder, e.g. ./sources/server/kitten.jpg.js)
      // (also supports regular expressions, e.g. /^\.\/node_modules\/*/, 
      //  and functions(path) { return true / false }).
      // in case of `include` only included paths will be processed by webpack-isomorphic-tools.
      //
      // include: [],

      // [optional]
      // 
      // determines which webpack stats modules 
      // belong to this asset type
      //
      // arguments:
      //
      //  module             - a webpack stats module
      //
      //                       (to understand what a "module" is
      //                        read the "What's a "module"?" section of this readme)
      //
      //  regularExpression  - a regular expression 
      //                       composed of this asset type's extensions
      //                       e.g. /\.scss$/, /\.(ico|gif)$/
      //
      //  options            - various options
      //                       (development mode flag,
      //                        debug mode flag,
      //                        assets base url,
      //                        project base folder,
      //                        regular_expressions{} for each asset type (by name),
      //                        webpack stats json object)
      //
      //  log
      // 
      // returns: a Boolean
      //
      // by default is: "return regularExpression.test(module.name)"
      //
      // premade utility filters:
      //
      // WebpackIsomorphicToolsPlugin.styleLoaderFilter
      //  (for use with style-loader + css-loader)
      //
      filter: function(module, regularExpression, options, log)
      {
        return regularExpression.test(module.name)
      },

      // [optional]
      //
      // transforms a webpack stats module name 
      // to an asset path (usually is the same thing)
      //
      // arguments:
      //
      //  module  - a webpack stats module
      //
      //            (to understand what a "module" is
      //             read the "What's a "module"?" section of this readme)
      //
      //  options - various options
      //            (development mode flag,
      //             debug mode flag,
      //             assets base url,
      //             project base folder,
      //             regular_expressions{} for each asset type (by name),
      //             webpack stats json object)
      //
      //  log
      // 
      // returns: a String
      //
      // by default is: "return module.name"
      //
      // premade utility path extractors:
      //
      // WebpackIsomorphicToolsPlugin.styleLoaderPathExtractor
      //  (for use with style-loader + css-loader)
      //
      path: function(module, options, log)
      {
        return module.name
      },

      // [optional]
      // 
      // parses a webpack stats module object
      // for an asset of this asset type
      // to whatever you need to get 
      // when you require() these assets 
      // in your code later on.
      //
      // this is what you'll see as the asset value in webpack-assets.json: 
      // { ..., path(): compile(parser()), ... }
      //
      // can be a CommonJS module source code:
      // module.exports = ...what you export here is 
      //                     what you get when you require() this asset...
      //
      // if the returned value is not a CommonJS module source code
      // (it may be a string, a JSON object, whatever) 
      // then it will be transformed into a CommonJS module source code.
      //
      // in other words: 
      //
      // // making of webpack-assets.json
      // for each type of configuration.assets
      //   modules.filter(type.filter).for_each (module)
      //     assets[type.path()] = compile(type.parser(module))
      //
      // // requiring assets in your code
      // require(path) = (path) => return assets[path]
      //
      // arguments:
      //
      //  module  - a webpack stats module
      //
      //            (to understand what a "module" is
      //             read the "What's a "module"?" section of this readme)
      //
      //  options - various options
      //            (development mode flag,
      //             debug mode flag,
      //             assets base url,
      //             project base folder,
      //             regular_expressions{} for each asset type (by name),
      //             webpack stats json object)
      //
      //  log
      // 
      // returns: whatever (could be a filename, could be a JSON object, etc)
      //
      // by default is: "return module.source"
      //
      // premade utility parsers:
      //
      // WebpackIsomorphicToolsPlugin.urlLoaderParser
      //  (for use with url-loader or file-loader)
      //  require() will return file URL
      //  (is equal to the default parser, i.e. no parser)
      //
      // WebpackIsomorphicToolsPlugin.cssLoaderParser
      //  (for use with css-loader when not using "modules" feature)
      //  require() will return CSS style text
      //
      // WebpackIsomorphicToolsPlugin.cssModulesLoaderParser
      //  (for use with css-loader when using "modules" feature)
      //  require() will return a JSON object map of style class names
      //  which will also have a `_style` key containing CSS style text
      //
      parser: function(module, options, log)
      {
        log.info('# module name', module.name)
        log.info('# module source', module.source)
        log.info('# debug mode', options.debug)
        log.info('# development mode', options.development)
        log.info('# webpack version', options.webpackVersion)
        log.debug('debugging')
        log.warning('warning')
        log.error('error')
      }
    },
    ...
  },
  ...]
}

Configuration examples

url-loader / file-loader (images, fonts, etc)

url-loader and file-loader are supported with no additional configuration

{
  assets:
  {
    images:
    {
      extensions: ['png', 'jpg']
    },

    fonts:
    {
      extensions: ['woff', 'ttf']
    }
  }
}

style-loader (standard CSS stylesheets)

If you aren't using "CSS modules" feature of Webpack, and if in your production Webpack config you use ExtractTextPlugin for CSS styles, then you can set it up like this

{
  assets:
  {
    styles:
    {
      extensions: ['less', 'scss'],

      // which `module`s to parse CSS from:
      filter: function(module, regularExpression, options, log)
      {
        if (options.development)
        {
          // In development mode there's Webpack "style-loader",
          // which outputs `module`s with `module.name == asset_path`,
          // but those `module`s do not contain CSS text.
          //
          // The `module`s containing CSS text are 
          // the ones loaded with Webpack "css-loader".
          // (which have kinda weird `module.name`)
          //
          // Therefore using a non-default `filter` function here.
          //
          return WebpackIsomorphicToolsPlugin.styleLoaderFilter(module, regularExpression, options, log)
        }

        // In production mode there will be no CSS text at all
        // because all styles will be extracted by Webpack Extract Text Plugin
        // into a .css file (as per Webpack configuration).
        //
        // Therefore in production mode `filter` function always returns non-`true`.
      },

      // How to correctly transform kinda weird `module.name`
      // of the `module` created by Webpack "css-loader" 
      // into the correct asset path:
      path: WebpackIsomorphicToolsPlugin.styleLoaderPathExtractor,

      // How to extract these Webpack `module`s' javascript `source` code.
      // basically takes `module.source` and modifies `module.exports` a little.
      parser: WebpackIsomorphicToolsPlugin.cssLoaderParser
    }
  }
}

style-loader (CSS stylesheets with "CSS modules" feature)

If you are using "CSS modules" feature of Webpack, and if in your production Webpack config you use ExtractTextPlugin for CSS styles, then you can set it up like this

{
  assets:
  {
    styleModules:
    {
      extensions: ['less', 'scss'],

      // which `module`s to parse CSS style class name maps from:
      filter: function(module, regex, options, log)
      {
        if (options.development)
        {
          // In development mode there's Webpack "style-loader",
          // which outputs `module`s with `module.name == asset_path`,
          // but those `module`s do not contain CSS text.
          //
          // The `module`s containing CSS text are 
          // the ones loaded with Webpack "css-loader".
          // (which have kinda weird `module.name`)
          //
          // Therefore using a non-default `filter` function here.
          //
          return WebpackIsomorphicToolsPlugin.styleLoaderFilter(module, regex, options, log)
        }

        // In production mode there's no Webpack "style-loader",
        // so `module.name`s of the `module`s created by Webpack "css-loader"
        // (those which contain CSS text)
        // will be simply equal to the correct asset path
        return regex.test(module.name)
      },

      // How to correctly transform `module.name`s
      // into correct asset paths
      path: function(module, options, log)
      {
        if (options.development)
        {
          // In development mode there's Webpack "style-loader",
          // so `module.name`s of the `module`s created by Webpack "css-loader"
          // (those picked by the `filter` function above)
          // will be kinda weird, and this path extractor extracts 
          // the correct asset paths from these kinda weird `module.name`s
          return WebpackIsomorphicToolsPlugin.styleLoaderPathExtractor(module, options, log);
        }

        // in production mode there's no Webpack "style-loader",
        // so `module.name`s will be equal to correct asset paths
        return module.name
      },

      // How to extract these Webpack `module`s' javascript `source` code.
      // Basically takes `module.source` and modifies its `module.exports` a little.
      parser: function(module, options, log)
      {
        if (options.development)
        {
          // In development mode it adds an extra `_style` entry
          // to the CSS style class name map, containing the CSS text
          return WebpackIsomorphicToolsPlugin.cssModulesLoaderParser(module, options, log);
        }

        // In production mode there's Webpack Extract Text Plugin 
        // which extracts all CSS text away, so there's
        // only CSS style class name map left.
        return module.source
      }
    }
  }
}

svg-react-loader (CSS stylesheets with "CSS modules" feature)

{
  assets: {
    svg: {
      extension: 'svg',
      runtime: true
    }
  }
}
{
  module: {
    rules: [{
      test: /\.svg$/,
      use: [{
        loader: 'babel-loader'
      }, {
        loader: 'svg-react-loader'
      }]
    }]
  }
}

What are webpack-assets.json?

This file is needed for webpack-isomorphic-tools operation on server. It is created by a custom Webpack plugin and is then read from the filesystem by webpack-isomorphic-tools server instance. When you require(pathToAnAsset) an asset on server then what you get is simply what's there in this file corresponding to this pathToAnAsset key (under the assets section).

Pseudocode:

// requiring assets in your code
require(path) = (path) => return assets[path]

Therefore, if you get such a message in the console:

[webpack-isomorphic-tools] [error] asset not found: ./~/react-toolbox/lib/font_icon/style.scss

Then it means that the asset you requested (require()d) is absent from your webpack-assets.json which in turn means that you haven't placed this asset to your webpack-assets.json in the first place. How to place an asset into webpack-assets.json?

Pseudocode:

// making of webpack-assets.json inside the Webpack plugin
for each type of configuration.assets
  modules.filter(type.filter).for_each (module)
    assets[type.path()] = compile(type.parser(module))

Therefore, if you get the "asset not found" error, first check your webpack-assets.json and second check your webpack-isomorphic-tools configuration section for this asset type: are your filter, path and parser functions correct?

What are Webpack stats?

Webpack stats are a description of all the modules in a Webpack build. When running in debug mode Webpack stats are output to a file named webpack-stats.json in the same folder as your webpack-assets.json file. One may be interested in the contents of this file when writing custom filter, path or parser functions. This file is not needed for operation, it's just some debugging information.

What's a "module"?

This is an advanced topic on Webpack internals

A "module" is a Webpack entity. One of the main features of Webpack is code splitting. When Webpack builds your code it splits it into "chunks" - large portions of code which can be downloaded separately later on (if needed) therefore reducing the initial page load time for your website visitor. These big "chunks" aren't monolithic and in their turn are composed of "modules" which are: standard CommonJS javascript modules you require() every day, pictures, stylesheets, etc. Every time you require() something (it could be anything: an npm module, a javascript file, or a css style, or an image) a module entry is created by Webpack. And the file where this require() call originated is called a reason for this require()d module. Each module entry has a name and a source code, along with a list of chunks it's in and a bunch of other miscellaneous irrelevant properties.

For example, here's a piece of an example webpack-stats.json file (which is generated along with webpack-assets.json in debug mode). Here you can see a random module entry created by Webpack.

{
  ...

  "modules": [
    {
      "id": 0,
      ...
    },
    {
      "id": 1,
      "name": "./~/fbjs/lib/invariant.js",
      "source": "module.exports = global[\"undefined\"] = require(\"-!G:\\\\work\\\\isomorphic-demo\\\\node_modules\\\\fbjs\\\\lib\\\\invariant.js\");",

      // the rest of the fields are irrelevant

      "chunks": [
        0
      ],
      "identifier": "G:\\work\\isomorphic-demo\\node_modules\\expose-loader\\index.js?undefined!G:\\work\\isomorphic-demo\\node_modules\\fbjs\\lib\\invariant.js",
      "index": 27,
      "index2": 7,
      "size": 117,
      "cacheable": true,
      "built": true,
      "optional": false,
      "prefetched": false,
      "assets": [],
      "issuer": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
      "failed": false,
      "errors": 0,
      "warnings": 0,

      "reasons": [
        {
          "moduleId": 418,
          "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\ReactInstanceHandles.js",
          "module": "./~/react/lib/ReactInstanceHandles.js",
          "moduleName": "./~/react/lib/ReactInstanceHandles.js",
          "type": "cjs require",
          "userRequest": "fbjs/lib/invariant",
          "loc": "17:16-45"
        },
        ...
        {
          "moduleId": 483,
          "moduleIdentifier": "G:\\work\\isomorphic-demo\\node_modules\\react\\lib\\traverseAllChildren.js",
          "module": "./~/react/lib/traverseAllChildren.js",
          "moduleName": "./~/react/lib/traverseAllChildren.js",
          "type": "cjs require",
          "userRequest": "fbjs/lib/invariant",
          "loc": "19:16-45"
        }
      ]
    },

    ...
  ]
}

Judging by its reasons and their userRequests one can deduce that this module is require()d by many other modules in this project and the code triggering this module entry creation could look something like this

var invariant = require('fbjs/lib/invariant')

Every time you require() anything in your code, Webpack detects it during build process and the require()d module is "loaded" (decorated, transformed, replaced, etc) by a corresponding module "loader" (or loaders) specified in Webpack configuration file (webpack.conf.js) under the "module.loaders" path. For example, say, all JPG images in a project are configured to be loaded with a "url-loader":

// Webpack configuration
module.exports =
{
  ...

  module:
  {
    loaders:
    [
      ...

      {
        test   : /\.jpg$/,
        loader : 'url-loader'
      }
    ]
  },

  ...
}

This works on client: require() calls will return URLs for JPG images. The next step is to make require() calls to these JPG images behave the same way when this code is run on the server, with the help of webpack-isomorphic-tools. So, the fields of interest of the module object would be name and source: first you find the modules of interest by their names (in this case, the module names would end in ".jpg") and then you parse the sources of those modules to extract the information you need (in this case that would be the real path to an image).

The module object for an image would look like this

{
  ...
  "name": "./assets/images/husky.jpg",
  "source": "module.exports = __webpack_public_path__ + \"9059f094ddb49c2b0fa6a254a6ebf2ad.jpg\""
}

Therefore, in this simple case, in webpack-isomorphic-tools configuration file we create an "images" asset type with extension "jpg" and these parameters:

  • the filter function would be module => module.name.endsWith('.jpg') (and it's the default filter if no filter is specified)
  • the path parser function would be module => module.name (and it's the default path parser if no path parser is specified)
  • the parser function would be module => module.source (and it's the default parser