Webpack provides multiple ways to set up module loaders. Each loader is a function accepting input and returning output. They can have side effects as they can emit to the file system and can intercept execution to implement caching.
Webpack supports common JavaScript formats out of the box. Other formats can be handled using loaders by setting up a loader, or loaders, and connecting those with your directory structure. In the example below, webpack processes JavaScript through Babel:
webpack.config.js
const config = {
module: {
rules: [
{
// **Conditions** to match files using RegExp, function.
test: /\.js$/,
// **Restrict** matching to a directory.
include: path.join(__dirname, "app"),
exclude: (path) => path.match(/node_modules/);
// **Actions** to apply loaders to the matched files.
use: "babel-loader",
},
],
},
};
It's good to keep in mind that webpack's loaders are always evaluated from right to left and from bottom to top (separate definitions). The right-to-left rule is easier to remember when you think about as functions. You can read definition use: ["style-loader", "css-loader"]
as style(css(input))
based on this rule. Consider the example below:
const config = {
test: /\.css$/,
use: ["style-loader", "css-loader"],
};
Based on the right to left rule, the example can be split up while keeping it equivalent:
const config = [
{ test: /\.css$/, use: "style-loader" },
{ test: /\.css$/, use: "css-loader" },
];
If you are not sure how a particular RegExp matches, consider using an online tool, such as regex101, RegExr, or ExtendsClass RegEx Tester.
Even though it would be possible to develop an arbitrary configuration using the rule above, it can be convenient to be able to force specific rules to be applied before or after regular ones. The enforce
field can come in handy here. It can be set to either pre
or post
to push processing either before or after other loaders.
Linting is a good example because the build should fail before it does anything else. Using enforce: "post"
is rarer and it would imply you want to perform a check against the built source. Performing analysis against the built source is one potential example.
const config = {
test: /\.js$/,
enforce: "pre", // "post" too
use: "eslint-loader",
};
It would be possible to write the same configuration without enforce
if you chained the declaration with other loaders related to the test
carefully. Using enforce
removes the necessity for that and allows you to split loader execution into separate stages that are easier to compose.
There's a query format that allows passing parameters to loaders:
const config = { test: /\.js$/, use: "babel-loader?presets[]=env" };
This style of configuration works in entries and source imports too as webpack picks it up. The format comes in handy in certain individual cases, but often you are better off using more readable alternatives. In this case, it's preferable to go through use
:
const config = {
test: /\.js$/,
use: { loader: "babel-loader", options: { presets: ["env"] } },
};
Even though configuration level loader definitions are preferable, it's possible to write loader definitions inline:
import "url-loader!./foo.png"; // Process through url-loader first
import "!!url-loader!./bar.png"; // Override completely
The problem with this approach is that it couples your source with webpack. Nonetheless, it's still an excellent form to know.
Since configuration entries go through the same mechanism, the same forms work there as well:
const config = { entry: { app: "babel-loader!./app" } };
use
using a function#In the book setup, you compose configuration on a higher level. Another option to achieve similar results would be to branch at use
as webpack's loader definitions accept functions that allow you to branch depending on the environment:
const config = {
test: /\.css$/,
// `resource` refers to the resource path matched.
// `resourceQuery` contains possible query passed to it
// `issuer` tells about match context path
// You have to return something falsy, object, or a string
use: ({ resource, resourceQuery, issuer }) =>
env === "development" && ["css-loader", "style-loader"],
};
Carefully applied, this technique allows different means of composition.
info
object#Webpack provides advanced access to compilation if you pass a function as a loader definition for the use
field. It expects you to return a loader from the call:
const config = {
rules: [
{
test: /\.js$/,
use: [
(info) => ({
loader: "babel-loader",
options: { presets: ["env"] },
}),
],
},
],
};
If you execute code like this, you'll see a print in the console:
{
resource: '/webpack-demo/src/main.css',
realResource: '/webpack-demo/src/main.css',
resourceQuery: '',
issuer: '',
compiler: 'mini-css-extract-plugin /webpack-demo/node_modules/css-loader/dist/cjs.js!/webpack-demo/node_modules/postcss-loader/src/index.js??ref--4-2!/webpack-demo/node_modules/postcss-loader/src/index.js??ref--4-3!/webpack-demo/src/main.css'
}
resourceQuery
#oneOf
field makes it possible to route webpack to a specific loader based on a resource related match:
const config = {
test: /\.png$/,
oneOf: [
{ resourceQuery: /inline/, use: "url-loader" },
{ resourceQuery: /external/, use: "file-loader" },
],
};
If you wanted to embed the context information to the filename, the rule could use resourcePath
over resourceQuery
.
issuer
#issuer
can be used to control behavior based on where a resource was imported. In the example below, style-loader is applied a CSS file is captured through JavaScript:
const config = {
test: /\.css$/,
rules: [
{ issuer: /\.js$/, use: "style-loader" },
{ use: "css-loader" },
],
};
Another approach would be to mix issuer
and not
:
const config = {
test: /\.css$/,
rules: [
// Add CSS imported from other modules to the DOM
{ issuer: { not: /\.css$/ }, use: "style-loader" },
{ use: "css-loader" }, // Apply against CSS imports
],
};
test
combined with include
or exclude
to constrain the match is the most common approach to match files. These accept the data types as listed below:
test
, include
, exclude
- Match against a RegExp, string, function, an object, or an array of conditions like these.resource: /inline/
- Match against a resource path including the query. Examples: /path/foo.inline.js
, /path/bar.png?inline
.issuer: /bar.js/
- Match against a resource requested from the match. Example: /path/foo.png
would match if it was requested from /path/bar.js
.resourcePath: /inline/
- Match against a resource path without its query. Example: /path/foo.inline.png
.resourceQuery: /inline/
- Match against a resource based on its query. Example: /path/foo.png?inline
.Boolean based fields can be used to constrain these matchers further:
not
- Do not match against a condition (see test
for accepted values).and
- Match against an array of conditions. All must match.or
- Match against an array while any must match.Loader behavior can be understood in greater detail by inspecting them. loader-runner allows you to run them in isolation without webpack. Webpack uses this package internally and Extending with Loaders chapter covers it in detail.
inspect-loader allows you to inspect what's being passed between loaders. Instead of having to insert console.log
s within node_modules
, you can attach this loader to your configuration and inspect the flow there.
Webpack provides multiple ways to setup loaders but sticking with use
is enough starting from webpack 4. Be careful with loader ordering, as it's a common source of problems.
To recap:
In the next chapter, you'll learn to load images using webpack.
This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.