Setting up boilerplate for JavaScript using Webpack seems daunting at first unless I set it up step by step and found that it wasn’t as difficult as I thought it to be.
If you are looking for setting up a multi-page JavaScript project using Webpack 5, you have come to the right place.
In this tutorial, we are going to set up a multi-page JavaScript boilerplate from scratch. Each step will be explained briefly to provide context for why we are taking these actions. I have not used definitions from package documentation but tried to briefly explain in the simplest manner how each package is used in the project.
If you are not interested in following article step by step, that’s completely ok. I have attached link to the boilerplate codebase at the end of article. Feel free to leave comment if you encounter any issue with its setup.
Here is the overview of what you are going to cover in this tutorial
1. Setup basic project structure
2. Setup Webpack 5
- Setup HTML templates using Webpack Plugins
- Setup CSS, Babel using Webpack Loaders
- Setup Assets (Images folder)
- Setup hot reload in dev environment
- Setup Multi-Page using Webpack
- Setup ESLint and Prettier
3. Setup Lint-Staged and Husky
4. Setup basic Github-Actions
Let’s start with the setup ->
Open the terminal app, create a directory for your project and initialize a new Node.js project. Let’s name it javascript-boilerplate.
mkdir javascript-boilerplate
cd javascript-boilerplate
npm init -y
You will see package.json file created in the project root directly. This file is used to maintain project metadata along with project dependencies.
Create src, images and fonts folder, and index.js and index.html file.
mkdir -p src src/assets/fonts src/assets/images
cd src
touch index.js index.html
Add index.html template.
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<div id="root">
<h1>This is a JS boilerplate</h1>
</div>
</body>
</html>
Now your folder structure should look something like this
Setup Webpack 5
Install webpack and webpack-cli
npm install --save-dev webpack@5 webpack-cli@5
webpack — It is a module bundler for JavaScript applications. It is primarily responsible for taking all javascript files, CSS files, image files, and other assets and combining them into single or multiple output files.
webpack-cli — It is a command-line tool for configuring Webpack.
Next, create webpack.config.js
file in the project root directory.
touch webpack.config.js
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
};
Add entry and output ->
Entry — An entry point is a JavaScript file (one or multiple) where webpack starts its bundling process. It traverse the project’s dependency graph from these entry points. We defined single entry path for root /index
Output — It indicates that the module defined in the entry object will be converted to a bundle and the result will be copied into /dist folder. We are appending bundle.js with file name. Notice clean flag. It is used to clear the /dist folder before creating a new build.
We are ready to generate a build. we will use webpack –mode development command to generate a build. The Default mode is production in Webpack therefore we explicitly defined development mode through command.
Open package.json file and add an entry in the script object. We can directly
"scripts": {
"dev": "webpack --mode development"
},
Run npm run dev
and you should see the dist/ folder inside the project root.
Note: we can directly use webpack –mode development but we want to simplify the command therefore we added it in script object.
These are the most basic configurations which are enough to run the boilerplate.
Setup HTML templates using Webpack Plugins
You might have noticed that there is only one file named index.bundle.js in /dist folder even though we did create an index.html. However, Webpack didn’t copy the index.html in the /dist folder.
This is where we need html-webpack-plugin
html-webpack-plugin — It is used to either generate html template or use our own template.
Webpack Plugins — These are JavaScript modules that enhance the functionality of Webpack. They are used to perform tasks such as defining HTML templates and optimizing and minifying codebase.
Install html-webpack-plugin plugin
npm install --save-dev html-webpack-plugin
Add html-webpack-plugin configurations in webpack.config.js
/**/
const HtmlWebpackPlugin = require('html-webpack-plugin');
{
entry: {
/**/
},
output: {
/**/
},
plugins: [
new HtmlWebpackPlugin({
template: `./src/index.html`,
filename: `index.html`,
inject: true,
}),
]
}
template — It defines the path to the template, the plugin is going to copy into /dist
folder
filename — Name of template in /dist
folder
inject — This flag is used to inject all assets into the given template. By default its value is true so you can skip this as well but I added it to highlight its behaviour
Run
npm run dev
command.
You will see index.html file is created inside/dist
folder. Noteindex.bundle.js
is automatically injected inside the template. This is due toinject
flag.
opendist/index.html
file in a browser and you should see html page rendered.
Setup CSS, Babel using Webpack Loaders
We will use loaders to setup Babel and SCSS in our project
Loaders allow us to preprocess files before they are added to the bundle. This allows to transform files such as JavaScript, CSS and images into modules and integrate into application
Install SCSS
Create file index.scss inside src/ folder
cd src
touch index.scss
body {
background: #e7a462;
}
If you run npm run dev, you will see that CSS styles are not applied.
To handle .scss files, we need to install a couple of libraries
npm install --save-dev style-loader css-loader sass-loader node-sass
style-loader — Inject CSS styles into the DOM
css-loader — Manage CSS dependencies in the project. It interprets @import and resolves them.
sass-loader — Compile SCSS and Sass files into css.
node-sass — This is a dependency and is used by sass-loader
Next, We need to add module object inside webpack configs. This is used to specify how modules should processed during bundling process. It allows to define rules and loader for handling different file types.
{
/*...*/
module: {
rules: [
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
}
]
}
}
Open index.js file and import index.scss file
import './index.scss';
Run npm run dev
to generate a new build. Open dist/index.html file in the browser and you should see the background CSS applied.

