In the past a few years, front end technologies were booming. New tools and version of frameworks emerged in a super fast pace. A project that was developed 3 or 4 years back, now could be outdated with old dependencies and lack of necessary new tools such as Eslint or TypeScript.
In this blog post, I would like to show you my idea about front end project upgrade.
DISCLAIMER: This blog post is not a model answer to the problem, but I hope it can give you some ideas about front end project upgrade.
Before getting start in actual setup and coding, you need to equip ourselves with a good mindset about project upgrade.
Our imagination of having upgraded project is beautiful, but there are some realistic problems you have to discuss before starting.
- Do you really have the needs for the upgrade? If the project does not have new feature requirements anymore and is in a very stable state, keeping what it is probably is a better choice.
- What is the goal for project upgrade? There should be a clear goal about project upgrade, instead of doing it just for doing it or being cool. For example, the reason you want code linting is because you have a lot of careless mistakes in the code which can be linted by linters and you want to eliminate those careless mistakes.
- Do you have enough time / man power to do that? The project upgrade is probably not going to complete in one night, but days or even months. You should carefully evaluate how much effort is needed and how long the upgrade process is going to take.
- Can you get supports from requirement side? Project upgrading takes many efforts and inevitably it will affect the delivery speed of project requirements / stories. So before you start, you should ensure that you have supports from the requirement provider sides. Otherwise, you would face questions and challenges from them, which could bring you endless stresses and anxiety.
- Do you have the scope and a plan in your mind? Before getting start, you should also have a clear scope of what should be upgraded (just applying linting processes, or convert the project to TypeScript or even refactor some working codes?) and how they are going to be done (do it by phases? how to ensure team members align to the big bang changes in code?).
- Are your team members ready for project upgrade? Some people are satisfied with what they already have and are reluctant to make changes. They might feel it is extra works to be done. Before kicking start, you need to make sure that they understand the benefits and necessity of doing project upgrade, not only for the project itself, but also for them as stakeholders to the project (e.g. For developers, easier development process. For operators, less chances of having errors. For requirement providers, faster delivery).
In a word, you should deliberately plan and decide to kick off the project upgrade. It’s not always a good choice to upgrade a project but you should carefully considers the gains and risks about it regarding your own projects with the unique problems you are facing.
2. Code Linting and Formatting
The very first step, if you haven’t implemented, would be the code linting and formatting tools.
There are many benefits of implementing code linting and formatting tools, which have been shared in many other blog posts. Basically, every developer may have their own coding habits, such as using tabs or spaces, 2 spaces or 4 spaces, comments in
// comments or
/* comments */. Using linting and formatting tools can enforce a kind of coding standardization so that everyone can produce the codes in the same style, which helps in code maintainability and readability.
So far, the most popular tools for linting is Eslint and tools for formatting is prettier. You can simply write configuration files
.prettierrc to customize the linting and formatting rules based on your own requirements. Especially for Eslint, there are many plugins to extend the capability, and you can even write your own eslint rule to standardize your unique code problems.
After configuring the Eslint with rules, you can simple run
npx eslint . --fix to automatically fix all the linting issues in your project and then run
npx prettier . to formatting all the codes as well.
Now here comes the most difficult part, how to make your teammates align with your linting changes?
By auto linting and formatting, there will be a LOT of changes in the code base. And these changes will go in to the master branch (assuming you are using git), while your other teammates might be developing their stuffs on their branches. When your teammates finish their work and want to merge to the master branch, they have to rebase / merge your linting and formatting changes from master branch. BOOM, their branch will be full of conflict errors!
Luckily, I have a great colleague in my team who came out a very nice solution to this problem. He suggests that they can choose “Accept Current Changes” / “use mine” all the time for the conflicts problems, and then run
npx eslint . --fix and
npx prettier . so that their own changes also get linted and formatted, based on the configuration files rebased from the master branch.
Simple but brilliant idea huh? ;)
Nowadays, TypeScript almost becomes the standard skill set for front end developers. TypeScript is useful and powerful and many blog posts have discussed about the benefits of using TypeScript and why you should do that. Here, I would like to share with you some of my opinions for your reference.
- Which project needs TypeScript
My opinion is TypeScript is most suitable for large and complex applications but less useful for pure scripts.
When building large applications, the data structure we are playing with could be very complex, with possibly many properties with many types, multiple layers or even nested layers. When dealing with complex data structure and passing them among different components of the application, without type checking, it’s very easy to make mistakes such as spelling error (e.g. classical mistake: I thought it was O but it is 0 zero) and mishandle of data type (e.g. I thought it was a string but it is an array of integers). Hence, you might need type checking for minimizing unnecessary careless mistakes.
Besides, having type check reduces the mental burden during development. You don’t have to keep worrying about the type and constantly switching opening files to double / triple confirm you are right about the types and spellings.
However, when it comes to pure scripts, TypeScript might not be that efficient. Usually, a script is for some specific task, which is independent to other application components. Once a script is done, it’s less likely that you will make further changes in future, which means maintainability might not be that important here. In this case, having TypeScript set for scripts creates additional layer of works and increase the complexity of scripts (I have to add interfaces to all the temporary data structure I used, else it becomes “AnyScript”…), which might be less efficient.
- TypeScript has its limitations
TypeScript is not a silver bullet. It also has limitations.
Another example, using TypeScript does not mean that you are 100% safe from type errors. One good instance is that, even if you have your applications fully typed, neatly and nicely, but you have to receive RESTful API response from an external system, which could change their API format without noticing you prior to their change, your application then still bears the risk of breaking because of type errors. In a word, TypeScript can only do type checking at build stage. In run time, it’s still a free land for all kinds of data.
Basically there are mainly 2 approaches.
- Convert the whole project to TypeScript in one shot, and then annotate those red flags with
Then the next task is to gradually reduce the number of
any in the on-going development, and stop creating more
any. What’s even better is that you can use the number of
any to track the progress of upgrading.
I haven’t tried this approach myself yet, but it sounds quite convincing. And I’m actually planning to do that in the near future.
Airbnb had a very good experience on this matter, and they even open sourced a tool called ts-migrate to help TypeScript migration!
4. Test Automation
Here comes our old friend, test automation! I’m sure you understand the importance of test automation and I’ve talked about good practices of test automation in my previous blog post. There are a lot of tutorials teaching how to write tests, so here we will not discuss them.
We will talk about these 2 topics:
- What tests bring most values but cost least efforts?
To answer this question, we need to revise our classical test pyramid here:
There are mainly 3 types of tests, unit tests, integration tests and e2e tests.
Normally, in a front end project, the number of unit tests are the highest, because it’s easy to write and easy to test. Then it’s integration test, which test the correctness of interactions among different components. And lastly, e2e tests, mimicking human interactions on the user interface and check if it meets the expected results, which is the most difficult type of tests to implement, maintain and run.
Among the 3 types of tests, you should always implement unit tests because it brings much values at a very low cost (easy to write and easy to test). No matter you are writing an application or a shared library, the very first step of test automation is unit tests and it is a must.
As for e2e tests, there are always on-going debates about implementing them or not, because it takes much time to write (explicitly state every step on the real user interface), is hard to maintain (a tiny UI change may cause failure of e2e tests), is uncertain in execution (flaky e2e tests with random success or failure due to unknown reasons, retry mechanism is needed).
The nature of e2e tests determines that they cannot be vastly implemented in every parts of the project because it takes great efforts to maintain. So some teams choose to have some trade-off regarding it. For core modules, which is critical for businesses and less likely to change, such as login functions, core business functions (e.g. payment), e2e tests are developed. While for less important modules or frequently updated modules, e2e tests are given up as the cost of developing it is higher than the value e2e tests brings.
- A realistic problem: our project is moving too fast to have time for tests, how?
I think there should be 2 different scenarios, building shared components or building applications.
For shared components, it is very dangerous not to have automated tests because they are consumed by many other parts in the application. Without automated tests, the risk of introducing bugs is high and potentially it affects all the consumers of the components. For this case, you should at least implement unit tests for them because it’s quick to do so.
For applications, if you are in a super dynamic team, business logics change rapidly, which causes all the related tests getting outdated all the time, it is acceptable not to write tests for them, as these tests slow down the delivery speed and bring little values. But here, you still need to do some researches and differentiate the more frequently changed components and less frequently changed components and try to build some tests for less frequently changed components.
It’s indeed a huge topic about front end project upgrade. Due to my limited experiences and capabilities, I couldn’t summarize all the aspects. If you, who are reading this blog post, have more ideas and stories to share, you are more than welcome to leave your comments here. Thankssss~