DEV Community

Sergey Melyukov
Sergey Melyukov

Posted on

Webpack 5 - Asset Modules

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'
        ]
      }
    ]
  }
};
Enter fullscreen mode Exit fullscreen mode

src/index.js

import './styles.css';

// ...
Enter fullscreen mode Exit fullscreen mode

src/styles.css

.logo {
  background: url("/images/logo.svg") no-repeat;
  background-size: cover;
  width: 75px;
  height: 65px;
}
Enter fullscreen mode Exit fullscreen mode

Output:

/dist/main.js
/dist/eb4c5fa504857.svg
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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'
        ]
      },
Enter fullscreen mode Exit fullscreen mode

Output:

/dist/main.js
Enter fullscreen mode Exit fullscreen mode

A bundled css will look like this:

-   background: url("eb4c5fa504857.svg") no-repeat;
+   background: url("data:image/svg+xml;base64,....") no-repeat;
Enter fullscreen mode Exit fullscreen mode

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'
        ]
      },
Enter fullscreen mode Exit fullscreen mode

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
+ }
};
Enter fullscreen mode Exit fullscreen mode

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'
      },
Enter fullscreen mode Exit fullscreen mode

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'
      },
Enter fullscreen mode Exit fullscreen mode

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'
      },
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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'
      },
Enter fullscreen mode Exit fullscreen mode

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]'
+ },
  // ...
};
Enter fullscreen mode Exit fullscreen mode

Output:

/dist/main.js
/dist/assets/logo.svg
Enter fullscreen mode Exit fullscreen mode

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]'
  },
  // ...
};
Enter fullscreen mode Exit fullscreen mode

Output:

/dist/main.js
/dist/assets/eb4c5fa504857.svg
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

Output:

/dist/main.js
/dist/assets/fd441ca8b6d00.png
/dist/icons/eb4c5fa504857.svg
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

webpack.config.js

module.exports = {
       // ...
      {
        test: /\.svg$/,
        type: 'asset/resource',
        generator: {
          filename: 'icons/[hash][ext]'
        },
        use: 'svgo-loader'
      },
+     {
+       test: /\.txt$/,
+       type: 'asset/source'
+     },
      // ...
Enter fullscreen mode Exit fullscreen mode

index.js

import './styles.css';
+ import txt from './file.txt';

+ console.log(txt); // hello world
Enter fullscreen mode Exit fullscreen mode

Output:

/dist/main.js
/dist/icons/eb4c5fa504857.svg
Enter fullscreen mode Exit fullscreen mode

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'
      },
      // ...
Enter fullscreen mode Exit fullscreen mode

The limit for using asset/inline strategy may be overrided:

      {
        test: /.svg$/,
        type: 'asset',
+       parser: {
+         dataUrlCondition: {
+           maxSize: 20 * 1024 // 20kb
+         }
+       },
        use: 'svgo-loader'
      },
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
rafarochas91 profile image
Rafael Rocha

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!

Collapse
 
charlieurt profile image
Charlie Urt

Has you try to set the type as 'type: 'asset/resource'?

Collapse
 
bkrmsp profile image
Bikram Singh Patel • Edited

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]'
},

Collapse
 
kcotrinam profile image
kevin Cotrina

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.

Collapse
 
ericneves profile image
Eric Neves

Thank you very much, excellent article! 🇧🇷