You will notice there is no separate file for CSS in dist/ folder. For this, we are going to install packages mini-css-extract-plugin and css-minimizer-webpack-plugin.
npm install --save-dev mini-css-extract-plugin css-minimizer-webpack-plugin
mini-css-extract-plugin — Extract CSS in a separate file inside /dist older
css-minimizer-webpack-plugin — Optimize and minify css
Add configs for these plugins in webpack.config.js
{
/*...*/
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
{
/*...*/
plugins: [
new HtmlWebpackPlugin({
template: `./src/index.html`,
filename: `index.html`,
inject: true,
}),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
module: {
rules: [
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
}
]
},
optimization: {
minimize: true,
minimizer: [new CssMinimizerPlugin()],
},
}
}
We made the following modifications in the webpack.config.js file
1. Import plugins
2. Initialize mini-css-extract-plugin in plugins array
3. Update rules key for scss. Replace style-loader with MiniCssExtractPlugin.loader
4. Add optimizationkey for setting up CSS minimization plugin
Run
npm run dev
and you should see a new minified index.css file in dist/ folder
We have successfully setup Scss.
Install Babel
We want to use the latest JavaScript features cross-browser. For this, we need to setup babel
babel — It takes Javascript code and converts it to vanilla javascript so that it can run on all browsers and we can use modern features across all browsers.
Install babel packages
npm install --save-dev @babel/core @babel/preset-env babel-loader
Here’s how these libraries work together:
babel-loader — It is configured in Webpack configurations to process JavaScript files. When Webpack encounters a JavaScript file, babel-loader is responsible for passing it to babel for transpilation
babel/core — It is the core Babel library. It’s responsible for actual parsing, transformation and generation of the vanilla javascript
babel-env — This tool helps to write modern javascript code and make it runnable in all browsers. in babel-env configs, we can tell which version of JavaScript we want to write code and it figures out how to translate it to vanilla JavaScript.
Create a new file .babelrc for babel configurations
touch .babelrc
Add these configs in .babelrc file
{
"presets": [
"@babel/preset-env"
]
}
Next, add babel configurations in webpack.config.js
{
/*...*/
module: {
rules: [
/*...*/
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
],
},
}
We are instructing Webpack to generate older javascript code for all .js files using babel-loader but exclude node_modules folder.
We are done!! We setup Babel and we should be good to write the latest javascript in your app. You can add the latest code in the index.js file and it should work.
Setup Assets
Next, we need to setup assets. Let’s add an image inside assets/images folder as it will be used inside index.js file. For this, we need to enable handling images in the source code so that we can use import statement for images
Add configs in webpack.config.js file
{
/*...*/
output: {
/*...*/
assetModuleFilename: 'images/[name][ext]',
},
module: {
rules: [
/*...*/
{
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/i,
type: 'asset/resource',
},
]
},
}
We are done setting up images.
Copy an image file under assets/images folder and use it in index.js file using import statement.
import bg from './assets/images/[IMAGE_NAME]';
Run build npm run dev. You should see image file inside assets/images folder is copied into dist/images folder.
Setup Hot Reload
We need to run npm run dev
every time we need a change. We want webpack to rebuild the project whenever there is a change. For this, we are going to install webpack-dev-server
npm install --save-dev webpack-dev-server
In webpack.config.js, add configs for webpack-dev-server
{
/*...*/
output: {
/*...*/
},
devServer: {
watchFiles: ['src/*.html'],
static: path.resolve(__dirname, './dist'),
hot: true,
open: true,
},
watchOptions: {
poll: 1000,
ignored: '/node_modules/',
},
}
Update package.json file, and modify dev key
{
"dev": "webpack serve --mode=development & webpack --mode development"
}
Hurrah! We are good now.
Run npm run dev, a page should open in the browser. From this point onwards whenever you make a change in index.css, index.js or index.html file, it should be reflected in the browser.
Setup Multi-Page using Webpack
HTML, CSS and JS files are working now but wait! We handle single file. How to handle multiple files??
Create a signup folder structure.
cd src
mkdir signup
cd signup
touch signup.html signup.scss signup.js
Copy the content in signup.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<div id="root">
<h1>This is signup page</h1>
</div>
</body>
</html>
Next, we need to add configurations in webpack.config.js to handle multiple HTML files.
/*...*/
const pages = ['signup'];
const mainHtmlPage = [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index'],
}),
];
const htmlPluginEntries = mainHtmlPage.concat(
pages.map(
(page) =>
new HtmlWebpackPlugin({
template: `./src/${page}/${page}.html`,
filename: `${page}.html`,
chunks: [page],
}),
),
);
module.exports {
/*...*/
entry: {
index: './src/index.js',
signup: './src/signup/signup.js', // signup entry for signup module
},
devServer: {
watchFiles: ['src/*.html', 'src/*/*.html'], // add new entry to handle signup html files
static: path.resolve(__dirname, './dist'),
hot: true,
open: true,
},
plugins: htmlPluginEntries.concat([
new MiniCssExtractPlugin({
filename: '[name].css',
}),
]),
}
We add the following configs for multi-page setup
1- Defined pages array and added the signup folder name
2– We need to initialize each template usingHtmlWebpackPlugin. To achieve this, we’ll loop on pages array and define template configs for each template and store in htmlPluginEntries
3- Update plugins entry and use htmlPluginEntries with the rest of the plugins’ configs
4- Update entry object. Add new entry for signup module in entry object
5- Update devServer object. In watchFiles key, add patterns to watch for changes under src/signup folder.
Restart the server and run npm run dev
Navigate to [LOCALHOST_URL]/signup.html and you will see that the signup page is rendered.
To add a new page, In webpack.config.js, add the folder name in pages array and you are good to go.
In this project, I am using feature-based pattern for organizing code therefore all assets related to signup page are kept in signup folder.
ESLint and Prettier
Next, we want to enforce consistent best practices and formatting throughout the project. For this, we are going to setup
ESLint and Prettier in our project.
Setup ESLint
npm install --save-dev eslint-webpack-plugin eslint @babel/eslint-parser
eslint — It helps to catch error and fix errors. It is the actual linter. We will configure ESlint using .eslintrc file in project directory
eslint-wepack-plugin — It is a webpack plugin for running ESLint during webpack build process
babel/eslint-parse — It is a parser for ESLint that allows ESLint to lint JavaScript code that uses features from recent version. It helps ESLint when babel is involved in transpilation process.
Initialize eslint-webpack-plugin in webpack.config.js
const ESLintPlugin = require('eslint-webpack-plugin');
{
/*...*/
plugins: htmlPluginEntries.concat([
new MiniCssExtractPlugin({
filename: '[name].css'})
],
new ESLintPlugin({
failOnError: false,
failOnWarning: false,
emitWarning: false,
emitError: false,
}),
),
}
Create .eslintrc file for defining ESlint configs in project directory
touch .eslintrc
Add rules in the .eslintrc file
{
"parser": "@babel/eslint-parser",
"extends": ["eslint:recommended"],
"rules": {
"quotes": [1, "single"]
},
"env": {
"browser": true,
"node": true
}
}
Add .eslintignore file
touch .eslintignore
We don’t want to run ESLint on /dist, node_modules and webpack.config.js file
Add this in .eslintignore file
/dist
/node_modules
webpack.config.js
Update package.json file, add lint and lint:fix in script object to run Eslint
"script: {
"dev": "webpack serve --mode=development & webpack --mode development",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}
We are done!
Run linter
npm run lint
to check ESLint errors or warnings in the code base.
Use commandnpm run lint:fix
to fix ESLint errors that can be fixed automatically.
Setup Prettier:
Prettier is used to format your code for you so that developers can get rid of the task of correcting formatting all the time.
Install prettier
npm install --save-dev prettier
Create .prettierrc file
touch .prettierrc
Add config file for prettier in project directory
{
"singleQuote": true,
"bracketSpacing": true,
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"trailingComma": "all"
}
Add .prettierignore file
touch .prettierignore
Add folders and files you don’t want prettier to scan for formatting
./dist
.babelrc
.eslintrc
README.md
Update package.json file, add prettier and prettier:fix command in script object to run prettier using npm
{
"script": {
/*...*/
"prettier": "prettier . --check",
"prettier:fix": "prettier . --write"
}
}
Run prettier npm run prettier and it will check formatting issues in the project files
Run command npm run prettier:fix to fix all the formatting issues
We are done with setting up JavaScript Multi-Page boilerplate. Next, we’ll configure husky and github-actions.
Setup lint-staged and Husky
Rre-Requisite: Make sure git is setup in your project. Run command
git init to initiate it.
It is a tedious process to run ESlint and prettier every time before committing to git. We want to run linter before committing files to git. Here comes lint-staged and Husky.
Install lint-staged
npm install --save-dev lint-staged
lint-staged — package is used to run linter on only staged git files
Update package.json file, add lint-staged configs to run linter on staged git files
{
/**/
"lint-staged": {
"*.{js}": [
"eslint --fix",
"prettier . --check"
]
}
}
We are going to use lint-staged in husky.
husky — It is used to improve git commits. It makes it easy to setup and manage git hooks in the project. Git hooks are scripts that are automatically executed before or after specific Git events, such as committing or pushing code. (https://github.com/typicode/husky)
Next, Install husky
npm install --save-dev husky
Let’s configure pre-commit hook that will run linter before committing to git
Update package.json file, add prepare entry in scripts object
{
"scripts": {
/*...*/
"prepare": "husky install"
},
}
Initialize husky by running the command in the project directory
npm run prepare
setup pre-commit hook
npx husky add .husky/pre-commit
git add .husky/pre-commit
This will create .husky/.pre-commit file
Open .husky/.pre-commit file and replace file content with this.
#!/usr/bin/env sh
. "$(dirname - "$0")/_/husky.sh"
npx lint-staged
We are done! On committing to git, ESLint and prettier will run. The commit should fail if ESLint or prettier error occurs.
Make a commit and you should see lint commands running in the console
Setup Github-Actions:
Last but not least let’s setup github actions
If a user comments out the pre-commit hook, then code with ESLint or prettier errors can be committed to git.
We want to make sure to push error-free code.
Github-action is a tool to automate developer workflows and CI/CD processes. It is provided by Git Hub.
Navigate to the project directory and create .github/workflows directory.
mkdir -p .github/workflows
cd .github/workflows
In workflows directory, create a file named code-quality.yml
touch code-quality.yml
Add yaml script in code-quality.yml file to run ESLint and prettier each time we create pull-request on github
name: ESLint and Prettier
run-name: Running ESLint and Prettier
on:
push: # Trigger the workflow on push events for any branch
pull_request:
types: [synchronize, opened, reopen]
jobs:
code_quality:
name: ESLint and Prettier
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install node.js
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: npm install
- name: Run ESLint
run: npm run lint
- name: Run Prettier
run: npm run prettier
Following is a brief detail of the script:
1- name and run-name: Specify the name of the github action
2- on: We want to run Linter whenever code is pushed or PR is created on github
3- jobs: This key holds the actual script we are going to run
On github, In Actions tab, you should see GitHub action running whenever you push or create PR.

We are done! We have successfully setup JavaScript multipage boilerplate. We saw that webpack is a powerful bundler. We can get maximum advantage by using plugins and loaders. It seems like a beast initially but once we get our hands dirty, we can truly see how useful this tool is.
Hope this article was insightful and feel free to drop your feedback!!
Find below the link to the boilerplate code we setup in this article
https://github.com/sehrishnaveed/javascript-boilerplate

Senior Software Engineer | ReactJS, Redux and JavaScript | Blogger
Hey, I am Sehr! 👋
I am a Full-stack Web Software Engineer. My tech stack is ReactJS, Redux, JavaScript, PHP and Database Designing. I have extensive experience in all phases of software development life cycle.
I’m relatively new to the world of blogging 🙂, and I am eager to outgrow the `newbie` status soon 😄. My focus on mastercodding.com will be to share insights on web development, JavaScript and other tech related topics. I hope my experiences and writing will help others! 🌟