Loading Images
HTTP/1 application can be made slow by loading a lot of small assets as each request comes with an overhead. HTTP/2 helps in this regard and changes the situation somewhat drastically. Till then you are stuck with different approaches. Webpack allows a few of these.
Webpack can inline assets by using url-loader. It emits your images as base64 strings within your JavaScript bundles. The process decreases the number of requests needed while growing the bundle size. It's enough to use url-loader during development. You want to consider other alternatives for the production build, though.
Webpack gives control over the inlining process and can defer loading to file-loader. file-loader outputs image files and returns paths to them instead of inlining. This technique works with other assets types, such as fonts, as you see in the later chapters.
Setting Up url-loader
url-loader is a good starting point and it's the perfect option for development purposes, as you don't have to care about the size of the resulting bundle. It comes with a limit option that can be used to defer image generation to file-loader after an absolute limit is reached. This way you can inline small files to your JavaScript bundles while generating separate files for the bigger ones.
If you use the limit option, you need to install both url-loader and file-loader to your project. Assuming you have configured your styles correctly, webpack resolves any url()
statements your styling contains. You can point to the image assets through your JavaScript code as well.
In case the limit
option is used, url-loader passes possible additional options to file-loader making it possible to configure its behavior further.
To load .jpg and .png files while inlining files below 25kB, you would have to set up a loader:
{
test: /\.(jpg|png)$/,
use: {
loader: "url-loader",
options: {
limit: 25000,
},
},
},
If you prefer to use another loader than file-loader as the limit is reached, set
fallback: "some-loader"
. Then webpack will resolve to that instead of the default.
Setting Up file-loader
If you want to skip inlining altogether, you can use file-loader directly. The following setup customizes the resulting filename. By default, file-loader returns the MD5 hash of the file's contents with the original extension:
{
test: /\.(jpg|png)$/,
use: {
loader: "file-loader",
options: {
name: "[path][name].[hash].[ext]",
},
},
},
If you want to output your images below a particular directory, set it up with
name: "./images/[hash].[ext]"
.
W> Be careful not to apply both loaders on images at the same time! Use the include
field for further control if url-loader limit
isn't enough.
Integrating Images to the Project
The ideas above can be wrapped in a small helper that can be incorporated into the book project. To get started, install the dependencies:
npm install file-loader url-loader --save-dev
Set up a function as below:
webpack.parts.js
exports.loadImages = ({ include, exclude, options } = {}) => ({
module: {
rules: [
{
test: /\.(png|jpg)$/,
include,
exclude,
use: {
loader: "url-loader",
options,
},
},
],
},
});
To attach it to the configuration, adjust as follows. The configuration defaults to url-loader during development and uses both url-loader and file-loader in production to maintain smaller bundle sizes. url-loader uses file-loader implicitly when limit
is set, and both have to be installed for the setup to work.
webpack.config.js
const productionConfig = merge([
...
leanpub-start-insert
parts.loadImages({
options: {
limit: 15000,
name: "[name].[ext]",
},
}),
leanpub-end-insert
]);
const developmentConfig = merge([
...
leanpub-start-insert
parts.loadImages(),
leanpub-end-insert
]);
To test that the setup works, download an image or generate it (convert -size 100x100 gradient:blue logo.png
) and refer to it from the project:
src/main.css
body {
background: cornsilk;
leanpub-start-insert
background-image: url("./logo.png");
background-repeat: no-repeat;
background-position: center;
leanpub-end-insert
}
The behavior changes depending on the limit
you set. Below the limit, it should inline the image while above it should emit a separate asset and a path to it. The CSS lookup works because of css-loader. You can also try importing the image from JavaScript code and see what happens.
Loading SVGs
Webpack allows a couple ways to load SVGs. However, the easiest way is through file-loader as follows:
{
test: /\.svg$/,
use: "file-loader",
},
Assuming you have set up your styling correctly, you can refer to your SVG files as below. The example SVG path below is relative to the CSS file:
.icon {
background-image: url("../assets/icon.svg");
}
Consider also the following loaders:
- raw-loader gives access to the raw SVG content.
- svg-inline-loader goes a step further and eliminates unnecessary markup from your SVGs.
- svg-sprite-loader can merge separate SVG files into a single sprite, making it potentially more efficient to load as you avoid request overhead. It supports raster images (.jpg, .png) as well.
- svg-url-loader loads SVGs as UTF-8 encoded data urls. The result is smaller and faster to parse than base64.
- react-svg-loader emits SVGs as React components meaning you could end up with code like
<Image width={50} height={50}/>
to render a SVG in your code after importing it.
You can still use url-loader and the tips above with SVGs too.
Optimizing Images
In case you want to compress your images, use image-webpack-loader, svgo-loader (SVG specific), or imagemin-webpack-plugin. This type of loader should be applied first to the data, so remember to place it as the last within use
listing.
Compression is particularly valuable for production builds as it decreases the amount of bandwidth required to download your image assets and speed up your site or application as a result.
srcset
Utilizing resize-image-loader and responsive-loader allow you to generate srcset
compatible collections of images for modern browsers. srcset
gives more control to the browsers over what images to load and when resulting in higher performance.
Loading Images Dynamically
Webpack allows you to load images dynamically based on a condition. The techniques covered in the Code Splitting and Dynamic Loading chapters are enough for this purpose. Doing this can save bandwidth and load images only when you need them or preload them while you have time.
Loading Sprites
Spriting technique allows you to combine multiple smaller images into a single image. It has been used for games to describe animations and it's valuable for web development as well as you avoid request overhead.
webpack-spritesmith converts provided images into a sprite sheet and Sass/Less/Stylus mixins. You have to set up a SpritesmithPlugin
, point it to target images, and set the name of the generated mixin. After that, your styling can pick it up:
@import "~sprite.sass";
.close-button {
sprite($close);
}
.open-button {
sprite($open);
}
Using Placeholders
image-trace-loader loads images and exposes the results as image/svg+xml
URL encoded data. It can be used in conjunction with file-loader and url-loader for showing a placeholder while the actual image is being loaded.
lqip-loader implements a similar idea. Instead of tracing, it provides a blurred image instead of a traced one.
Getting Image Dimensions
Sometimes getting the only reference to an image isn't enough. image-size-loader emits image dimensions, type, and size in addition to the reference to the image itself.
Referencing to Images
Webpack can pick up images from style sheets through @import
and url()
assuming css-loader has been configured. You can also refer to your images within the code. In this case, you have to import the files explicitly:
import src from "./avatar.png";
// Use the image in your code somehow now
const Profile = () => <img src={src} />;
If you are using React, then you use babel-plugin-transform-react-jsx-img-import to generate the require
automatically. In that case, you would end up with code:
const Profile = () => <img src="avatar.png" />;
It's also possible to set up dynamic imports as discussed in the Code Splitting chapter. Here's a small example:
const src = require(`./avatars/${avatar}`);`.
Images and css-loader Source Map Gotcha
If you are using images and css-loader with the sourceMap
option enabled, it's important that you set output.publicPath
to an absolute value pointing to your development server. Otherwise, images aren't going to work. See the relevant webpack issue for further explanation.
Conclusion
Webpack allows you to inline images within your bundles when needed. Figuring out proper inlining limits for your images requires experimentation. You have to balance between bundle sizes and the number of requests.
To recap:
- url-loader inlines the assets within JavaScript. It comes with a
limit
option that allows you to defer assets above it to file-loader. - file-loader emits image assets and returns paths to them to the code. It allows hashing the asset names.
- You can find image optimization related loaders and plugins that allow you to tune their size further.
- It's possible to generate sprite sheets out of smaller images to combine them into a single request.
- Webpack allows you to load images dynamically based on a given condition.
- If you are using source maps, you should remember to set
output.publicPath
to an absolute value for the images to show up.
You'll learn to load fonts using webpack in the next chapter.