Last year I started working as an independent developer to create a project that I’ve dreamed of doing for many years. One key benefit I saw in being independent was that I could finally do as much refactoring as I wanted. :) Now here I am, reflecting on that freedom and my latest big refactoring.
Context
The project I’ve been working on is a framework for low-code web development. At some point it needed a visual IDE and I figured using the framework itself to develop the IDE would make the most sense - it tests the framework thoroughly with a real, complex use case and also avoids external dependencies. For the UI, initially defining the low-code JSON configurations without a visual editor seemed reasonable and worked quite well, but for the background logic of an IDE, not so much. So I needed a flexible integration between the low-code configurations and the code. I started with a relatively simple but hacky approach that worked well for a first prototype, but doesn’t scale well. Which leads us to the main story of this article.
The Struggle
As developers we often like to complain about management and customers, but no great software is truly great without real impact. I’m currently free from any kind of management or customers, but I’m still not really free. How I use my time now may determine my future freedom and how much impact my work will have. Having significant delays for new features due to refactoring is painful and often seeing little progress in the process can be frustrating. I’m convinced that real innovation can’t be forced, at least my brain is usually not the most creative under pressure. I’m also not able to fully plan complex changes in advance, I have to go step by step and see how things unfold. And there’s a real risk of just replacing one flawed approach with another, potentially even more problematic one. Knowing what’s not good is fairly simple, doing it better is often harder than it seems. What if a new approach also has significant issues? Do I push through and accept these issues or go a big step back, giving my brain the space and time to come up with a better solution?
I decided to go the hard, long way - now that I finally have the freedom I always wanted, I gotta make it count. At the beginning of this project I was determined not to create another “okay” framework. I’d either come up with something I’m really excited about or throw it away. But over time things became more complicated. The framework surpassed my boldest initial dreams, but the IDE architecture was too complex and inflexible. Changing that would require making the framework itself even more flexible, not just refactoring the IDE. My motivation was quite low, I wanted to build new features, not spend weeks or months defining new core architecture and rebuilding existing stuff. It was tempting to just polish my workarounds and move forward, but I didn’t want to betray my core principles for the project at this early stage.
The Gain
Reaching meaningful success after a difficult refactoring feels very rewarding. The gain of efficiency and clarity, being able to keep the same functionality with less code and based on more intuitive concepts. Imagining how it will simplify development moving forward.
Parts of my IDE refactoring are still in progress, but its base architecture is now worthy of the framework and it’s super exciting to see it unfold. The framework itself is not only more flexible but even a bit simpler than before. It now provides an easy way to build on low-code UI components within TypeScript code. That supports developing complex applications in code while maintaining most of the UI in the visual editor, which still enables a high level of customization and adaptation without touching code.
Let’s look at a simple example:
interface InputParams {
label?: string;
placeholder?: string;
}
class InputComponent extends CustomViewComponent<string, InputParams> {
get viewRef(): PlainResourceRef {
return 'forms:input';
}
}
The Related low-code JSON
forms:input
JSON can for example look like this:
{
"_ui": "view",
"view": "forms:input-wrapper",
"params": {
"label": {
"_bind": "param",
"ref": "label"
}
},
"children": [
{
"_ui": "html",
"props": {
"element": "input",
"value": {
"_bind": "data"
}
},
"params": {
"attr": {
"placeholder": {
"_bind": "param",
"ref": "placeholder"
}
}
}
}
]
}
The custom superclass in this example provides dynamic parameters to configure the linked low-code component, using a type-safe reactive data model.
abstract class CustomViewComponent<T, P> extends NativeViewComponent<T> {
constructor(model: Model<T>, readonly config: Model<P>) {
super(model);
}
protected createViewParams(): RecordType<DynamicProp> {
return this.modelAsParams(this.config);
}
}
Such code-based components can be combined to more complex structures, controlling hierarchies and core functionality from the code while leaving UI details to low-code.
abstract class SearchDialogComponent extends DialogComponent {
readonly search: Model<string> = ModelFactory.text();
protected abstract getContent(): ViewSlotContent;
protected createSlots(): DialogSlots {
return {
header: () => [
new InputComponent(this.search, ModelFactory.record({
placeholder: 'Search',
})),
],
content: () => this.getContent(),
};
}
}
The JSON for the UI components can be provided from files, a database, local storage or whatever other source. Even within the same application it can be dynamically exchanged based on user or use case. For the IDE, this can enable different UI versions based on the user’s level of expertise or adjustments for embedded editing.
I believe this kind of flexible integration between code and low-code in both directions holds incredible potential for building advanced customizable web applications. In most cases, native services are likely still the better option to integrate code-based logic and keep flexibility on the low-code side. But where more control from code is desirable, the new approach creates a powerful alternative.
Worth it?
So is this refactoring worth the time and effort or should I have continued and published with the hacky solution? To be honest, I'm not sure, focusing on more features first and publishing faster might have been overall more beneficial. But it feels good to do things “the right” way, at least for now… :)
For more details on the core concepts of the project read this article:

Bridging the Gap: Low-Code for Web Developers
Tobias Augenstein ・ Mar 5
Or visit the website: https://ontineo.com
Top comments (0)