Webpack 5 - Asset Modules
Tags: webpack, webpack5, javascript
Published: 2020-02-16 10:47:36

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`**
```javascript
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: [
          'file-loader',
          'svgo-loader'
        ]
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  }
};
```
**`src/index.js`**
```javascript
import './styles.css';

// ...
```
**`src/styles.css`**
```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](https://github.com/svg/svgo) and emitted into a bundle output path by [file-loader](https://github.com/webpack-contrib/file-loader). A styles will be transformed into:
```css
.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](https://github.com/webpack-contrib/url-loader) for this:
```diff
      {
        test: /\.svg$/,
        use: [
-         'file-loader',
+         'url-loader',
          'svgo-loader'
        ]
      },
```
**Output:**
```
/dist/main.js
```
A bundled css will look like this:
```diff
-   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:
```diff
      {
        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:
```diff
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:
```diff
      {
        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:
```diff
      {
        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](https://github.com/tigt/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:
```diff
+ 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:
```diff
-   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](https://github.com/webpack-contrib/file-loader). All the modules that match a rule with `type: 'asset/resource'` will be emitted into the output directory:
```diff
      {
        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:
```diff
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:
```diff
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`:
```diff
      {
        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](https://github.com/webpack-contrib/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`**
```diff
module.exports = {
       // ...
      {
        test: /\.svg$/,
        type: 'asset/resource',
        generator: {
          filename: 'icons/[hash][ext]'
        },
        use: 'svgo-loader'
      },
+     {
+       test: /\.txt$/,
+       type: 'asset/source'
+     },
      // ...
 ```
**`index.js`**
```diff
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.
```diff
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:
```diff
      {
        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](https://github.com/wdxlab/articles/tree/master/webpack-asset-modules).

## 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](https://github.com/webpack/webpack/issues/9802)

## 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] (https://twitter.com/wdxlab).

Thanks