On this page

Asset modules let you work with asset files such as fonts and icons without setting up extra loaders.

Before webpack 5, the common approach was to use:

  • raw-loader to import a file as a string.
  • url-loader to inline a file into the bundle as a data URI.
  • file-loader to emit a file into the output directory.

Asset modules replace all of these loaders by introducing five new module types:

  • asset/resource emits a separate file and exports its URL. Previously achievable with file-loader.
  • asset/inline exports a data URI of the asset. Previously achievable with url-loader.
  • asset/source exports the source code of the asset. Previously achievable with raw-loader.
  • asset/bytes exports a Uint8Array view of the asset.
  • asset automatically chooses between exporting a data URI and emitting a separate file. Previously achievable with url-loader using an asset size limit.

If you combine the old asset loaders (file-loader, url-loader, or raw-loader) with asset modules in webpack 5, you may want to prevent asset modules from processing the same assets again, since that would duplicate them. To do this, set the asset's module type to 'javascript/auto'.

export default {
  module: {
   rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
            }
          },
        ],
+       type: 'javascript/auto'
      },
   ]
  },
}

To exclude assets that originate from new URL calls from the asset loaders, add dependency: { not: ['url'] } to the loader configuration.

export default {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
+       dependency: { not: ['url'] },
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
            },
          },
        ],
      },
    ],
  }
}

Under the hood, the asset type resolves URLs as __webpack_public_path__ + import.meta by default. As a result, setting output.publicPath in your configuration lets you override the URL from which the asset loads.

If you set __webpack_public_path__ in code, you need to run it as the very first code in your app, and you must not wrap it in a function, otherwise the asset loading logic will break. For example, you might have a file named publicPath.js with the following contents:

Then, in your webpack.config.js, update the entry field to include it:

export default {
  entry: ['./publicPath.js', './App.js'],
};

Alternatively, you can import it at the top of App.js without touching your webpack configuration. The only downside is that you must enforce import ordering yourself, which can conflict with some linting tools.

import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
+ module: {
+   rules: [
+     {
+       test: /\.png/,
+       type: 'asset/resource',
+     },
+   ],
+ },
};

All .png files will be emitted to the output directory and their paths will be injected into the bundles. You can also customize outputPath and publicPath for them.

By default, asset/resource modules are emitted into the output directory using the [hash][ext][query] filename template.

You can change this template by setting output.assetModuleFilename in your webpack configuration:

import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
+   assetModuleFilename: 'images/[hash][ext][query]',
  },
  module: {
    rules: [
      {
        test: /\.png/,
        type: 'asset/resource',
      },
    ],
  },
};

Another reason to customize the output filename is to emit a certain kind of asset into a specific directory:

import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
+   assetModuleFilename: 'images/[hash][ext][query]',
  },
  module: {
    rules: [
+     {
+       test: /\.html/,
+       type: 'asset/resource',
+       generator: {
+         filename: 'static/[hash][ext][query]',
+       },
+     },
    ],
  },
};

With this configuration, all .html files will be emitted into a static directory inside the output directory.

Rule.generator.filename behaves the same as output.assetModuleFilename and works only with the asset and asset/resource module types.

import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
+     {
+       test: /\.svg/,
+       type: 'asset/inline',
+     },
    ],
  },
};

All .svg files will be injected into the bundles as data URIs.

By default, the data URI that webpack emits represents the file contents encoded with the Base64 algorithm.

If you want to use a custom encoding algorithm, you can provide a function to encode the file contents:

import path from "node:path";
import { fileURLToPath } from 'node:url';
+ import svgToMiniDataURI from "mini-svg-data-uri";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);


export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.svg/,
        type: 'asset/inline',
+       generator: {
+         dataUrl: content => {
+           content = content.toString();
+           return svgToMiniDataURI(content);
+         },
+       },
      },
    ],
  },
};

Now all .svg files will be encoded with the mini-svg-data-uri package.

