node package manager
Orgs are free. Discover, share, and reuse code in your team. Create a free org »

isomorphic-loader

NPM version Build Status Dependency Status devDependency Status

Webpack Isomorphic Loader

Webpack loader and tools to make NodeJS require understands files such as images when you are doing server side rendering (SSR).

It contains three pieces: a webpack loader and plugin, and a library for your NodeJS app.

The webpack loader and plugin are for marking and generating a mapping for your asset files.

The Node library is for extending NodeJS require so it understands your asset files by using the mapping.

Install

$ npm install isomorphic-loader --save

Purpose

With webpack and file-loader, you can do things like this in your React code:

import smiley from "./images/smiley.jpg";
 
 
render() {
    return <div><img src={smiley} /></div>
}

That works out nicely, but if you need to do SSR, you will get SyntaxError from Node require. That's because require only understand JS files.

With this module, you can extend Node's require so it understands these files.

It saves a list of your files that you want to be treated as assets and the extended require will return the URL string like it would on the client side.

Usage

Configuring Webpack

First you need to mark all your asset files that you want extendRequire to handle. To do that, simply use the webpack loader isomorphic-loader on the files.

The webpack loader isomorphic-loader is just a simple pass thru loader to mark your files. It will not do anything to the file content.

You also need to install the webpack plugin isomorphic-loader/lib/webpack-plugin to collect and save the list of the files you marked.

For example, in the webpack config, to mark the usual image files to be understood by extendRequire:

var IsomorphicLoaderPlugin = require("isomorphic-loader/lib/webpack-plugin");
 
module.exports = {
    plugins: [
        new IsomorphicLoaderPlugin()
    ],
    module: {
        loaders: [
            {
                test: /\.(jpe?g|png|gif|svg)$/i,
                loader: "file!isomorphic"
            }
        ]
    }
};

You can also mark any file in your code directly:

import smiley from "file!isomorphic!./images/smiley.jpg";

After webpack compiled your project, the plugin will generate a config file .isomorphic-loader-config.json in your CWD and a JSON file with mappings of the asset files you marked.

more details about config and assets mapping files below.

Extending Node Require

To use the asset files mapping for SSR with Node, you need to extend require before your server starts.

To do that:

var extendRequire = require("isomorphic-loader/lib/extend-require");
 
extendRequire(function (err) {
    if (err) {
        console.log(err);
    } else {
        require("./server");
    }
});

If Promise is supported:

extendRequire().then(function () {
    require("./server");
}).catch(function (err) {
    console.log(err);
});

It will use the config file .isomorphic-loader-config.json in your CWD generated by the webpack plugin.

If the config file is not found, then it will wait until it's generated. This is so you can use webpack-dev-server.

Custom Config Overrides

You can pass in custom overrides for the config when you call extendRequire.

extendRequire(config);

Where config is an object, which will be deep extended into the object read from the config generated by the webpack plugin. Look inside the file .isomorphic-loader-config.json to see what you can override.

For example, if you want to always use a different publicPath:

extendRequire({output: { publicPath: "http://cdn.com/" } }).then(function () {
    require("./server");
}).catch(function (err) {
    console.log(err);
});

The following config options for extend require itself are supported:

  • startDelay - Delay extendRequire start. More details here
  • processAssets - A call back that will be called every time assets is loaded and set. It should take assets as the only argument and returns the new assets that's been processed.

webpack-dev-server

webpack-dev-server is automatically detected and supported.

There is no easy way to detect webpack-dev-server, but if you use its CLI, then it changes output.path to "/". Since it's unlikely anyone would do that, when that is the case it's assumed that webpack-dev-server is running.

The webpack plugin accepts an object webpackDev in the options with two configs for webpack-dev-server.

  • webpackDev.url - The URL to the server. Default: http://localhost:8080.
  • webpackDev.addUrl - A flag to toggle adding the dev URL to the final asset URL. Default: true

In example:

new IsomorphicLoaderPlugin({
    webpackDev: {
        url: "http://localhost:8080",
        addUrl: true
    }
});

With the above config, your assets will be resolved to http://localhost:8080/<publicPath>/<hash>.<ext> when loaded with require.

They are resolved to <publicPath>/<hash>.<ext> if webpackDev.addUrl is false.

When webpack-dev-server refresh modified files, extendRequire will also auto refresh.

Reloading Assets for Extend Require

It's not necessary, but if at any time you wish extendRequire to reload the assets, then you can use the loadAssets API.

var extendRequire = require("isomorphic-loader/lib/extend-require");
 
extendRequire.loadAssets(function (err) {
    if (err) {
        console.log(err);
    }
});

Deactivating extendRequire

If you wish to deactivate extendRequire during run time, then you can use the deactivate API:

extendRequire.deactivate();

Reload assets to reactivate.

process.env.WEBPACK_DEV

If extendRequire detected that webpack-dev-server is running, it will set process.env.WEBPACK_DEV to "true" before returning to your server startup code. If you don't want that, set webpackDev.skipSetEnv to true in the plugin options:

module.exports = {
    plugins: [
        new IsomorphicLoaderPlugin({ webpackDev: { skipSetEnv: true } })
    ]
}

Config and Assets Files

The webpack plugin creates a config file in your CWD and an assets mapping file in your webpack output directory.

The default name of the mapping file is isomorphic-assets.json.

You can configure the name when you initialize the plugin. For example:

new IsomorphicLoaderPlugin({assetsFile: "assets/isomorphic-assets.json"});

It will also save in the config the publicPath from your webpack config.

Here is how the generated config file might look like:

{
  "version": "0.1.0",
  "context": "client",
  "output": {
    "path": "dist",
    "filename": "bundle.js",
    "publicPath": "/test/"
  },
  "webpackDev": {
    "url": "http://localhost:8080",
    "addUrl": true
  },
  "isWebpackDev": false,
  "assetsFile": "dist/isomorphic-assets.json"
}

Since webpack-dev-server keeps output in memory, the assets mapping is saved to the config also when it's detected.

Auto Removing Config

To avoid immediately starting in webpack-dev-server mode when a config file already exist. The webpack plugin automatically remove the config file at startup. If you don't want it to do that, pass in an option keepExistingConfig as true:

module.exports = {
    plugins: [
        new IsomorphicLoaderPlugin({ keepExistingConfig: true })
    ]
}

Start Delay

Even with the plugin removing the config file, if you take the approach of starting your sever and webpack-dev-server together, then there's a high chance your server could start before the webpack plugin has a chance to remove the config file.

To avoid that, extendRequire automatically delays a short time (500ms) before starting.

You can configure that value when you call extendRequire. For example, set it to zero to start immediately.

extendRequire({startDelay: 0}).then(function () {
    require("./server");
}).catch(function (err) {
    console.log(err);
});

Usage with CDN Server

If you publish your assets to a Content Delivery Network server, and if it generates a new unique path for your assets, then you likely have to set publicPath after webpack compiled your project.

That's why webpack's document has this note in the section about publicPath:

Note: In cases when the eventual publicPath of output files isn't known at compile time, it can be left blank and set dynamically at runtime in the entry point file. If you don't know the publicPath while compiling you can omit it and set __webpack_public_path__ on your entry point.

In that case, you would have to save the path CDN created for you and pass it to extendRequire with a custom config override, or you can just modify the config file directly.

If your CDN server generates an unique URL for every asset file instead of a single base path, then you have to do some custom post processing to update the asset mapping files yourself.

License

MIT License