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.
$ npm install isomorphic-loader --save
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.
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.
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.
You can pass in custom overrides for the config when you call extendRequire
.
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);
});
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>
ifwebpackDev.addUrl
isfalse
.
When webpack-dev-server refresh modified files, extendRequire
will also auto refresh.
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);
}
});
If you wish to deactivate extendRequire
during run time, then you can use the deactivate
API:
extendRequire.deactivate();
Reload assets to reactivate.
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 } })
]
}
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.
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 })
]
}
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);
});
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 thepublicPath
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.