Asset Modules allow one to use asset files (fonts, icons, etc) without configuring additional loaders.
Prior to webpack 5 it was common to use:
raw-loader
to import a file as a stringurl-loader
to inline a file into the bundle as a data URIfile-loader
to emit a file into the output directoryAsset Modules types replace all of these loaders by adding 4 new module types:
asset/resource
emits a separate file and exports the URL. Previously achievable by using file-loader
.asset/inline
exports a data URI of the asset. Previously achievable by using url-loader
.asset/source
exports the source code of the asset. Previously achievable by using raw-loader
.asset
automatically chooses between exporting a data URI and emitting a separate file. Previously achievable by using url-loader
with asset size limit.When using the old assets loaders (i.e. file-loader
/url-loader
/raw-loader
) along with Asset Modules in webpack 5, you might want to stop Asset Modules from processing your assets again as that would result in asset duplication. This can be done by setting the asset's module type to 'javascript/auto'
.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
}
},
],
+ type: 'javascript/auto'
},
]
},
}
To exclude assets that came from new URL calls from the asset loaders add dependency: { not: ['url'] }
to the loader configuration.
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
+ dependency: { not: ['url'] },
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
],
}
}
By default, under the hood, the asset
type does __webpack_public_path__ + import.meta
. This means that setting the output.publicPath
in your config will allow you to override the URL from which the asset
loads.
If you set the __webpack_public_path__
in code, the way you need to achieve it so as not to break the asset
loading logic is to make sure you run it as the first code in your app and not use a function to do so. An example of this would be having a file called publicPath.js
with contents
__webpack_public_path__ = 'https://cdn.url.com';
And then in your webpack.config.js
updating your entry
field to look like
module.exports = {
entry: ['./publicPath.js', './App.js'],
};
Alternatively, you can do the following in your App.js
without modifying your webpack config. The only downside is you have to enforce ordering here and that can collide with some linting tools.
import './publicPath.js';
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
+ module: {
+ rules: [
+ {
+ test: /\.png/,
+ type: 'asset/resource'
+ }
+ ]
+ },
};
src/index.js
import mainImage from './images/main.png';
img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'
All .png
files will be emitted to the output directory and their paths will be injected into the bundles, besides, you can customize outputPath
and publicPath
for them.
By default, asset/resource
modules are emitting with [hash][ext][query]
filename into output directory.
You can modify this template by setting output.assetModuleFilename
in your webpack configuration:
webpack.config.js
const path = require('path');
module.exports = {
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 case to customize output filename is to emit some kind of assets to a specified directory:
const path = require('path');
module.exports = {
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'
- }
+ },
+ {
+ test: /\.html/,
+ type: 'asset/resource',
+ generator: {
+ filename: 'static/[hash][ext][query]'
+ }
+ }
]
},
};
With this configuration all the html
files will be emitted into a static
directory within the output directory.
Rule.generator.filename
is the same as output.assetModuleFilename
and works only with asset
and asset/resource
module types.
webpack.config.js
const path = require('path');
module.exports = {
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'
+ test: /\.svg/,
+ type: 'asset/inline'
- },
+ }
- {
- test: /\.html/,
- type: 'asset/resource',
- generator: {
- filename: 'static/[hash][ext][query]'
- }
- }
]
}
};
src/index.js
- import mainImage from './images/main.png';
+ import metroMap from './images/metro.svg';
- img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'
+ block.style.background = `url(${metroMap})`; // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)
All .svg
files will be injected into the bundles as data URI.
By default, data URI emitted by webpack represents file contents encoded by using Base64 algorithm.
If you want to use a custom encoding algorithm, you may specify a custom function to encode a file content:
webpack.config.js
const path = require('path');
+ const svgToMiniDataURI = require('mini-svg-data-uri');
module.exports = {
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 by mini-svg-data-uri
package.
webpack.config.js
const path = require('path');
- const svgToMiniDataURI = require('mini-svg-data-uri');
module.exports = {
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);
- }
- }
+ test: /\.txt/,
+ type: 'asset/source',
}
]
},
};
src/example.txt
Hello world
src/index.js
- import metroMap from './images/metro.svg';
+ import exampleText from './example.txt';
- block.style.background = `url(${metroMap}); // url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo...vc3ZnPgo=)
+ block.textContent = exampleText; // 'Hello world'
All .txt
files will be injected into the bundles as is.
When using new URL('./path/to/asset', import.meta.url)
, webpack creates an asset module too.
src/index.js
const logo = new URL('./logo.svg', import.meta.url);
Depending on the target
in your configuration, webpack would compile the above code into a different result:
// 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('url').pathToFileUrl(__filename)
);
As of webpack 5.38.0, Data URLs are supported in new URL()
as well:
src/index.js
const url = new URL('data:,', import.meta.url);
console.log(url.href === 'data:,');
console.log(url.protocol === 'data:');
console.log(url.pathname === ',');
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
+ test: /\.txt/,
+ type: 'asset',
}
]
},
};
Now webpack will automatically choose between resource
and inline
by following a default condition: a file with size less than 8kb will be treated as a inline
module type and resource
module type otherwise.
You can change this condition by setting a Rule.parser.dataUrlCondition.maxSize
option on the module rule level of your webpack configuration:
webpack.config.js
const path = require('path');
module.exports = {
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
+ }
+ }
}
]
},
};
Also you can specify a function to decide to inlining a module or not.
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 mimic the functionality of the inline syntax.
For example, in the case of replacing raw-loader
with 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',
+ }
]
},
and if you'd like 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',
}
]
},
or a oneOf
list of rules. Here only the first matching rule will be applied:
module: {
rules: [
// ...
+ { oneOf: [
{
resourceQuery: /raw/,
type: 'asset/source',
},
+ {
+ test: /\.m?js$/,
+ use: [ ... ]
+ },
+ ] }
]
},
For use cases like Server side rendering, you might want to disable emitting assets, which is feasible with emit
option under Rule.generator
:
module.exports = {
// …
module: {
rules: [
{
test: /\.png$/i,
type: 'asset/resource',
generator: {
emit: false,
},
},
],
},
};