developer insights
Technical debt and types: The benefits of TypeScript vs JavaScript
Published July 26th, 2023
19 min read
Changing programming languages is always a torturous decision. But with a product as complex as a rich text editor, that’s so prone to technical debt, the right programming language is vital to its longevity. Thankfully, for TinyMCE, converting from JavaScript to TypeScript was easy – and carried with it a lot of benefits for an editor with heavy technical debt burdens.
Millie Macdonald
Product Manager at Halo Connect
Right now, JavaScript (JS) is easily one of the most popular programming languages. It’s particularly appropriate for rich text editors, because they're mostly integrated into websites and web apps – where JavaScript is the weapon of choice for developers.
However, if you go beyond the surface of JavaScript, you quickly find a host of complications that may make it a less than perfect choice:
- Easy to learn but hard to master. JavaScript has a low barrier to entry, but also a lot of unintuitive quirks and pitfalls (function closures, anyone?).
→ Learning these quirks can take a while, and handling them during development adds both time and lines of code to a project.
- Even bad code gets interpreted. JavaScript always tries its best to interpret your code – even if it makes no sense. The Wat talk by Gary Bernhardt gives some hilarious examples that highlight just how weird JavaScript can get.
→ Sadly JavaScript doesn’t have a safety net, so you’re on your own when it comes to ensuring your code does what it’s meant to.
- Readability requires documentation. Between the lack of types and how easy it is to write confusing code in JavaScript, making your code readable often requires extra steps.
→ Code comments, good naming conventions, and internal documentation are your friend when it comes to large JavaScript code bases – but they all require extra work.
Complications like these can cause any number of problems, which is why there are so many articles about JavaScript being a ‘bad’ programming language. However, there’s one danger that isn’t talked about nearly enough: technical debt.
JavaScript and technical debt
First up, it has to be said that Javascript and tech debt have a symbiotic relationship.
Flexibility may be one of Javascript’s strengths, but it also makes it easy for errors to creep into the code, unless significant precautions are taken.
A rich text editor is already a breeding ground for tech debt, due to the scale and complexity of its features and codebase. So it goes without saying that developing a product that’s already prone to tech debt, in a language that’s also prone to tech debt, isn’t a great idea. But it's a mistake that's often made.
JavaScript’s really easy to learn, it’s quick to hire dev talent, and runs almost anywhere. So how do you leverage those benefits, without risking the ongoing accumulation of technical debt that negatively impacts your developer velocity and product quality?
Enter, TypeScript.
Insights on the complexities of RICH TEXT EDITORS and technical debt
TypeScript vs JavaScript
JavaScript (JS) may be the undisputed ‘most popular’ programming language of HTML and the web, but in 2022, TypeScript (TS) joined the world’s top 5 most used languages.
What is TypeScript?
TypeScript is a free, open source programming language built on top of JavaScript, that adds strong types, tooling, and guide rails for better development. It compiles to JavaScript, which means it can run anywhere that JS runs and it maintains much of JavaScript’s feature set.
Best of all TypeScript answers all the prior concerns noted, plus some.
Getting into the weeds, although JavaScript is considered the primary scripting language for apps and web pages, it was never designed for that purpose. Nor was it designed for creating large and complex web apps – it’s better suited for small-scale applications.
For this very reason, in 2012, Microsoft developed TypeScript with the specific goal of a scripting language that better handles large-scale complicated applications.
Definitions
Statically typed:
Types must be defined in the code and are checked at compile time
Dynamically typed:
Types are assigned and checked at run time
Why is TypeScript better than Javascript?
Importantly, TypeScript isn’t a completely different programming language – it’s JavaScript but better.
- TypeScript simplifies, handles, or restricts many of JavaScript’s weirder features, both inherently and via the extra functionality it offers to developers, to reduce the risk of falling into the common pitfalls of JS.
- It adds type checking and better errors, to increase the chance that bad code is caught during development.
- Adding types adds inline documentation with minimal extra work, and allows you to define not just variables and functions, but also concepts and APIs.
For a rich text editor, this means you get the easy interoperability (of Javascript) with web applications and technologies, and also gain (with TypeScript) the functionality and tooling that’s invaluable for maintaining the quality of your code – all with minimal overhead.
That’s a massive long-term gain in terms of reduced bugs and work impediments. But what’s the cost of conversion?
Developing a brand new rich text editor from scratch, in TypeScript, is the easiest route. But what if, like TinyMCE, you already have an editor written in JavaScript? You don’t want to throw that code away. No.
So, how hard was it to convert TinyMCE from JavaScript to TypeScript? Well…
EXPERT TIP
Runtime errors vs compilation errors
Runtime errors occur when a program is running – AKA when it’s being used. By comparison, compilation errors happen during development when the code is being compiled. Because of this, compilation errors catch problems before the code goes out to customers.
→ JavaScript isn’t compiled, so it’s easier for issues to sneak into the product and cause runtime errors that may be found by users.
→ TypeScript is compiled, and its inbuilt error reporting, during development, means issues are more likely to be caught, and developers can fix them before the code goes live. This results in cleaner code overall, but the faster and clearer feedback loop also helps devs write and type code faster.
Converting JavaScript TinyMCE to TypeScript TinyMCE
The initial kickoff work on the mammoth task of converting TinyMCE to TypeScript, began in late 2017 and took about six weeks. It’s worth noting that was only the ‘start’ – because the key to converting an existing JavaScript codebase to TypeScript is gradual typing.
Since then, TinyMCE has had more underlying restructuring work done on it, than ever before in its history.
Thanks to the mountain of technical debt the open source core carried, it wasn’t until late 2022 that the team finished migrating it to “strict” TypeScript. Some of our internal libraries are so complex, that those migrations are still incomplete. In addition, over the same period there’s been a slow expansion of the way TypeScript is being leveraged – mainly in the Premium plugins – and it can now be said that TinyMCE is a TypeScript editor.
In a previous article we gave a lot more detail about the conversion to TypeScript, but if you’re considering converting your codebase from JS to TypeScript, the most notable takeaway is:
TypeScript accepts various levels of “typed” code. Plain JavaScript can become valid TypeScript with very minimal changes using the “any” escape hatch. This means the initial conversion is easy, and improvements can be scheduled over the following months or years.
That's the exact route TinyMCE took, to typing: a protracted one.
However, be aware that using “any” does mean you miss out on a lot of TypeScript’s features. If you want all of TypeScript’s functionality you need to type the code properly.
That said, even if your whole codebase uses “any”, you still get value thanks to TypeScript pointing out compilation issues during development, instead of at runtime (which is what JavaScript does). Here’s an example: TypeScript can check that functions are passed the right number of arguments, even if it can’t yet check they’re the right type.
So, while using “any” may not provide the ultimate value that you could gain, it’s a start – especially when you have a mammoth code base where you need to detect and decrease your tech debt.
Another benefit of using “any” is that it opens the door to slowly increasing how much code is typed – and therefore how much benefit you get from TypeScript. Once the initial conversion was done, our developers would slip types into pieces of code they were writing or updating with minimal extra effort, increasing the chance of errors being pointed out by TypeScript.
Time and again this caught errors early, and saved them from going into the code and creating tech debt we would have had to fix later.
Want to see one of the silliest bugs TypeScript detected in TinyMCE?
It’s called ‘The Case of the Mysterious Double Argument Error….’
To find out what happened, read the article Benefits of gradual strong typing in JavaScript
How TypeScript helps with technical debt
When it comes to comparing TypeScript vs JavaScript, TS actively helps with tech debt – by making it easier to implement three principles needed to avoid and manage it:
- Clean code
- Good tests
- Good tooling
Yes, most developers acknowledge that the best kind of technical debt to have, is intentional tech debt – a shortcut that was chosen during planning or development to speed up the release of a project. But why? Because this kind of tech debt can be recorded, tracked and planned.
However, tech debt isn’t always intentional.
Unintentional tech debt sneaks in when developers are in a rush, or don’t have all the information they need to make the best decisions. Meanwhile environmental tech debt is caused by forces outside your control – changes to dependencies, to browsers or operating systems, or to code in other parts of the product.
The hidden danger of both these kinds of technical debt is the inability to detect them before they cause trouble.
As you can see by the time it’s taken to pay down the tech debt found in the TinyMCE Core editor, hidden debt is particularly hard work in a large codebase, like a rich text editor. And the challenge never ends. Tech debt endlessly grows in our Core editor (especially environmental and functional tech debt), and needs constant work.
With so many moving parts, dependencies, demands for updates and new feature releases, rich text editors wage a constant battle with the tech debt demon. Switching to TypeScript, helped the TinyMCE development team to identify, prioritise and overcome the debt that had accumulated from many years of choices, accidents, or outside forces and influences.
There’s four key ways that TypeScript helped to clean up the TinyMCE code base.
What is Functional Debt?
Wondering where functional debt fits into the technical debt puzzle? Find out more by reading Functional debt vs Technical debt: What's the difference in a rich text editor?
Benefits of switching to TypeScript for WYSIWYG tech debt
Using TypeScript has plenty of benefits – no matter what stage of development you begin using it. However, there are extra benefits from switching to TypeScript, like TinyMCE did, and some of those are especially favourable to rich text editors.
1. Porting catches old tech debt
Two bonuses from porting TinyMCE’s JavaScript to TypeScript – compared to starting fresh with TypeScript – were:
a. Porting the codebase forces reviews of old code
To add types to an existing code base, a developer has to work through the code to figure out what types should be used. Some of that process can be automated, but the results should still be manually checked.
It’s time-consuming, and sometimes hard to justify scheduling the manual review work when there are features to be developed. But it's so worthwhile.
If your codebase is anywhere near as large, complex and old as TinyMCE, peering into the untouched corners can unearth all kinds of interesting things. Bugs, cruft, tech debt, old to-dos – you name it, you’ll probably find it.
For example, typing TinyMCE’s Image plugin found a potential memory leak.
b. Reviewing code can prompt discussions
Converting TinyMCE to TypeScript also prompted discussions amongst our engineering team – on everything from individual variables to entire editor concepts.
A pull request that was made – for improving some types in TinyMCE’s core engine – is a great example. It had forty-two comments from multiple developers discussing the changes. It also prompted further changes, including the addition of test cases and some minor changes to functionality.
EXPERT TIP
Type key dependencies first
If your codebase has more than one library, type key dependencies first.
It’s more efficient to type your lowest-level libraries first – the ones that are the foundation of your application. Then work your way up, carrying changes from previously-typed libraries up to the higher-level libraries.
If you type libraries in the wrong order, it can result in double handling the higher-level libraries. As an example, this pull request fixed some incorrect types in one part of TinyMCE’s API – which resulted in the need to change fifty-two other files across various modules.
This ‘key dependencies first’ approach, can also catch fun errors where two libraries don’t agree. TinyMCE had plenty of these, thanks to the sheer number of libraries it uses.
Depending on the scale of your WYSIWYG editor, there may be a lot of libraries you need to type. Managing it all, and remembering all the changes you need to carry through, can be hard. Thankfully, TypeScript’s tooling helps.
2. TypeScript in-built tooling aids a transition from JS to TypeScript
JavaScript is distinctly lacking in good, in-built tooling. Why’s that? Some of the very same features that make it popular, make it difficult to develop tools for JS.
Because it’s dynamically-typed, there’s a lack of rules that JS code should follow, and therefore a lack of rules tooling can check. A lot of JavaScript libraries and frameworks have created their own rules to work around this handicap, but they don’t necessarily match between different libraries.
This adds an additional layer of difficulty when developing a reliable, high quality rich text editor in JavaScript. If you’re considering building one in JS, and want to avoid bugs and tech debt, your code may need to be quite defensive.
By contrast, TypeScript’s strong types opens all kinds of opportunities for better tooling – adding rules inherent to the language, plus devs can add their own rules (AKA ‘types’).
Plus, of course, TypeScript comes with its own in-built type checking. According to a 2017 research paper “To Type or Not to Type: Quantifying Detectable Bugs in JavaScript“, TypeScript’s static type system alone can prevent 15% of common bugs:
Is Typescript dynamic-typed or static-typed?
Although Typescript is a superset of JavaScript, TypeScript is statically-typed – JavaScript is not, it’s dynamically typed.
But because TS transpiles ‘to’ JS, it technically does ‘both’.
It’s the combination of TS's static typing being optional (thanks to "any") and JS being dynamic, that allows you to do gradual typing. If TS wasn't optional, gradual typing wouldn't be possible.
Here’s a great article on the topic Static Typing vs Dynamic Typing
In this paper, we evaluated the code quality benefits that static type systems provide to JavaScript codebases. The results are encouraging; we found that using Flow or TypeScript could have prevented 15% of the public bugs for public projects on GitHub.
Beyond TypeScript itself, other organisations have created or extended tooling to work with TypeScript.
- The popular JavaScript linting tool ESLint, has a TypeScript variant.
→ And typescript-eslint isn’t just ESLint made to work with TypeScript – it taps into the TypeScript type system in order to offer additional linting functionality to help make your code extra clean and safe.
- Another category of TypeScript tools is IDE integrations.
→ For example, Visual Studio Code’s integration with TypeScript adds a whole host of functionality, such as:
- Code completion
- Type information on hover
- Errors and warnings in the IDE
- Code navigation and formatting shortcuts
- Refactoring and debugging assistance
Typescript tooling not only helps developers avoid mistakes – it helps them code faster.
Due to its complexity, refactoring anything in TinyMCE is full of hurdles – both seen and unseen. Historically, it’s proven itself to be difficult, wide-scale, and/or dangerous. But switching to TypeScript with its IDE refactoring and linting tools significantly reduced the work and risk of bad refactorings.
But all those TypeScript benefits are reliant on all your code being typed.
What is defensive coding?
Red Hat has a great Defensive Coding Guide
It provides guidelines for improving software security through secure coding and gives you concrete recommendations.
3. TypeScript types increase code readability
Types aren’t just helpful to the computer – their usefulness goes way beyond simply defining individual variables as numbers or strings.
There’s an extensive type system within TypeScript, which you can use to represent almost anything – from function signatures to library APIs. But why bother putting the time into typing anything more complex than variables and functions?
Types increase readability, which in turn increases development velocity and code quality.
a. Use types to define your functions and variables
What do you think this function does?
The likely assumption is that a and b are numbers, and the function does some calculation and returns a number.
What if it was written like this instead?
It returns a string. Or maybe it does a calculation, then converts the result to a string? Or does it do something completely different? The question can’t be answered from just the types – but you now know you need to look deeper.
Being able to make better assumptions about a function, at a glance, can be a vital step towards avoiding technical debt.
It’s so easy for tech debt to slip into a complicated piece of code, all because it was misread or misunderstood. By taking active steps to increase the readability of code, you decrease:
- Time to understand it
- Time to fix it
- Time to improve it
- Risk of bugs happening
- Risk of tech debt being accidentally introduced.
And this goes far beyond just functions.
b. Use types to clarify concepts as interfaces
Rich text editors rely on dozens of domain concepts: DOM ranges, HTML structures, and formatting models. When you’re a specialist developer working on a rich text editor, It’s impossible to remember how each concept works. And trying to keep external documentation in sync with code changes is always a nightmare.
The quickest way to clarify concepts is to use TypeScript interfaces.
For example, this is TinyMCE’s concept of a keyboard shortcut represented as a TypeScript interface:
Our developers no longer need to remember what a keyboard shortcut requires – it’s right there for them. And because every keyboard shortcut implemented in TinyMCE is typed using this interface, TypeScript’s type checking ensures any new or changed shortcuts don’t have errors. This also helps to prevent bugs or tech debt that could have arisen due to a shortcut being incorrectly defined.
Of course though, interfaces can be a lot bigger than this little example.
c. Use types to clarify your module boundaries
TinyMCE is a very modular product, composed of hundreds of distinct pieces – libraries, modules, plugins, and more. And one of the key principles of TinyMCE development is that each of those pieces must have a single entry point: an API that defines what functionality other pieces of the code base can access.
The API for TinyMCE’s Core editor spans dozens of files, full of interfaces defining how each part of the engine works. And those interfaces form the backbone of every part of TinyMCE that builds on top of the engine – they ensure every other piece maintains the same concepts and principles as the core.
Defined APIs prevent misunderstandings and conflicts across libraries – particularly the kinds of problems that could result in tech debt cascading through the editor.
It isn’t the perfect solution. How an API functions could be changed without the API’s type definition changing. But it’s another layer of assurance than what’s provided by using JavaScript, and sometimes that can make all the difference.
4. TypeScript helps to future-proof against tech debt
Gradual typing is an ideal approach for managing the time invested in your TypeScript conversion. However, it’s all too easy to do the initial conversion, then leave further improvements… for quite a while.
After all, you’ve started using TypeScript. You’re getting some of the benefits. Is it really worth the time and effort to add more types?
Think of it this way:
The more types you add to your code base, the more benefits gained from TypeScript. The more benefits gained, the more in-built protection from tech debt.
If you’re stuck trying to prioritise the improvement of certain parts of code, use your product roadmap as a guide. Type improvements can be planned into projects, or sections of the code base can be typed ahead of a new project – to get maximum benefits for minimum work.
The goal of typing is to find existing tech debt – shore up your foundation – and help prevent any new technical debt. So any kind of ongoing investment in paying down tech debt has a long term payoff, and converting to TypeScript has certainly helped curtail TinyMCE’s debt burden.
TypeScript’s great, but WYSIWYG editors are best left to experts
Yes, TypeScript endows your code with a multitude of benefits, particularly if you’re developing a rich text editor from scratch. But ask yourself, why take that on, when the experts struggle with WYSIWYG maintenance and tech debt burdens?
Repeat these three statements: Rich text editors are complex. Rich text editors are expensive. Rich text editors accumulate technical debt.
While gradual typing solves some issues, it doesn't absolve you of the ongoing investment.
If you need a TypeScript rich text editor, use an open source WYSIWYG like TinyMCE. It has components your dev team can use as a framework for customization and a public TypeScript declaration file for integration with a TypeScript app – and it’s supported by our professional dev team and advanced features. You’ll get the best of both worlds.
Because technical debt is always unfinished business.
Download the white paper
Opportunity Cost of Technical Debt: Minimize Your Rich Text Editor Development
author
Millie Macdonald
Product Manager at Halo Connect
Millie is a Product Manager at Halo Connect who dabbles in writing. Previously, she worked at Tiny as a Product Owner, dev, and QA engineer. She loves learning above all else, whether it's about people, tech, or leadership.
Related Articles
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.