Developing software has always been tough. However, now it is tough in a different way.
Decades ago, complicity was driven by the technical side of the development process. Software engineers had to thoroughly control the consumption of computer resources, like CPU power and RAM, choose optimization techniques and be, in general, “close to your hardware”. As software development was more concerned with technical parts, it was very hard to concentrate on the business value of the product.
Nowadays, technologies have evolved. Thanks to a layered architecture, a lot of complexity was hidden by other developers. A lot of low-level tasks were solved, so the developers (at least web developers) now are able to care more about the business value of their work than the technical aspect.
Legacy code problem
Everything goes with the price. The rapid development of frontend technologies makes the code outdated extremely fast. So fast, that even your own code becomes “legacy” by some criteria.
Frontend developers often face a new dilemma: refactor the code to be up-to-date with modern development standards or continue to develop new features.
Of course, the developers would be happy to do it in a first way, but that is not what is sufficient for the product. On the other hand, product owners are also interested in modern technologies if they make products evolve faster.
So how to find a good compromise here?
Well, it was already done
Enterprise-level approach
This issue is for sure not new. It doesn’t even require accepting any new paradigm to be solved. The primary development imperative is “divide and conquer” as old as software development. The most straightforward approach for this is dividing software into components (modules), limiting their interface, and making them as independent as possible.
However, in this case, the code will still be highly united by the technology stack.
With the beginning of microservices madness, this issue seems to be solved, at least for the backend part. Components could be separated into truly independent parts, any of which could be written in any programming language, by different development teams and even could run on different servers. However, the UI app still tended to be united. Something should unite all backend parts to provide a user holistic perception. To split frontend into parts, similar to the microservices approach for the backend, the micro frontend approach was introduced (You can read more about it here: https://martinfowler.com/articles/micro-frontends.html)
The micro frontend is an awesome technic, however, it comes with the same issues as microservices: it works well for bigger teams and generates more overhead.
Small teams approach
But what can we do in case of a small team? If we just have a few or even fewer people and have obtained a legacy project to support, which options do we have? Do we need to use jQuery and write a new code in Coffee Script? Well, the good news is that we can significantly reduce the usage of legacy technologies.
Frontend technologies allow us to use scripts, built by different build systems on the same page, and they can interact one with another. And this simple principle is actually very important for dealing with legacy code.
Depending on the situation, you can set up the interface with legacy code on an acceptable level. This can be compile-time level if the legacy code is not that bad, and uses an acceptable build system. In this case, you need to architect your legacy code boundaries and reduce interaction points as much as possible. Interaction, however, will happen with simple function invocations.
In the worst scenario, you can write new code and build it with the new build system. You should architect even smaller boundaries and interactions between the two systems.
Crossing the boundaries
Now let's specify which approaches can we use to bind the two systems, old and one.
Functions(methods) invocation
The first, and most obvious one is to invoke the functions. This method makes the code bound at compile time. Both old and new code has to be built with the same build system. New features have to be implemented in new modules, and boundaries between the old and the new code have to be as small as possible (Well, this is true for any interaction scenario)
Interaction through globals (object window, events, etc)
This method doesn’t require compile-time binding. You can define a public interface for old and new code and publish it (assign it to some window attribute). Then these two systems can exchange messages by invoking methods. Or you can use custom events to publish and subscribe to messages.
Iframes
Good choice if the part built with a new build system also represents a separate UI element, i.e. widget, menubar, shopping cart, etc. You still can exchange messages between these systems, i.e. sending notifications from parent to iframe.
Webpack technics
That is not actually a technique, just a syntax sugar good to know. You can declare a window object property as “external” and then import it as a module inside your ES 2015+ modules. Technically you will use a global object, but in code, it will be a normal import. This is very convenient for later updates. You can read more about externals in back here: https://webpack.js.org/configuration/externals/
Don’t know where to start?
We will advise you on the best way to realize your idea, leveraging our expertise
Real-life example
Let us consider a situation, when we have a legacy Vue.js application, written in coffee script and which uses some outdated libraries, i.e. jquery.
We want to add new components written with new technologies (written with js/ts, using a single-file component approach and webpack for build).
The first thing we can do is to define externals to use Vue.js and jQuery from the global scope.
Of course, we can install Vue.js inside a new application part with npm/yarn, as well as jQuery (right, actually it is better to avoid jQuery at all, but maybe we just need to control some jQuery plugins from the legacy application). However, we need to use the same instance of Vue.js or jQuery in all parts of the application. This is not important when you use stateless functionality (i.e., jQuery selectors, which you should not use at all, and use document.querySelector), but it is very important when you use stateful logic (i.e. using jQuery plugins, register Vue.js components, etc).
So, we can add the following lines to our webpack config:
This will import the global properties window.jQuery, window.Vue, window.Vuex
Then we can use them inside our new code:
Next, we can define new component(s):
And then make it available in the global object:
If we build this project and include it before the main app, we will be able to use our modern SomeComponent in the legacy application.
The same approach could work in the opposite direction too:
We can now use SomeLegacyComponent from the legacy application in our modern application.
Read more:
- An overview of React Hooks and Vue Composition API
- Should you prefer to React over Vue.js
- Introduction to TypeScript, a JavaScript With Syntax For Types
Practical Recommendations
Here is the simple algorithm to do in case you received a new legacy project, or when your current project is outdated:
- Estimate how old code actually is and how you should integrate new pieces with it. Can you use the existing build system, or is it better to set up a modern one, with webpack, rollup, etc?
- Define the boundaries of old and new code and the smallest possible interface to communicate through it.
- Write your code and be happy ;)
Conclusions
The front is a rapidly evolving area. However, basic principles of javascript are still up to date and help to manage the complexity of modern software development. To bring a new life to the legacy application, you don’t really need to rewrite every line of code. Using handy javascript specific, you can granularly rewrite the most changeable parts of the application, or write new features using new technologies without altering old functionality. With e2e tests, this is the ultimate way to make frontend applications scalable and maintainable.
FAQ
What is the front end development process?
Front-end development is the process of building components that interact with users. Examples are the user interface, buttons, user-entered data, websites and other features used to interact with and deliver a positive user experience.
What technology is used in front-end engineering?
The three main front-end coding languages are HTML, CSS, and JavaScript. They work together to create the underlying scaffolding that web browsers use to render web pages for users.
Why front-end development is important?
Front-end development is important in creating a cohesive experience for users, who see the aesthetic and functionality of a website's design and its back-end development.
What is front-end migration?
These are large-scale, enterprise-level migrations, often the most complex. Some examples include: integrating a company's legacy frontend technology into an existing framework or updating a legacy application to modern standards.
Table of contents:
Want to estimate your app idea?