Published July 26th, 2023
19 min read
Product Manager at Halo Connect
→ Learning these quirks can take a while, and handling them during development adds both time and lines of code to a project.
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.
Insights on the complexities of RICH TEXT EDITORS and technical debt
What is TypeScript?
Best of all TypeScript answers all the prior concerns noted, plus some.
For this very reason, in 2012, Microsoft developed TypeScript with the specific goal of a scripting language that better handles large-scale complicated applications.
Types must be defined in the code and are checked at compile time
Types are assigned and checked at run time
- 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.
That’s a massive long-term gain in terms of reduced bugs and work impediments. But what’s the cost of conversion?
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.
→ 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.
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:
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.
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….’
How TypeScript helps with technical debt
- 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.
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
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.
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
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’).
Is Typescript dynamic-typed or static-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
Beyond TypeScript itself, other organisations have created or extended tooling to work with TypeScript.
→ 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.
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.
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?
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
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.