Creating a Multi-Page JavaScript Boilerplate with Webpack 5, Babel, Prettier and Github-Actions

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. Note index.bundle.js is automatically injected inside the template. This is due to inject flag.
open dist/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/ folderFor 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 /distnode_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 command npm 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

 

Leave a Reply

Your email address will not be published. Required fields are marked *

No Related Post

X