A Comprehensive Look: Pros and Cons of Development Workflow in Frontend Engineering
I tried setting up a frontend project with ESLint, Prettier, Husky, Lint Staged, and Commit Lint in my latest employment.
Technology Stack of the project
NextJS
Typescript
TailwindCSS
ESLint, Prettier, Husky, Lint-staged
Problem
In my last employment, I worked as a Team Leader for a comprehensive project. There were rules set by the Tech Lead that we need to maintain:
Import orders sorted alphabetically and organized according to contexts. Example: React related imports, Third party imports, internal imports, etc.
Good commit message conventions.
Good formatting of code.
Some problems regarding these were:
Unused import statements.
Unused variables.
Unremoved console.log statements.
Bad commit messages by junior team members.
Example:
git commit -m "Fixed Grid"
- Problem: In a wider codebase, it is not clear where and which grid was fixed.
Example:
git commit -m "tweaks"
- Problem: Too much vague, and also not clear what was tweaked.
Unformatted code due to not having formatters set properly.
Bugs being pushed into the codebase without building the app.
These problems often are faced by Junior Developers/Trainees and also from the rush of pulling off a feature in due time. But once this starts, the cycle goes on, and as a result, it gets harder to remove these small mistakes from a badly growing codebase. Moreover, the commit history looks bad. Maintaining a good commit history is quintessential for debugging, or checking which commit caused a build/deployment fail.
Solution
To solve these problems, so that things can be automated and maintained, the following can be used:
ESLint: A static code analysis tool that helps identify and fix problems in the code. It enforces coding standards and best practices.
Prettier: A code formatter that automatically formats code to ensure consistent style and appearance.
Husky: A tool that allows to set up Git hooks easily. It's commonly used to run scripts, such as linters or tests, before commits.
Lint Staged: A tool that runs linters on pre-committed files. It helps ensure that only the modified files are linted before being staged, optimizing the process.
Commit Lint: A tool for linting commit messages to ensure they follow a conventional and consistent format. It is often used in conjunction with Husky to enforce commit message standards.
What things I focused on while setting up
The pre-commit file can be customized according to you. We can define actions to be performed in this file before any commit is successful. The actions can display meaningful messages for the developer to understand what's going on.
Check ts-config standards: This checks TypeScript types in the project. It ensures that all TypeScript files do not have type-related errors. If there are any type errors, the pre-commit hook will fail, preventing the commit.
Check prettier standards: This checks if there are unformatted code in the codebase.
Fix prettier-standards: This fixes the format of all files if there are any unformatted files in the codebase.
Run linters: This runs lint-staged which executes linters on pre-committed files. It checks and fixes files for issues such as linting errors, formatting problems, or any other violations.
Check lint: This checks if ESLint rules are followed.
Run build: If all checks pass, run build. If the build is successful, the commit is successful.
Now, you can push the code accordingly. All these steps solve a lot of problems mentioned above as it is now well automated.
How I defined my husky pre-commit file
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo '๐จโ๐ง๐จ Styling, testing, and building your project before committing'
# Check tsconfig-standards
yarn check-types || (
echo '๐คก๐โโ Failed type checks. Make the changes mentioned above.'
false;
)
# Check Prettier Standards
yarn check-format || (
echo '๐จ๐พ Your code is not in format. Prettier check failed. Run yarn format, add changes and try committing again.';
false;
)
# Fix Prettier Standards
yarn format || (
echo '๐จ๐พ Your code has been formatted';
false;
)
# Run lint-staged to check and fix files pre-commit
yarn lint-staged || (
echo '๐๐จ lint-staged checks failed. Please check and fix the issues mentioned above before committing.';
false;
)
# Check ESLint Standards
yarn check-lint || (
echo '๐จ๐พ Rules were broken. ESLint check failed. Make the required changes listed above, add changes and try committing again.';
false;
)
# If all tests pass, run build
echo 'โ
๐๐ฆพ All checks passed. Building your app...';
yarn build || (
echo '๐๐จโโ Build failed. Check the aforementioned.';
false;
)
Why I did not setup commit lint
I might be wrong, but I tried setting up commit lint at the very end but it seemed to conflict with ESLint. Commit lint is a massive powerful enhancer for the development workflow for maintaining conventional commit messages. But, as I set it up at the end, it was outweighing the pre-commit hook that I already set. Besides, I should mention that the documentation of commit-lint is pretty vague. The commands they listed are currently deprecated. I did not find an improved version. We can as responsible developers who cares for the workflow, maintain custom set conventional commit message rules or manually maintain the ones provided by commit lint. Even though newcomers could often times be clumsy, it should build up the practice of learning to commit meaningfully.
How it looks like while committing
$ git commit -m "fix: Removed backend dependencies"
๐จโ๐ง๐จ Styling, testing, and building your project before committing
yarn run v1.22.21
$ tsc --pretty --noEmit
Done in 2.25s.
yarn run v1.22.21
$ prettier --write .
.eslintrc.json 24ms (unchanged)
.prettierrc 26ms (unchanged)
.vscode/settings.json 1ms (unchanged)
.yarn/releases/yarn-1.22.21.cjs 8818ms (unchanged)
// all components/utils/pages/functions (unchanged)
next.config.mjs 3ms (unchanged)
package.json 5ms (unchanged)
postcss.config.js 4ms (unchanged)
README.md 44ms (unchanged)
tailwind.config.ts 3ms (unchanged)
tsconfig.json 2ms (unchanged)
Done in 10.17s.
yarn run v1.22.21
$ prettier --check .
Checking formatting...
All matched files use Prettier code style!
Done in 10.72s.
yarn run v1.22.21
$ eslint . --ext ts --ext tsx --ext js
Done in 1.89s.
โ
๐๐ฆพ All checks passed. Building your app...
yarn run v1.22.21
$ next build
โฒ Next.js 14.1.0
Creating an optimized production build ...
โ Compiled successfully
Linting and checking validity of types ...
โ The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/basic-features/eslint#migrating-existing-config
Collecting page data ...
Generating static pages (0/8) ...
Generating static pages (2/8)
Generating static pages (4/8)
Generating static pages (6/8)
โ Generating static pages (8/8)
Finalizing page optimization ...
Collecting build traces ...
Route (app) Size First Load JS
โ โ / 174 B 91.1 kB
โ โ /_not-found 885 B 85.1 kB
โ โ /forgot-password 139 B 84.3 kB
โ โ /login 140 B 84.3 kB
โ โ /registration 39.9 kB 131 kB
+ First Load JS shared by all 84.2 kB
โ chunks/69-c95e44b70b569bbf.js 28.9 kB
โ chunks/fd9d1056-773bcbcf1b98b984.js 53.4 kB
โ other shared chunks (total) 1.9 kB
โ (Static) prerendered as static content
Done in 21.74s.
[main 724c85b] fix: Placed already have an account message under each step's form
7 files changed, 55 insertions(+), 12 deletions(-)
Pros and Cons
As this setup solves a lot of problems, it is the advantages and disadvantages that I mainly want to talk about. Setting this up, caused me to setup and delete multiple repositories for either missing a step, or overdoing it. All of the packages are not a good combination all together as the automation that we are dreaming about does not always work out. Sometimes, the developer working on the codebase just needs to follow the rules themselves, manually.
Pros
Code consistency and Quality Assurance.
- We can easily get rid of unused expressions, import statements, unremoved console statements, and many more rules that ESlint provides. It is a massive help.
Automated Code Formatting.
- Automates the formatting of code, reducing the time spent on manual formatting and minimizing style-related discussions among team members.
Blocking dilemmas' through the Pre-Commit hook.
- The Pre-Commit Hook enables the execution of tasks, such as code linting and formatting, before each commit, ensuring that only clean and consistent code is pushed to the repository.
Standardized and Conventional Commit Messages
- Enforces a consistent and standardized format for commit messages, making it easier to understand the history of the project and facilitating collaboration among team members.
Enhanced Developer Experience
- Once, someone starts working in a codebase like this, they will find it really fun, where everything is checked for and well maintained without any scopes of pushing bugs into the codebase.
Cons
Tough for newcomers
A professional codebase itself is often overwhelming. Any new developer who is a junior or a trainee joining the project would find it hard adapting to the system.
Once, a developer gets to understand the frustration of the dilemma, they would love to work in this setup.
Customization Challenges
- As I mentioned above, that I could not use commit-lint. Using all the mentioned packages together requires a lot of trial and error. Customizing this setup is really challenging. In my opinion, maintaining commit-lint manually looks to be the solution, but if it can be set up properly, it is a massive gamechanger.
Commits are time-consuming
In demo/deployment days, the teams often go through rapid bug fixing and pushing code into the codebase. This very setup grabs quite some time while committing as it runs all the scripts defined in the pre-commit file. Moreover, there is a delay and gap within staged changes, linting and formatting.
In crucial situations, one can use the
--no-verify
flag.But maintaining commit conventions manually should be done.
git commit -m "fix: Added optional chaining in product fields" --no-verify
Overhead in maintenance
- As the project evolves, maintaining and updating the toolchain configurations may become an overhead, especially when new versions or features are released.
Conclusion
I do think this is a great setup for maintaining a robust and optimized development workflow, but the cons that I discussed had me setup multiple repositories and delete them eventually as there needs to be a proper orientation as to which of the packages should be setup first. Things are also different for Windows and Linux Operating Systems as various YouTube tutorials follow different approaches and they are pretty confusing. I did follow a similar setup working in a backend repository, and things there went pretty smooth. I believe I have a lot more to learn about this setup in the Frontend. Hopefully, I will post a tutorial on how to have a proper frontend setup with all the latest versions. (Excuse to start my YouTube channel ๐)