A Complete Guide to Setting Up Dev Tools for Vanilla JavaScript Projects: Part 1
Learning the Essentials
📖 Part 1: Learning the essentials | 👈 you are here |
🤖 Part 2: Automating the process |
📘 Introduction
Managing and debugging large code bases can be challenging, especially when they lack proper structure or standards. Following a standard for writing code from the beginning, we can avoid spending hours on minor issues and reduce the communication gap among teams.
For example, imagine many unused variables in the code go unnoticed and create confusion among the team members.
Or everyone writes commits in their own way, making it difficult for others to understand what the commit is trying to say. This can lead to a big mess in large code bases and waste a huge amount of time.
Bad commit messages would look like:
git commit -m "update code"
git commit -m "updted the code"
git commit -m "the code had a bug so fixed it"
git commit -m "just changed a few lines of code to look better"
You can see there is no consistency in the commits. No one can understand what exactly the commit did to the code. Also, you can’t see if the commit brings an update, fixes a bug, or refactors it unless you read the whole commit. This consumes a lot of time.
It can be prevented if everyone on the team follows a similar structure for writing the commits.
Running checks through the code before it gets committed, removing any unused variables, or fixing lines of code that could cause errors in the future can save a lot of time.
Also, checking the commit messages to ensure they follow some conventions and standards can increase efficiency.
Good commit messages following some convention would look like:
git commit -m "feat: add filters under the search bar"
git commit -m "fix: resolve 404 error on /book route"
git commit -m "refactor: re-define share function to work as a global helper"
This is where linting can help us.
Linting is an automated process that checks the source code for potential errors related to the formatting and the logic of the code. Several lint tools can help achieve this, ensuring that code remains clean and error-free.
For simplicity, I decided to divide the blog into two parts:
Part 1: The basic need for linting and examples of a few lint tools.
Part 2: Automating the process and avoiding some general mistakes.
This is part 1 of the blog. In it, you will learn why linting is important and how it can be integrated into your vanilla JS project, using examples of some popular lint tools.
🌱 Motivation
Before we proceed, I want to mention my motivation for writing the blog. When I started my first vanilla JS project, Shelf Share, I was trying to implement these tools myself.
I did my research and found many resources but most of them were about setting up these tools in the environment of a framework, like React. A vanilla JS environment and a ReactJs environment are very different.
As I was setting up these tools for the first time, it was difficult for me to convert the setup to suit a vanilla JS environment. I looked at various blogs and tutorials on YouTube and had to run through many different resources before I finally came up with my setup.
Finally, having everything in one place, I thought it would be great if I shared this knowledge with everyone so that the next time someone tries to set up these tools in their vanilla JS project, they can find all the related information from a single source.
Let us continue to understand these tools.
🧰 Which tools should you use?
There is no specific set of tools that you must use, but here are some commonly used tools that can give you a great head start:
ESLint: A linting tool focused on JavaScript and its frameworks. It identifies and reports patterns, helping you follow standards and prevent bugs in your code.
Stylelint: A linting tool focused on CSS. It helps prevent errors and enforce conventions in your stylesheets.
Commitlint: A linting tool for commits in version control systems like Git. It enforces certain rules and formatting for commit messages.
Husky: While not a linting tool, Husky makes it easy to use Git hooks to automate tasks such as the linting process.
Prettier: A code formatter that ensures a consistent code style across the codebase. While some functionality overlaps with the code linters, it does not analyze the code to find and fix bugs but ensures that a consistent code style is followed.
Lint-staged: Again, not a linting tool, but it helps run scripts only on files in the git staging area, making the process faster.
These tools cover a wide range of areas where linting is beneficial and are generally sufficient for most projects. However, linting is not limited to these tools or the areas they cover. You can always explore additional tools if you require more robust linting.
The first part of the blog will cover the lint tools and Husky mentioned above. The last two are covered in the second part.
🔧 ESLint
ESLint is focused on JavaScript and its frameworks like React. It helps identify bugs in our code that can cause issues in the future or code that does not adhere to a particular set of rules.
For example, if there are any unused variables, ESLint will identify that line of code and notify the user.
Consider the code snippet below:
All these red lines are because of ESLint installed in my code editor. Most code editors support the ESLint extension, so you can also add that to yours.
The error lines indicate that these variables and functions were declared but never used. ESLint can identify this for us. Also, notice it highlighted the use of the name keyword, as it is already defined as a built-in global variable. ESLint can identify such potential bugs for us from the beginning.
Configuration
Installing the extension is not necessary; we can always run ESLint through npm scripts right before committing our code and can make it run only on staged files (more on this later), to speed up the process.
We install ESLint as a dev dependency in our project. You can find the step-by-step installation process in the official ESLint documentation.
After installation, a configuration file is created, usually named eslint.config.mjs
or .eslintrc.js
. You can check your root folder for the exact name of your file, as it may vary.
This configuration file contains all the environment variables and rules that ESLint will follow. Here, you can also define your own custom rules and have consistent code according to your rules throughout the team or your project.
Below is an example of a general configuration file setup:
import globals from "globals";
import pluginJs from "@eslint/js";
export default [
{languageOptions: { globals: globals.browser }},
pluginJs.configs.recommended,
];
The globals
package imports all the necessary environment variables (here, it imports variables for both the browser and Node environments).
The plugin:js
package imports the core JavaScript rules that ESLint follows by default.
You can learn about each value in detail via the official documentation. The syntax may change over time, so it’s important to refer to the official documentation for the latest configuration setup.
Apart from the basic setup that ESLint automatically generates upon installation, you can manually add your own rules to this file. Just declare a rules
property in the file and mention your rules there.
In the example below, a custom rule is declared which warns upon using console
statements:
import globals from 'globals';
import pluginJs from '@eslint/js';
export default [
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
{
rules: { 'no-console': 'warn' },
},
];
Likewise, you can add as many custom rules as you want and/or turn the core rules on or off as per your choice.
Setting up eslint with npm scripts
To execute ESLint, you can either globally install ESLint and then execute it by passing the files to the command directly or set up npm scripts to make the process faster.
Using npm scripts is a better option as you can further combine it with other linters. We will understand this a bit later in the blog.
{
"scripts": {
"lint": "eslint --fix *.js"
}
}
The --fix
option will automatically fix the problems. Check out the official documentation to find out about various options you can use according to your needs.
Now running the npm run lint
command will execute ESLint on all the files ending with the .js
extension. You can modify this as per your requirements.
Below is an output of ESLint:
🔧 Stylelint
Similar to ESLint, Stylelint is also a lint tool that identifies the parts of code with potential bugs and reports them. But this one is for all the CSS files rather than Javascript files. It also helps to enforce any conventions that you want to be followed in your stylesheets.
For example, if you do not want to use px as a unit in the code, you can define that in your custom rules, or if there is any invalid hex code then Stylelint will report that.
Configuration
Just like ESLint, Stylelint has a configuration file that is created during the installation process. You can refer to the official documentation of Stylelint for detailed steps on installation. The configuration file is usually named .stylelintrc.json
.
This is how the file might look initially:
{ "extends": ["stylelint-config-standard"] }
It considers all the standard rules while linting, but you can modify it as per your needs.
For example, you can add support for CSS-like languages such as SASS and add plugins to support conventions like the BEM naming convention.
Note: You may need to install some packages beforehand. Always check the installation steps from the official documentation of your plugin or extension.
Below is a snippet of extending support for SASS in the configuration file and adding a plugin for the BEM convention:
{
"extends": ["stylelint-config-standard-scss"],
"plugins": ["stylelint-selector-bem-pattern"]
}
If you want Stylelint to work for both SCSS and CSS, you can include both the standard
and standard-scss
configurations in the extends
array.
There are many plugins listed on npm; you can check there for the proper setup of your file accordingly.
Below is an example of setting up rules for the BEM selector:
{
"extends": ["stylelint-config-standard-scss"],
"plugins": ["stylelint-selector-bem-pattern"],
"rules": {
"plugin/selector-bem-pattern": {
"preset": "bem",
"componentName": "[a-z]+(?:-[a-z]+)*",
"componentSelectors": {
"initial": "^\\.{componentName}(?:-[a-z]+)*(?:--[a-z]+)?$"
},
"utilitySelectors": "^\\.util-[a-z]+$"
},
"selector-class-pattern": null
}
}
You can search and understand the setup of your plugins from their official documentation.
You can also set up your own custom rules in this configuration file. For example, you can define a set of legal units:
{
"extends": "stylelint-config-standard-scss",
"plugins": ["stylelint-selector-bem-pattern"],
"rules": {
"plugin/selector-bem-pattern": {
"preset": "bem",
"componentName": "[a-z]+(?:-[a-z]+)*",
"componentSelectors": {
"initial": "^\\.{componentName}(?:-[a-z]+)*(?:--[a-z]+)?$"
},
"utilitySelectors": "^\\.util-[a-z]+$"
},
"selector-class-pattern": null,
"unit-allowed-list": ["%", "deg", "px", "rem", "ms"]
}
}
The last line defines a custom rule that lists all the legal units. If any unit apart from these is used, Stylelint will report it like below:
Setting up stylelint with npm scripts
Although, just like ESLint, we can run Stylelint directly from the command line on our desired files, it is better to use npm scripts to enhance the linting process.
Let’s combine Stylelint with the previous npm script for ESLint and see how useful it is to use npm scripts to execute lint tools.
Below is the updated script where both ESLint and Stylelint are executed. To ensure they run independently of each other, the npm-run-all package is used:
"scripts": {
"lint:js": "eslint --fix *.js",
"lint:css": "stylelint --fix *.css",
"lint": "npm-run-all --continue-on-error lint:js lint:css"
}
The --fix
option is used to automatically fix as many errors as possible.
As you can see, by running just one command, we can run two different lint tools on different file types. As you proceed further with this blog, the whole lint process will get even more automated with the help of npm scripts and other tools mentioned at the beginning.
🔧 Commitlint and Husky
Commitlint
So far, we have covered the JS and CSS-related files for linting. Now we will go a step further and acknowledge version control in the linting process as well. Version control tools like git allow us to commit every change we make and keep a log of our changes so we can revert whenever needed.
These commits require a commit message. When these commit messages are not precise or consistent, it can get difficult to understand the changes made, both for an individual and a team. To keep these commits consistent and follow proper standards, global or custom, we use another lint tool, commitlint.
It checks every commit message and ensures that they adhere to the global commit conventions and/or to any custom rules that you define.
You can refer to the official documentation for the installation steps. After installation, you will have a configuration file for commitlint. You have to create it manually but the official documentation can assist with that. The file will be named commitlint.config.js
, you might need to change the extension to .mjs
or .cjs
depending on your project.
After you create the configuration file following the official documentation, you will have commitlint configured to adhere to the rules defined by the conventional commits.
Below is a snapshot of the configuration file.
export default { extends: ['@commitlint/config-conventional'] };
You can add custom rules to this configuration. For example:
export default {
extends: ['@commitlint/config-conventional'],
rules: { 'subject-max-length': [2, 'always', 50] },
};
Here, the rule specifies the maximum length the commit’s subject can have.
Husky
After setting up the commitlint, now we need to utilize it. Unlike previous lint tools that can be used independently, commitlint needs to use git hooks to work as intended.
Git hooks are scripts that can be triggered when certain important events occur in the Git life cycle. Events such as committing, merging, pushing, etc. To know more about git hooks, refer to the official documentation.
Husky is a tool that simplifies the usage of these git hooks and helps us easily set up custom scripts by providing us with a unified interface.
The setup is quick and easy, refer to the official documentation for the latest installation steps.
Once done, we only need to worry about two git hooks, commit-msg
and pre-commit
. These files are present in the root of the husky folder when it is created. You may need to manually create the commit-msg
hook but that is shown in the official documentation of commitlint.
For the commitlint to work, we need to set up the commit-msg
hook.
The commit-msg
hook is a git hook that runs its scripts every time a commit is created. It is used to enforce rules on commit messages, such as formatting and other guidelines. This is achieved by adding scripts of commitlint to this hook.
Set up commitlint with npm scripts
We set up commitlint with the npm script so that the commit-msg
hook can execute the script whenever a commit message is created.
In the package.json
, we write a script that will execute commitlint.
{
"scripts": {
"commitlint": "commitlint --edit"
}
}
The --edit
option tells commitlint to open and read the file that contains the commit message. Without this option, it won’t be able to open the commit message’s file and the execution will get interrupted.
In the commit-msg
hook, add the following script to utilize the npm script above:
npm run commitlint ${1}
The commit-msg
hook has access to the file containing the commit message. When a commit message is created, a temporary file is generated with the exact message. The ${1}
argument contains the path to this file.
We provide this path to the commitlint script via the commit-msg
hook. This way, Commitlint can read and analyze the commit message according to the setup.
Below is a snapshot of commitlint reporting a commit message that does not follow the conventions.
The above commit doesn’t adhere to the format defined by conventional commits, hence the commit fails.
🏁 Concluding Part 1
Until now, we have covered what lint tools are, why we need them, and how we can set them up in our vanilla JS project. I hope I was able to help you understand these concepts well.
After understanding the part 1 of the blog, you should be able to set up ESLint
, Stylelint
, commitlint
and husky
. But we have only covered how to use these tools individually, with a manual effort, via npm scripts
and command-line interface.
2️⃣ Check out Part 2
Part 2 of the blog will help you understand how all of these processes can be automated and how to set up these tools in such a way that they only run on the files present in the git staging area.
It will also cover some of the mistakes that I made while learning these concepts. It will give you a great headstart to set up your project with these tools so do not forget to check that out.
Here’s the link to the second part: A Complete Guide to Setting Up Dev Tools for Vanilla JavaScript Projects: Part 2
🚀 Check out the project
Here’s the project that I built with no frameworks, pure JS. Do check it out and let me know any feedback, I would love to hear your thoughts!
📚 Shelf Share: Search, Share, and Bookmark Books
Here is the GitHub repository for this project, if you are interested in the source code. Please do star it if you like it!
⭐ Shelf Share's Git Hub Repository