import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
+     {
+       test: /\.txt/,
+       type: 'asset/source',
+     },
    ],
  },
};

[!TIP] You don't need to add rules when using the import text from './file.txt' with { type: "text" }; syntax.

Alternative usage:

import exampleText from './example.txt' with { type: 'text' };

block.textContent = exampleText; // 'Hello world';

All .txt files will be injected into the bundles as UTF-8 strings.

When you use new URL('./path/to/asset', import.meta.url), webpack creates an asset module too.

[!TIP] You don't need to add rules when using the const file = new URL('./file.ext', import.meta.url); syntax.

Depending on the target in your configuration, webpack compiles the code above into different output:

// target: web
new URL(
  `${__webpack_public_path__}logo.svg`,
  document.baseURI || self.location.href
);

// target: webworker
new URL(`${__webpack_public_path__}logo.svg`, self.location);

// target: node, node-webkit, nwjs, electron-main, electron-renderer, electron-preload, async-node
new URL(
  `${__webpack_public_path__}logo.svg`,
  require('node:url').pathToFileUrl(__filename)
);

As of webpack 5.38.0, data URLs are supported in new URL() as well:

const url = new URL('data:,', import.meta.url);
console.log(url.href === 'data:,');
console.log(url.protocol === 'data:');
console.log(url.pathname === ',');
import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
+     {
+       test: /\.txt/,
+       type: 'asset',
+     },
    ],
  },
};

Now webpack automatically chooses between resource and inline based on a default condition: a file smaller than 8kb is treated as an inline module type, and larger files use the resource module type.

You can change this condition by setting the Rule.parser.dataUrlCondition.maxSize option at the module rule level of your webpack configuration:

import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.txt/,
        type: 'asset',
+       parser: {
+         dataUrlCondition: {
+           maxSize: 4 * 1024, // 4kb
+         },
+       },
      },
    ],
  },
};

You can also specify a function to decide whether to inline a module.

import path from "node:path";
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export default {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
+     {
+       test: /\.txt/,
+       type: 'asset/bytes',
+     },
    ],
  },
};

[!TIP] You don't need to add rules when using the import data from './file.ext' with { type: "bytes" }; syntax.

Alternative usage:

import exampleText from './example.txt' with { type: 'bytes' };

const decoder = new TextDecoder('utf-8');
const textString = decoder.decode(exampleText);

block.textContent = textString; // 'Hello world';

All .txt files will be injected into the bundles as raw bytes (Uint8Array), without any text encoding or transformation.

Before asset modules and webpack 5, it was possible to use inline syntax with the legacy loaders mentioned above.

It is now recommended to remove all inline loader syntax and use a resourceQuery condition to reproduce the behavior of the inline syntax.

For example, to replace raw-loader with the asset/source type:

- import myModule from 'raw-loader!my-module';
+ import myModule from 'my-module?raw';

And in the webpack configuration:

module: {
    rules: [
    // ...
+     {
+       resourceQuery: /raw/,
+       type: 'asset/source',
+     }
    ]
  },

If you want to exclude raw assets from being processed by other loaders, use a negative condition:

module: {
    rules: [
    // ...
+     {
+       test: /\.m?js$/,
+       resourceQuery: { not: [/raw/] },
+       use: [ ... ]
+     },
      {
        resourceQuery: /raw/,
        type: 'asset/source',
      }
    ]
  },

Alternatively, use a oneOf list of rules, where only the first matching rule is applied:

module: {
    rules: [
    // ...
+     { oneOf: [
        {
          resourceQuery: /raw/,
          type: 'asset/source',
        },
+       {
+         test: /\.m?js$/,
+         use: [ ... ]
+       },
+     ] }
    ]
  },

For use cases such as server-side rendering, you might want to disable emitting assets. This is possible with the emit option under Rule.generator:

export default {
  // …
  module: {
    rules: [
      {
        test: /\.png$/i,
        type: 'asset/resource',
        generator: {
          emit: false,
        },
      },
    ],
  },
};