Good day, friends!
I present to you the translation of the article "Webpack: A gentle introduction" by Tyler McGinnis.
Before exploring a new technology, ask yourself two questions:
- Why is this tool needed?
- What tasks does it perform?
If you cannot answer these questions, you may not need the technology you are studying. Let's try to answer these questions in relation to Webpack.
Why do you need a webpack?
Webpack is a module builder. It parses the modules of the application, creates a dependency graph, then assembles the modules in the correct order into one or more bundles, which can be referenced by the "index.html" file.
App.js -> |
Dashboard.js -> | Bundler | -> bundle.js
About.js -> |
What problems does webpack solve?
Usually, when creating a JavaScript application, the code is split into several parts (modules). Then, in the "index.html" file, you must specify a link to each script.
<body>
...
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="libs/react.min.js"></script>
<script src='src/admin.js'></script>
<script src='src/dashboard.js'></script>
<script src='src/api.js'></script>
<script src='src/auth.js'></script>
<script src='src/rickastley.js'></script>
</body>
This is not only tedious but also error prone. It is important not only not to forget about any script, but also to arrange them in the correct order. If you load a script that depends on React before loading React itself, your application will break. Webpack solves these problems. You don't have to worry about including all scripts in sequence.
<body>
...
<script src='dist/bundle.js'></script>
</body>
As we will soon learn, collecting modules is just one aspect of how a webpack works. If necessary, you can force the webpack to do some module transformations before adding them to the bundle. For example converting SASS / LESS to regular CSS, or modern JavaScript to ES5 for older browsers.
Installing a webpack
After initializing the project with npm, for the webpack to work, you need to install two packages -
webpack
and webpack-cli
.
npm i webpack webpack-cli -D
webpack.config.js
After installing these packages, the webpack needs to be configured. To do this, a file is created
webpack.config.js
that exports the object. This object contains webpack settings.
module.exports = {}
The main task of a webpack is to analyze modules, transform them optionally and intelligently combine them into one or more bundles, so the webpack needs to know three things:
- Application entry point
- Conversions to be performed
- Where to place the generated bundle
Point of entry
No matter how many modules an application contains, there is always a single entry point. This module includes the rest. Typically, this file is index.js. It might look like this:
index.js
imports about.js
imports dashboard.js
imports graph.js
imports auth.js
imports api.js
If we tell the webpack the path to this file, it uses it to create the application's dependency graph. To do this, you need to add a property
entry
to the webpack settings with the value of the path to the main file:
module.exports = {
entry: './app/index.js'
}
Conversion with loaders
After adding the entry point, you need to inform the webpack about the transformations that need to be performed before generating the bundle. For this, loaders are used.
By default, when generating operator-based dependency graphs, the
import / require()
webpack is only able to process JavaScript and JSON files.
import auth from './api/auth' //
import config from './utils/config.json' //
import './styles.css' // ️
import logo from './assets/logo.svg' // ️
It's unlikely that you dare to limit yourself to JS and JSON files in your application, most likely you will also need styles, SVGs, images, etc. That's where loaders come in. The main task of loaders, as their name suggests, is to provide a webpack with the ability to work with more than just JS and JSON files.
The first step is to install the loader. Since we want to load SVG, use npm to install svg-loader.
npm i svg-inline-loader -D
Next, add it to the webpack settings. All loaders are included in an array of objects
module.rules
:
module.exports = {
entry: './app/index.js',
module: {
rules: []
}
}
Loader information consists of two parts. The first is the type of files being processed (
.svg
in our case). The second is the loader used to process this type of file ( svg-inline-loader
in our case).
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' }
]
}
}
We can now import SVG files. But what about our CSS files? For styles used
css-loader
.
npm i css-loader -D
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: 'css-loader' }
]
}
}
We can now import both SVG and CSS files. However, in order for our styles to work correctly, we need to add another loader. Thanks to
css-loader
us, we can import CSS files. But that doesn't mean they will be included in the DOM. We want to not only import such files, but also place them in a tag <style>
so that they apply to DOM elements. For this you need style-loader
.
npm i style-loader -D
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }
]
}
}
Note that since two loaders are used to process CSS files, the property value
use
is an array. Also pay attention to the order of the loaders, first style-loader
, then css-loader
. It is important. The webpack will apply them in reverse order. It is used first css-loader
for imports './styles.css'
, then style-loader
for injecting styles into the DOM.
Loaders can be used not only to import files, but also to convert them. The most popular is converting next generation JavaScript to modern JavaScript using Babel. It is used for this
babel-loader
.
npm i babel-loader -D
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
{ test: /\.(js)$/, use: 'babel-loader' }
]
}
}
There are loaders for almost any type of file.
Exit point
Now the webpack knows about the entry point and loaders. The next step is to specify the directory for the bundle. To do this, you need to add a property
output
to the webpack settings.
const path = require('path')
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
{ test: /\.(js)$/, use: 'babel-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
}
}
The whole process looks like this:
- The webpack receives an entry point located at
./app/index.js
- It parses statements
import / require
and creates a dependency graph - The webpack starts building the bundle by converting the code using the appropriate loaders
- He collects a bundle and places it in
dist/index_bundle.js
Plugins
We have seen how to use loaders to process individual files before or during the generation of a bundle. Unlike loaders, plugins allow you to perform tasks after building a bundle. These tasks can concern both the bundle itself and other code. You can think of plugins as more powerful, less limited loaders.
Let's take an example.
HtmlWebpackPlugin
The main task of the webpack is to generate a bundle that can be referenced in
index.html
.
HtmlWebpackPlugin
creates index.html
in the directory with the bundle and automatically adds a link to the bundle in it.
We named the bundle
index_bundle.js
and placed it in dist
. HtmlWebpackPlugin
will create a new file index.html
in the directory dist
and add a link to the bundle in it - <script src='index_bundle.js'></script>
. Great, isn't it? Since it is index.html
generated HtmlWebpackPlugin
, even if we change the exit point or the name of the bundle, it HtmlWebpackPlugin
will receive this information and change the content index.html
.
How do we use this plugin? As usual, you need to install it first.
npm i html-webpack-plugin -D
Next, add the property to the webpack settings
plugins
.
const path = require('path')
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
{ test: /\.(js)$/, use: 'babel-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
plugins: []
}
We create an instance
HtmlWebpackPlugin
in an array plugins
.
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
{ test: /\.(js)$/, use: 'babel-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin()
]
}
EnvironmentPlugin
If you are using React, you will want to set this
process.env.NODE_ENV
to a value production
before deploying the application. This will allow React to build in production by removing development tools such as warnings. The webpack allows you to do this through a plugin EnvironmentPlugin
. It is part of the webpack, so you don't need to install it.
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
{ test: /\.(js)$/, use: 'babel-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin(),
new webpack.EnvironmentPlugin({
'NODE_ENV': 'production'
})
]
}
Now, anywhere in our application, we can set the production mode with
process.env.NODE_ENV
.
HtmlWebpackPlugin
and EnvironmentPlugin
is just a small part of the webpack plugin system.
Mode (mode)
In the process of preparing an application for production, there are several steps to take. We've just covered one of them - setting
process.env.NODE_ENV
to value production
. Another action is to minify the code and remove comments to reduce the size of the bundle.
There are special plugins for solving these tasks, but there is an easier way. In the webpack settings, it can be set
mode
to development
or production
depending on the development environment.
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './app/index.js',
module: {
rules: [
{ test: /\.svg$/, use: 'svg-inline-loader' },
{ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },
{ test: /\.(js)$/, use: 'babel-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin()
],
mode: 'production'
}
Please note that we have removed
EnvironmentPlugin
. The fact is that after setting mode
the value, the production
webpack automatically assigns the process.env.NODE_ENV
value production
. It also minifies the code and removes warnings.
Launching a webpack
At the moment we know how the webpack works and how to configure it, it remains to run it.
We have a file
package.json
that we can create script
to run webpack
.
"scripts": {
"build": "webpack"
}
Now, when you run the command
npm run build
in the terminal, a webpack will be launched, which will create an optimized bundle index_bundle.js
and place it in dist
.
Development and production modes
All in all, we are done with the webpack. Finally, let's take a look at how to switch between modes.
When building for production, we want to optimize everything as much as possible. In the case of development mode, the opposite is true.
To switch between modes, you need to create two scripts in
package.json
.
npm run build
will build the production bundle.
npm run start
will start the development server and watch for file changes.
If you remember, we set this
mode
to a value production
in the webpack settings. However, we don't need it now. We want the environment variable to have an appropriate value depending on the command being executed. Let's change the script build
in package.json
.
"scripts": {
"build": "NODE_ENV='production' webpack",
}
If you have Windows, the command would be:
"SET NODE_ENV='production' && webpack"
.
Now in the webpack settings we can change the value
mode
depending on process.env.NODE_ENV
.
...
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development'
}
To build a ready-made bundle for our application, we simply run it
npm run build
in the terminal. The directory dist
files are created index.html
and index_bunlde.js
.
Development server
When it comes to developing an application, speed is of the essence. We don't want to restart the webpack and wait for a new build with every change. This is where the package comes in handy
webpack-dev-server
.
As the name suggests, this is a webpack server for development. Instead of creating a directory
dist
, it stores data in memory and processes it on a local server. What's more, it supports live reboot. This means that any change will webpack-dev-server
rebuild the files and restart the browser.
Install the package.
npm i webpack-dev-server -D
All that's left to do is add the script
start
to package.json
.
"scripts": {
"build": "NODE_ENV='production' webpack",
"start": "webpack-dev-server"
}
We now have two commands: one to start the development server, and the other to build the finished bundle.
I hope this article was helpful to you. Thank you for attention.