Hello. This post starts a series of articles about the new features of coming webpack 5. Why do I want to tell about the webpack? At least because I take an active part in its development and constantly delve its source code. In this article, I want to tell you about the Asset Modules - an experimental feature of webpack 5, which makes it possible to throw out a few habitual loaders, but not to cut these functionalities.
Let's imagine that we need to bundle a page with some images and styles.
With webpack 4
Webpack 4 configuration for this purpose may look like this:
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
use: [
'file-loader',
'svgo-loader'
]
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
};
src/index.js
import './styles.css';
// ...
src/styles.css
.logo {
background: url("/images/logo.svg") no-repeat;
background-size: cover;
width: 75px;
height: 65px;
}
Output:
/dist/main.js
/dist/eb4c5fa504857.svg
As the result all the svg-files will be handled by svgo and emitted into a bundle output path by file-loader. A styles will be transformed into:
.logo {
background: url("eb4c5fa504857.svg") no-repeat;
background-size: cover;
width: 75px;
height: 65px;
}
But later we may want to inline the images into css to optimize our page. Let's replace file-loader
to url-loader for this:
{
test: /\.svg$/,
use: [
- 'file-loader',
+ 'url-loader',
'svgo-loader'
]
},
Output:
/dist/main.js
A bundled css will look like this:
- background: url("eb4c5fa504857.svg") no-repeat;
+ background: url("data:image/svg+xml;base64,....") no-repeat;
Next we would want to inline only small svg (e.g, less than 8kb) and emit the rest files into the output directory. url-loader
has the limit
-option for this:
{
test: /\.svg$/,
use: [
- 'url-loader',
+ 'url-loader?limit=8192',
'svgo-loader'
]
},
After that only a small svg (less than 8kb) will be inlined, the rest svg-files will be emitted into the output directory, url-loader
will implicitly use file-loader
for it.
The issue is solved... but wait a minute, with webpack 5 and its Asset Modules feature this issue may be resolved easier, without url-loader
and file-loader
(url-loader
implicitly use it for the files with size less than specified in limit
-option).
With webpack 5
First, we need to explicitly specify that we want to use the Asset Modules. Let's specify it in our configuration:
module.exports = {
// ...
+ experiments: {
+ asset: true
+ }
};
Asset Modules is an experimental feature at this moment and we are collecting a feedback from the users.
Now, we just need to mark our svg-files as asset
and that's it. All that I was described related with file-loader
and url-loader
above will work out of the box, without any loaders:
{
test: /\.svg$/,
- use: [
- 'url-loader?limit=8000',
- 'svgo-loader'
- ]
+ type: 'asset',
+ use: 'svgo-loader'
},
That's it, for the modules that match a rule with type: 'asset'
the next logic will be applied: If a module size is less than 8kb (by default), then inline it to the bundle, otherwise emit its file to the output directory.
Note that
use
-property is also taken into account (if specified)
It's not the end, there are some other module-types besides asset
.
asset/inline
Just like the url-loader
. All the modules that match a rule with type: 'asset/inline'
will be inlined to the bundle as data-url:
{
test: /\.svg$/,
- type: 'asset',
+ type: 'asset/inline',
use: 'svgo-loader'
},
Also we can specify a custom data-url generator for type: 'asset/inline'
.
For example, we can use mini-svg-data-uri for the svg-files. It transforms a svg as the data-url but without base64. It reduces a bundle size a little bit:
+ const miniSVGDataURI = require('mini-svg-data-uri');
// ...
{
test: /\.svg$/,
type: 'asset/inline',
+ generator: {
+ dataUrl(content) {
+ content = content.toString();
+ return miniSVGDataURI(content);
+ }
+ },
use: 'svgo-loader'
},
Our css will be transformed into this:
- background: url("data:image/svg+xml;base64,....") no-repeat;
+ background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'....") no-repeat;
We even can use a custom data-url generator and the
use
-property
asset/resource
Just like the file-loader. All the modules that match a rule with type: 'asset/resource'
will be emitted into the output directory:
{
test: /\.svg$/,
- type: 'asset/inline',
+ type: 'asset/resource',
- generator: {
- dataUrl(content) {
- content = content.toString();
- return miniSVGDataURI(content);
- }
- },
use: 'svgo-loader'
},
Output path for Asset Modules
By default, all the modules with type asset/resource
are emitting into the root of the output directory (dist
by default). But with output.assetModuleFilename
you can change this behaviour:
module.exports = {
+ output: {
+ assetModuleFilename: 'assets/[name][ext]'
+ },
// ...
};
Output:
/dist/main.js
/dist/assets/logo.svg
By the way if we replace [name]
to [hash]
then we get a perfect base for assets long term caching:
module.exports = {
output: {
- assetModuleFilename: 'assets/[name][ext]'
+ assetModuleFilename: 'assets/[hash][ext]'
},
// ...
};
Output:
/dist/main.js
/dist/assets/eb4c5fa504857.svg
Also we can chage assetModuleFilename
only for specific asset-rule. E.g. we may emit the svg-icons into the dist/icons
and the rest asset-modules into the dist/assets
:
{
test: /\.svg$/,
type: 'asset/resource',
+ generator: {
+ filename: 'icons/[hash][ext]'
+ },
use: 'svgo-loader'
Output:
/dist/main.js
/dist/assets/fd441ca8b6d00.png
/dist/icons/eb4c5fa504857.svg
asset/source
Just like raw-loader. All the modules that match a rule with type: 'asset/source'
will be inlined without any transformations (as is):
file.txt
hello world
webpack.config.js
module.exports = {
// ...
{
test: /\.svg$/,
type: 'asset/resource',
generator: {
filename: 'icons/[hash][ext]'
},
use: 'svgo-loader'
},
+ {
+ test: /\.txt$/,
+ type: 'asset/source'
+ },
// ...
index.js
import './styles.css';
+ import txt from './file.txt';
+ console.log(txt); // hello world
Output:
/dist/main.js
/dist/icons/eb4c5fa504857.svg
asset
Combines asset/resource
and asset/inline
. It chooses a strategy by next logic: if a module size is greater than 8kb (by default), then it uses asset/resource
strategy and asset/inline
otherwise.
module.exports = {
// ...
{
test: /\.svg$/,
- type: 'asset/resource',
+ type: 'asset'
- generator: {
- filename: 'icons/[hash][ext]'
- },
use: 'svgo-loader'
},
{
test: /\.txt$/,
type: 'asset/source'
},
// ...
The limit for using asset/inline
strategy may be overrided:
{
test: /.svg$/,
type: 'asset',
+ parser: {
+ dataUrlCondition: {
+ maxSize: 20 * 1024 // 20kb
+ }
+ },
use: 'svgo-loader'
},
Totally: webpack 5 Asset Modules makes possible to throw out some habitual loaders because its features works out of the box.
The full example may be found here.
When webpack 5 will come out?
We have no known date. At the moment when I writing this guide, webpack 5 has beta.13
version and a user's feedback is collecting. You can help with it by trying to migrate your projects to webpack 5 (for production on your own risk). You can read more here
P.S
I plan to continue talking about the new features of webpack 5 and about the webpack itself. Some of the articles will be larger, some smaller. Very small notes (not only about webpack) can be seen in my twitter.
Thanks
Top comments (5)
Hi, really good article!
I'm having an issue with data URI (Safari doesn't really like them) and svg sprites. How would I avoid inlining as a Data URI and serve the svg from the static folder by default?
In this case I'm using the Copy plugin for Webpack to place the svgs from an npm package in the node_modules into the assetFileModuleName path.
Any ideas?
Thanks in advance!
Has you try to set the type as 'type: 'asset/resource'?
Hi @smelukov ,
The icons are not loading after i generate my application build with npm run build. Though it works fine on debugging application with npm start
As mentioned above and in webpack documentation, i have used the following approach:
Output Path ->
assetModuleFilename: 'assets/[hash][ext]'
Module Rules ->
test: /.(png|jpg|gif|ttf|woff|eot|jpeg|svg|woff2|otf|webp)$/,
type: 'asset/resource',
generator: {
filename: 'icons/[hash][ext]'
},
Hi, It was a really helpful article. Thanks for taking the time to make it. However, I was wondering If there is a way to call the image from the HTML template since I'm not capable to do it that way yet. Thanks in advance for your time.
Thank you very much, excellent article! 🇧🇷