If you've been following the guides from the start, you will now have a small project that shows "Hello webpack". Now let's try to incorporate some other assets, like images, to see how they can be handled.
Prior to webpack, front-end developers would use tools likegrunt andgulp to process these assets and move them from their/src folder into their/dist or/build directory. The same idea was used for JavaScript modules, but tools like webpack willdynamically bundle all dependencies (creating what's known as adependency graph). This is great because every module nowexplicitly states its dependencies and we'll avoid bundling modules that aren't in use.
One of the coolest webpack features is that you can alsoinclude any other type of file, besides JavaScript, for which there is a loader or built-inAsset Modules support. This means that the same benefits listed above for JavaScript (e.g. explicit dependencies) can be applied to everything used in building a website or web app. Let's start with CSS, as you may already be familiar with that setup.
Let's make a minor change to our project before we get started:
dist/index.html
<!DOCTYPE html><html> <head> <meta charset="utf-8" />- <title>Getting Started</title>+ <title>Asset Management</title> </head> <body>- <script src="main.js"></script>+ <script src="bundle.js"></script> </body></html>webpack.config.js
const path = require('path');module.exports = { entry: './src/index.js', output: {- filename: 'main.js',+ filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },};In order toimport a CSS file from within a JavaScript module, you need to install and add thestyle-loader andcss-loader to yourmodule configuration:
npminstall --save-dev style-loader css-loaderwebpack.config.js
const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },+ module: {+ rules: [+ {+ test: /\.css$/i,+ use: ['style-loader', 'css-loader'],+ },+ ],+ },};Module loaders can be chained. Each loader in the chain applies transformations to the processed resource. A chain is executed in reverse order. The first loader passes its result (resource with applied transformations) to the next one, and so forth. Finally, webpack expects JavaScript to be returned by the last loader in the chain.
The above order of loaders should be maintained:'style-loader' comes first and followed by'css-loader'. If this convention is not followed, webpack is likely to throw errors.
webpack uses a regular expression to determine which files it should look for and serve to a specific loader. In this case, any file that ends with.css will be served to thestyle-loader and thecss-loader.
This enables you toimport './style.css' into the file that depends on that styling. Now, when that module is run, a<style> tag with the stringified css will be inserted into the<head> of your html file.
Let's try it out by adding a newstyle.css file to our project and import it in ourindex.js:
project
webpack-demo |- package.json |- package-lock.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src+ |- style.css |- index.js |- /node_modulessrc/style.css
.hello{color: red;}src/index.js
import _ from 'lodash';+import './style.css';function component() { const element = document.createElement('div'); // Lodash, now imported by this script element.innerHTML = _.join(['Hello', 'webpack'], ' ');+ element.classList.add('hello'); return element;}document.body.appendChild(component());Now run your build command:
$npm run build...[webpack-cli] Compilation finishedasset bundle.js72.6 KiB[emitted][minimized](name: main)1 related assetruntime modules1000 bytes5 modulesorphan modules326 bytes[orphan]1 modulecacheable modules539 KiB modules by path ./node_modules/538 KiB ./node_modules/lodash/lodash.js530 KiB[built][code generated] ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js6.67 KiB[built][code generated] ./node_modules/css-loader/dist/runtime/api.js1.57 KiB[built][code generated] modules by path ./src/965 bytes ./src/index.js +1 modules639 bytes[built][code generated] ./node_modules/css-loader/dist/cjs.js!./src/style.css326 bytes[built][code generated]webpack5.4.0 compiled successfullyin2231 msOpen updist/index.html in your browser again and you should see thatHello webpack is now styled in red. To see what webpack did, inspect the page (don't view the page source, as it won't show you the result, because the<style> tag is dynamically created by JavaScript) and look at the page's head tags. It should contain the style block that we imported inindex.js.
Note that you can, and in most cases should,minimize css for better load times in production. On top of that, loaders exist for pretty much any flavor of CSS you can think of –postcss,sass, andless to name a few.
So now we're pulling in our CSS, but what about our images like backgrounds and icons? As of webpack 5, using the built-inAsset Modules we can easily incorporate those in our system as well:
webpack.config.js
const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.css$/i, use: ['style-loader', 'css-loader'], },+ {+ test: /\.(png|svg|jpg|jpeg|gif)$/i,+ type: 'asset/resource',+ }, ], },};Now, when youimport MyImage from './my-image.png', that image will be processed and added to youroutput directoryand theMyImage variable will contain the final url of that image after processing. When using thecss-loader, as shown above, a similar process will occur forurl('./my-image.png') within your CSS. The loader will recognize this is a local file, and replace the'./my-image.png' path with the final path to the image in youroutput directory. Thehtml-loader handles<img src="./my-image.png" /> in the same manner.
Let's add an image to our project and see how this works, you can use any image you like:
project
webpack-demo |- package.json |- package-lock.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src+ |- icon.png |- style.css |- index.js |- /node_modulessrc/index.js
import _ from 'lodash';import './style.css';+import Icon from './icon.png';function component() { const element = document.createElement('div'); // Lodash, now imported by this script element.innerHTML = _.join(['Hello', 'webpack'], ' '); element.classList.add('hello');+ // Add the image to our existing div.+ const myIcon = new Image();+ myIcon.src = Icon;++ element.appendChild(myIcon);+ return element;}document.body.appendChild(component());src/style.css
.hello { color: red;+ background: url('./icon.png');}Let's create a new build and open up theindex.html file again:
$npm run build...[webpack-cli] Compilation finishedassets by status9.88 KiB[cached]1 assetasset bundle.js73.4 KiB[emitted][minimized](name: main)1 related assetruntime modules1.82 KiB6 modulesorphan modules326 bytes[orphan]1 modulecacheable modules540 KiB(javascript)9.88 KiB(asset) modules by path ./node_modules/539 KiB modules by path ./node_modules/css-loader/dist/runtime/*.js2.38 KiB ./node_modules/css-loader/dist/runtime/api.js1.57 KiB[built][code generated] ./node_modules/css-loader/dist/runtime/getUrl.js830 bytes[built][code generated] ./node_modules/lodash/lodash.js530 KiB[built][code generated] ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js6.67 KiB[built][code generated] modules by path ./src/1.45 KiB(javascript)9.88 KiB(asset) ./src/index.js +1 modules794 bytes[built][code generated] ./src/icon.png42 bytes(javascript)9.88 KiB(asset)[built][code generated] ./node_modules/css-loader/dist/cjs.js!./src/style.css648 bytes[built][code generated]webpack5.4.0 compiled successfullyin1972 msIf all went well, you should now see your icon as a repeating background, as well as animg element beside ourHello webpack text. If you inspect this element, you'll see that the actual filename has changed to something like29822eaa871e8eadeaa4.png. This means webpack found our file in thesrc folder and processed it!
So what about other assets like fonts? The Asset Modules will take any file you load through them and output it to your build directory. This means we can use them for any kind of file, including fonts. Let's update ourwebpack.config.js to handle font files:
webpack.config.js
const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.css$/i, use: ['style-loader', 'css-loader'], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', },+ {+ test: /\.(woff|woff2|eot|ttf|otf)$/i,+ type: 'asset/resource',+ }, ], },};Add some font files to your project:
project
webpack-demo |- package.json |- package-lock.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src+ |- my-font.woff+ |- my-font.woff2 |- icon.png |- style.css |- index.js |- /node_modulesWith the loader configured and fonts in place, you can incorporate them via an@font-face declaration. The localurl(...) directive will be picked up by webpack, as it was with the image:
src/style.css
+@font-face {+ font-family: 'MyFont';+ src: url('./my-font.woff2') format('woff2'),+ url('./my-font.woff') format('woff');+ font-weight: 600;+ font-style: normal;+}+.hello { color: red;+ font-family: 'MyFont'; background: url('./icon.png');}Now run a new build and let's see if webpack handled our fonts:
$npm run build...[webpack-cli] Compilation finishedassets by status9.88 KiB[cached]1 assetassets by info33.2 KiB[immutable] asset 55055dbfc7c6a83f60ba.woff18.8 KiB[emitted][immutable][from: src/my-font.woff](auxiliary name: main) asset 8f717b802eaab4d7fb94.woff214.5 KiB[emitted][immutable][from: src/my-font.woff2](auxiliary name: main)asset bundle.js73.7 KiB[emitted][minimized](name: main)1 related assetruntime modules1.82 KiB6 modulesorphan modules326 bytes[orphan]1 modulecacheable modules541 KiB(javascript)43.1 KiB(asset) javascript modules541 KiB modules by path ./node_modules/539 KiB modules by path ./node_modules/css-loader/dist/runtime/*.js2.38 KiB2 modules ./node_modules/lodash/lodash.js530 KiB[built][code generated] ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js6.67 KiB[built][code generated] modules by path ./src/1.98 KiB ./src/index.js +1 modules794 bytes[built][code generated] ./node_modules/css-loader/dist/cjs.js!./src/style.css1.21 KiB[built][code generated] asset modules126 bytes(javascript)43.1 KiB(asset) ./src/icon.png42 bytes(javascript)9.88 KiB(asset)[built][code generated] ./src/my-font.woff242 bytes(javascript)14.5 KiB(asset)[built][code generated] ./src/my-font.woff42 bytes(javascript)18.8 KiB(asset)[built][code generated]webpack5.4.0 compiled successfullyin2142 msOpen updist/index.html again and see if ourHello webpack text has changed to the new font. If all is well, you should see the changes.
Another useful asset that can be loaded is data, like JSON files, CSVs, TSVs, and XML. Support for JSON is actually built-in, similar to NodeJS, meaningimport Data from './data.json' will work by default. To import CSVs, TSVs, and XML you could use thecsv-loader andxml-loader. Let's handle loading all three:
npminstall --save-dev csv-loader xml-loaderwebpack.config.js
const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.css$/i, use: ['style-loader', 'css-loader'], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', }, { test: /\.(woff|woff2|eot|ttf|otf)$/i, type: 'asset/resource', },+ {+ test: /\.(csv|tsv)$/i,+ use: ['csv-loader'],+ },+ {+ test: /\.xml$/i,+ use: ['xml-loader'],+ }, ], },};Add some data files to your project:
project
webpack-demo |- package.json |- package-lock.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src+ |- data.xml+ |- data.csv |- my-font.woff |- my-font.woff2 |- icon.png |- style.css |- index.js |- /node_modulessrc/data.xml
<?xml version="1.0" encoding="UTF-8"?><note><to>Mary</to><from>John</from><heading>Reminder</heading><body>Call Cindy on Tuesday</body></note>src/data.csv
to,from,heading,bodyMary,John,Reminder,Call Cindy on TuesdayZoe,Bill,Reminder,Buy orange juiceAutumn,Lindsey,Letter,I miss youNow you canimport any one of those four types of data (JSON, CSV, TSV, XML) and theData variable you import, will contain parsed JSON for consumption:
src/index.js
import _ from 'lodash';import './style.css';import Icon from './icon.png';+import Data from './data.xml';+import Notes from './data.csv';function component() { const element = document.createElement('div'); // Lodash, now imported by this script element.innerHTML = _.join(['Hello', 'webpack'], ' '); element.classList.add('hello'); // Add the image to our existing div. const myIcon = new Image(); myIcon.src = Icon; element.appendChild(myIcon);+ console.log(Data);+ console.log(Notes);+ return element;}document.body.appendChild(component());Re-run thenpm run build command and opendist/index.html. If you look at the console in your developer tools, you should be able to see your imported data being logged to the console!
This can be especially helpful when implementing some sort of data visualization using a tool liked3. Instead of making an ajax request and parsing the data at runtime you can load it into your module during the build process so that the parsed data is ready to go as soon as the module hits the browser.
Only the default export of JSON modules can be used without warning.
// No warningimport datafrom'./data.json';// Warning shown, this is not allowed by the spec.import{ foo}from'./data.json';It's possible to import anytoml,yaml orjson5 files as a JSON module by using acustom parser instead of a specific webpack loader.
Let's say you have adata.toml, adata.yaml and adata.json5 files undersrc folder:
src/data.toml
title = "TOML Example"[owner]name = "Tom Preston-Werner"organization = "GitHub"bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."dob = 1979-05-27T07:32:00Zsrc/data.yaml
title: YAML Exampleowner:name: Tom Preston-Wernerorganization: GitHubbio:|- GitHub Cofounder & CEO Likes tater tots and beer.dob:1979-05-27T07:32:00.000Zsrc/data.json5
{ // comment title: 'JSON5 Example', owner: { name: 'Tom Preston-Werner', organization: 'GitHub', bio: 'GitHub Cofounder & CEO\n\Likes tater tots and beer.', dob: '1979-05-27T07:32:00.000Z', },}Installtoml,yamljs andjson5 packages first:
npminstall toml yamljs json5 --save-devAnd configure them in your webpack configuration:
webpack.config.js
const path = require('path');+const toml = require('toml');+const yaml = require('yamljs');+const json5 = require('json5');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.css$/i, use: ['style-loader', 'css-loader'], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', }, { test: /\.(woff|woff2|eot|ttf|otf)$/i, type: 'asset/resource', }, { test: /\.(csv|tsv)$/i, use: ['csv-loader'], }, { test: /\.xml$/i, use: ['xml-loader'], },+ {+ test: /\.toml$/i,+ type: 'json',+ parser: {+ parse: toml.parse,+ },+ },+ {+ test: /\.yaml$/i,+ type: 'json',+ parser: {+ parse: yaml.parse,+ },+ },+ {+ test: /\.json5$/i,+ type: 'json',+ parser: {+ parse: json5.parse,+ },+ }, ], },};src/index.js
import _ from 'lodash';import './style.css';import Icon from './icon.png';import Data from './data.xml';import Notes from './data.csv';+import toml from './data.toml';+import yaml from './data.yaml';+import json from './data.json5';++console.log(toml.title); // output `TOML Example`+console.log(toml.owner.name); // output `Tom Preston-Werner`++console.log(yaml.title); // output `YAML Example`+console.log(yaml.owner.name); // output `Tom Preston-Werner`++console.log(json.title); // output `JSON5 Example`+console.log(json.owner.name); // output `Tom Preston-Werner`function component() { const element = document.createElement('div'); // Lodash, now imported by this script element.innerHTML = _.join(['Hello', 'webpack'], ' '); element.classList.add('hello'); // Add the image to our existing div. const myIcon = new Image(); myIcon.src = Icon; element.appendChild(myIcon); console.log(Data); console.log(Notes); return element;}document.body.appendChild(component());Re-run thenpm run build command and opendist/index.html. You should be able to see your imported data being logged to the console!
The coolest part of everything mentioned above, is that loading assets this way allows you to group modules and assets in a more intuitive way. Instead of relying on a global/assets directory that contains everything, you can group assets with the code that uses them. For example, a structure like this can be useful:
- |- /assets+ |– /components+ | |– /my-component+ | | |– index.jsx+ | | |– index.css+ | | |– icon.svg+ | | |– img.pngThis setup makes your code a lot more portable as everything that is closely coupled now lives together. Let's say you want to use/my-component in another project, copy or move it into the/components directory over there. As long as you've installed anyexternal dependencies and yourconfiguration has the same loaders defined, you should be good to go.
However, let's say you're locked into your old ways or you have some assets that are shared between multiple components (views, templates, modules, etc.). It's still possible to store these assets in a base directory and even usealiasing to make them easier toimport.
For the next guides we won't be using all the different assets we've used in this guide, so let's do some cleanup so we're prepared for the next piece of the guidesOutput Management:
project
webpack-demo |- package.json |- package-lock.json |- webpack.config.js |- /dist |- bundle.js |- index.html |- /src- |- data.csv- |- data.json5- |- data.toml- |- data.xml- |- data.yaml- |- icon.png- |- my-font.woff- |- my-font.woff2- |- style.css |- index.js |- /node_moduleswebpack.config.js
const path = require('path');-const toml = require('toml');-const yaml = require('yamljs');-const json5 = require('json5');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), },- module: {- rules: [- {- test: /\.css$/i,- use: ['style-loader', 'css-loader'],- },- {- test: /\.(png|svg|jpg|jpeg|gif)$/i,- type: 'asset/resource',- },- {- test: /\.(woff|woff2|eot|ttf|otf)$/i,- type: 'asset/resource',- },- {- test: /\.(csv|tsv)$/i,- use: ['csv-loader'],- },- {- test: /\.xml$/i,- use: ['xml-loader'],- },- {- test: /\.toml$/i,- type: 'json',- parser: {- parse: toml.parse,- },- },- {- test: /\.yaml$/i,- type: 'json',- parser: {- parse: yaml.parse,- },- },- {- test: /\.json5$/i,- type: 'json',- parser: {- parse: json5.parse,- },- },- ],- },};src/index.js
import _ from 'lodash';-import './style.css';-import Icon from './icon.png';-import Data from './data.xml';-import Notes from './data.csv';-import toml from './data.toml';-import yaml from './data.yaml';-import json from './data.json5';--console.log(toml.title); // output `TOML Example`-console.log(toml.owner.name); // output `Tom Preston-Werner`--console.log(yaml.title); // output `YAML Example`-console.log(yaml.owner.name); // output `Tom Preston-Werner`--console.log(json.title); // output `JSON5 Example`-console.log(json.owner.name); // output `Tom Preston-Werner`function component() { const element = document.createElement('div');- // Lodash, now imported by this script element.innerHTML = _.join(['Hello', 'webpack'], ' ');- element.classList.add('hello');-- // Add the image to our existing div.- const myIcon = new Image();- myIcon.src = Icon;-- element.appendChild(myIcon);-- console.log(Data);- console.log(Notes); return element;}document.body.appendChild(component());And remove those dependencies we added before:
npm uninstall css-loader csv-loader json5 style-loader toml xml-loader yamljsLet's move on toOutput Management