DEV Community

Cover image for My first finished project in 7 years!!! :)
Keff
Keff

Posted on • Edited on

My first finished project in 7 years!!! :)

Hey! It's been a while since I've written anything here. It's been a weird last few months. But I'm glad to be writing again. Hope you're all doing great!

I never thought I'd say this, but I finished a side-project for the first time in my career!

Yup, that's right. In my 7-year-long career, I've NEVER, ever, finished a project, how rare.

Not because I did not have them, I have them in the hundreds. Some in GitHub, some in my hard drive, some lost to time. But one thing's for sure, they are not finished, and most of them are a big mess of code that does not know what it is.

This post is not a tutorial and it's not meant to teach anything. But it's great if you take something from it!! This is more of a log and a little story about my journey.

A little retrospective

I've always loved starting new side projects. Always striving to make something better than before, and always trying to add some cool twist. But the ambition has always been to create an elegant, clean, and sturdy piece of software. I don't care what the software is. I could easily set to build some Android app, make video games, create a compiler, make coding art, a js library, apis... I've recreated an FTP server/client, and created an HTML preprocessor (kind of), I've learned at least 15 languages, some in-depth, some not. I've done stuff with blockchain, which I regret xD

You get the point, I've done a lot of stuff.

The fact I have some sort of undiagnosed ADD or ADHD might've helped, looking at it now xD

I think one of the reasons why I always end up letting the projects aside is because they are too ambitious to do in a short amount of time. And I get bored of them quite fast.

One other reason is most times I don't have a good vision of what I want the project to be and do. I tend to add features and extra fluff without thinking. This can get messy very quickly, as features compete with one and another, priorities shift, features might require changing the original plan to fit in, making the original idea fade. And projects start to become way too big and messy, removing my motivation to keep working on them.

How have I finished the project?

Well, as I mentioned before, the main thing has been time. It's been short enough that I've not become bored of it. The second is that I had a very good vision of what I wanted, since is something I've attempted at least 3 times before and failed each time.

This time I had a very clear focus on what I wanted, and every single decision and action I took went towards that vision. Even when I steered away, which I did a few times, I soon realised and went back.

So yeah, I'm basically fucking Dennis Ritchie right here!

Kidding, of course, I'm more of a Donald Knuth guy 😂

So what is this project about?

Glad you asked, I was gonna ask you the same question! I mean, yeah, I know what it is, yes.

Okay, enough diversion for now!

Yeah, so, you know HTML right? That beautiful piece of engineering. I hate it. Not really, but yes. Not the language itself, but its syntax. Not just that, but I feel it's quite redundant, and could be simplified a lot in some ways.

So over the years, I've attempted to create some kind of tool to help me not write it.

The first thing I did was try to write an HTML pre-processor from scratch. I created a language and wrote a pre-processor to generate html from that. It kind of worked, but was a mess. I did not know what I was doing at the time, just got out of school! The project is available over on GitHub if you're curious, just don't judge too hard, I had less than a year of experience coding seriously!

Here's a little snippet of the language (it's called STML):

div#target(.bold .row)[click='doX()']
  div(.col-4)
    span 'Heyy'
  div(.col-6)
    span 'You like stones?'
Enter fullscreen mode Exit fullscreen mode

After that, I tried making frameworks that leverage the need to touch HTML but had no luck on that front.

That takes us a couple of weeks back. When I found the project mentioned before, I thought to revisit it and re-write it.

So I did, I started by taking a fresh look at the language and trying to break it. It did not take long. And the language was incomplete and had a lot of limitations. That motivated me to create another language and start from the beginning. I wrote the language and created a very rude transpiler.

While doing it I thought that the process of transpiling was kind of redundant. Why not let the user write the transpiler? Well, that's kind of incorrect, the user would just create what would be the parse or syntax tree if you will.

It would also offer the tools for creating the tree in a clean, simple and effective way. As well as offer the option to convert the tree into HTML code.

Now, that sounded quite interesting.

Let's look at an example. Take this small sample:

div#target(.bold .row)
    p 'Hello world'
Enter fullscreen mode Exit fullscreen mode

This internally would become a tree, representing the hierarchy and relations between tags. And each tag would contain information about itself.

Something like this, but more complex of course:

div { 
    id: 'target', 
    class: 'bold row', 
    children: [ 
        p { children: ['Hello world'] } 
    ] 
}
Enter fullscreen mode Exit fullscreen mode

Then I would take that tree and convert it into HTML.

In theory, if I made an API simple enough to not make it a chore to write, it could be possible to let the user just write in JavaScript or typescript instead of a custom language. It would also be easier to make and maintain.

And so it starts!

At that moment I had one vision, and I saw it. Here are the key points:

  • Simple - Easy to understand, and write (after learning a bit of course)
  • Light - API should not have much fluff, and make methods short to help keep code compact. It might look weird at first but I think you get used to it quite fast. I'll talk about it later!
  • Complete language generation - It must be able to generate any HTML you want.
  • Must do 2 things: It must do only 2 things, give the tools to build a tree representing an HTML document. And convert that tree into html, and nothing more.
  • Typed - Must be typed, IDE autocomplete everywhere that is possible
  • Tested - Extensively tested (extra)

So, the first step for me usually is, in cases like this one, to write an example of how I want the code to look. As that's a big part of the vision.

This is what I came up with initially:

const myPage = doc();

tag('data');

div().id('test').ac('container')
  .div()
  .p(['Hello ', p(['world']).ac('bold', 'red')]);
Enter fullscreen mode Exit fullscreen mode

tag creates any tag you want. div creates a div, .id() sets the tag id, .ac() sets the class of the tag, etc...

for the chained .div, the idea was that by calling .<tag_name>() in another tag, it would be added as a child.

This approach became a problem pretty fast. On one hand, it's not intuitive. Who's child is p in the example above? Well, it depends on how I code it. And that adds complexity that's not needed. But I insisted as I liked it. I tried both ways, but it did not feel simple and intuitive.

I ditched the idea of adding tags directly and decided to just pass the children in, or use .append.

const myPage = doc();

tag('data');

div([
    div().append(span()),
    p(['Hello ', p(['world']).ac('bold', 'red')])
]).id('test').ac('container');
Enter fullscreen mode Exit fullscreen mode

This felt better, more intuitive and more familiar. This is done all the time, meanwhile, the other approach was made up. But you might have noticed a little inconvenience with this approach. The tag information is after the children. Imagine HTML, where the class is on the closing tag xD.


I went off track!

At this point, I for some reason started tinkering with the idea of adding logic to the project. State, events, all that frameworky kind of stuff. I modified it to work at runtime and started messing around. It was starting to become quite weird, complex and not very useful to be honest. But somehow I regained the vision and luckily backtracked away from that. Having that defined vision made me take a step back and re-think. What does this project need to do: "create tree" -> "generate html". Not be a framework or anything else.


... time to re-think about it. This makes it weird and makes it hard to read.

The previous version was a pseudo-builder-pattern implementation to call it something. It was a class acting as a builder. That meant that I had to first create the tag and then I could add attributes.

I rewrote it to follow a correct builder pattern. But, for some reason I wanted to not have to call the builder each time you want to create a tag, those extra pair of parenthesis... This ended up working fine. But oh man, was it tricky to get working.

With parenthesis:

div().id('test').ac('container')
    .b([
        div().append(span()).b(),
        p(['Hello ', p().ac('bold', 'red').b(['world'])]).b()
    ]);
Enter fullscreen mode Exit fullscreen mode

.b() build the tag with children if passed in

Without parenthesis:

div.id('test').ac('container')
    .b([
        div.append(span()).b(),
        p.b(['Hello ', p.ac('bold', 'red').b(['world'])]).b()
    ]);
Enter fullscreen mode Exit fullscreen mode

It's a minor thing, but now it looks nice. It reads nice, and makes sense I think.

Oh, those square brackets, not needed, remove them.

So instead of receiving an array, we receive a spread of arguments. And now it looks like this:

div.id('test').as('color', 'red')
    .b(
        div.append(span()).b(),
        p.b('Hello ', p.ac('bold', 'red').b('world'))
    );
Enter fullscreen mode Exit fullscreen mode

Yup, now it looks perfect...

Just one last thing, it would just be a moment. It also seems redundant to need to call .b() every single time.

This is what I want:

div.id('test').as('color', 'red')
    .b(
        div.append(span()), // No need to call, just pass builder in
        p('Hello ', p.ac('bold', 'red').b('world')) // Call the builder directly
    );
Enter fullscreen mode Exit fullscreen mode

Now, you can call .b, not call, or call the builder directly. Gives a lot of flexibility and makes it cleaner.

This might sound trivial, but it was quite difficult to make. I had to juice out all of my JS knowledge to make it work.

How it works: Technically speaking div, span, p, and so on are instances of class TagBuilder. Which is an instance of a function. TagBuilder is callable. Every time you change something, like calling .ac(), a new TagBuilder is returned. I would've preferred this to not be the case, but I could not get it working without it.

This makes it possible to do this:

div.ac('some-class');
div(div.text("hey"), div.b(), img);
// 
Enter fullscreen mode Exit fullscreen mode

Oh, there's also another layer to this. From the start, I wanted to give some way of adding children without having to reference the parent tag. Magic!

I made it so that you can "attach" to tags. This means that whenever you create a tag, it will be created as a child of the attached tag. This makes this:

const root = div();
root.append(span("One"));
root.append(span("Two"));
root.append(span("Three"));
root.append(span("Four"));
Enter fullscreen mode Exit fullscreen mode

Into this:

const root = div();
attach(root);
span("One");
span("Two");
span("Three");
span("Four");
Enter fullscreen mode Exit fullscreen mode

All 4 spans will be added as children of root.

There's a problem though, what if we do this?:

const root = div();
attach(root);
span("One");
span("Two", span("Three"));
Enter fullscreen mode Exit fullscreen mode

Where does span three end up? Well, both as a child of span two and the root div. That's not good.

This took me a bit of thinking to come up with a solution. The best I could come up with is to make attaching optional. I did not want to make it a method, or an argument. I decided to make it a getter. So the TagBuilder had another layer now.

This is how it ended up:

const root = div();
attach(root);
span.a.ca("one-class");
span.a.append("Two", span("Three"));
Enter fullscreen mode Exit fullscreen mode

.a attaches the builder to the attached tag. Then when the tag is built, it will also be added as a child of its parent.

I also added support for CSS styles (with nesting 🙌), and scripts:

style.a({
    '.wrapper': {
        background: 'black',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        ':hover': {
            background: 'red'
        }
    },
});

script.a(() => {
  const btn = document.querySelector('#button-id');
});
Enter fullscreen mode Exit fullscreen mode

The content of the script function will be added to the script tag. Did you know you can get the string representation of a function in JS? Kinda cool!

So yeah, that's it. That's Hobo! Please tell me what you think! A couple more notes left before I leave though.

Types

I wrote Hobo using typescript, for that vision of having it as typed as possible. Good decision, typing it with jsdoc would've been a pain or maybe impossible. For example when adding autocomplete for CSS values based on the property name (i.e. showing the named colors for the background-color property).

Now, hobo is mostly typed. Not all property values are included, but will be adding more as I go and PR are very welcome if you fancy it!

Benefits of this approach

Before wrapping this article, I just want to list some benefits of this approach:

  • Familiarity
    • No need to learn a custom language
  • Access to all JS features
    • Loops, conditionals, etc...
    • If you need 3 divs, just use a for loop, or some es6 feature.
  • Ease of development and maintenance
    • If I want to change something or add a new feature, I don't need to modify my custom language
  • Easy to extend and customize
    • The user can extend and customize it

What I've taken from this?

I learned a few things from this. Some about me, some about development in general.

I've learned that I have a propensity to deviate from the plan and vision, which in the end makes me ditch the projects. I also realized that I'm a bit of a perfectionist, even though it's very hard for me to make something perfect as I get bored quite easily. I should prefer short projects I can make in a week or less.

I also realized something that might seem obvious to some of you, but not to me until now. Development is about focus, vision and realizing when you got off track and steer back.
Know what your project is about, and try to stick to it. Don't be afraid to experiment, but realize when something does not fit in or would make the original idea fail, and don't be afraid to throw code away if it does not fit in. I know it's hard to throw away code you've spent time writing, but it's better to throw it than to create a mess.

Another thing I learned is to take a step back, and really think if the current approach and idea is the best option. And try to look for simpler options that might fit the project better.

Please check Hobo out if you find it interesting

GitHub logo nombrekeff / hobo-js

Little library to generate any HTML with JavaScript/TypeScript.

Hobo.js

Welcome to Hobo. A little utility to generate html inside your js/ts code. Meant as a side-project, but after writing it I thought it might be useful to some people in some scenarios.

Who's this for?

I have no idea! I might use it some time. But if you use it, and feel like letting me know, you can either leave a star, contribute or reach out! I would be interested in knowing how it's being used, if at all!

What does it do?

Well, in essence it allows us to create html documents inside js or ts with ease. If I've not missed anything obvious, I think it's possible to generate any kind of html document. Or maybe other stuff like XML too 🤷🏻‍♂️

You can generate any tag you want. Add classes, ids, styles, and attributes. Create css and add scripts. All…




That's a wrap!

Yup, I think that will do it. Thanks for taking the time to read all that rant hehe, and let me know what you think! 👍

Further Reading

If you've enjoyed this article and want to read more stuff from me, I think you'll enjoy these:

Top comments (36)

Collapse
 
efpage profile image
Eckehard

Hy,

you should definitively check out my DML-project, which has quite a similar approach, so things will feel quite familiar to you. Instead of attach it uses selectBase, which is pretty much the same.

DML exposes most HTML tags as functions, so you can simply write button("Press me") to create a button with some text. Initially, all DOM elements are appended to the document body, but you can select a new basepoint anytime. This might look like this:

selectBase(div("","border: 2px solid red;")
   p("Hello world")
   button("Press me").onclick(() => alert("button pressed"))
unselecBase()
Enter fullscreen mode Exit fullscreen mode

You will find more examples to play around on the homepage. Please let me know what you think! The Version of DML currently on the web is a pretty early state but perfectly usable. V2 is about to be finished, but this also took me 3 years now. See, you´r not alone...

There is another intrestinig project on github called VanJS with a similar methodology, but a different approach to compose your site. VanJS can run both on the server and on the client side and has some really cool tricks to keep the library as small as possible.

Collapse
 
nombrekeff profile image
Keff

I just went and did it. I've made a project inspired by Hobo and DML that works at runtime. Take a look if you want! github.com/nombrekeff/cardboard-js

It's still quite raw and simple, but I think it's useful already!

Collapse
 
nombrekeff profile image
Keff

That's very interesting, I went on that route for a bit while developing Hobo. But it did not feel right, in my case, I just wanted to generate HTML and wanted it to be quick to make (I made Hobo in less than a week). But I might revisit it in the future and make a version that works runtime similar to your project and VanJS!

One question though, when you create a tag, for example, button, is the result an HTMLElement or do you have some wrapper around it?

And would you be up to giving me a couple of tips, in case I decide to make hobo runtime? We could both benefit from working together, as they're quite close.

Collapse
 
efpage profile image
Eckehard • Edited

DML generates DOM nodes directly at runtime. This is quite powerful, as each function returns a DOM reference. No need to use "getElementById()" anymore, just store the value in a variable and you have direct access to the DOM. This enables some interesting options to build applications.

VanJS can run in both modes, directly on the DOM or on the backend using NodeJS to create HTML. Both approaches have their pro´s and con´s, but working on the DOM has some big advantages.

You might hear that using Javascript to generate the DOM is slow. In practice we see, it is not. If you check the page performance of the DML-hompage (which was created using DML only), it is quite brilliant:

Image description

I would really appreciate to combine the efforts. It would be interesting to have a code that can run on the server side and on the client.

There are some examples on dev.to that give some insights, for example an small working desk calculator in 61 lines of code that implements and uses some of the core routines too. Maybe you find also some inspiration here: The magic function. This is one of the core functions of VanJS slightly adapted to generate tag functions.

If you want so share some ideas to add a HTML-rendering capability to DML, this is most welcome. Happy coding!

Thread Thread
 
nombrekeff profile image
Keff

Very interesting, very interesting. I'm now fiddling with making a version of hobo that works at runtime similar to your project.

Feel free to use hobo as inspiration or even fork it if you want to make DML work server-side. Or if you want any advice or anything please reach out! I think I could modify hobo to render DML. Is DML open-source? If so, I could take a look and maybe, just maybe, hobo could be able to help :)

I did something similar to the magic function, but I wanted to have autocomplete for every tag. So I ended up doing this (this way you can know exactly what tags are available):

const tagNames = allKnownTags;

type BuilderFunctions = {
  [key in ValidTagName]: ((...children: ValidTagChild[]) => Tag) & TagBuilder;
} & {
  tag: (tagName: TagName, ...children: ValidTagChild[]) => TagBuilder;
};

let fns: Partial<BuilderFunctions> = { tag: tagBuilder };

for (let tname of tagNames) {
  fns[tname] = tagBuilder(tname);
}

export const builders = fns;
Enter fullscreen mode Exit fullscreen mode

I also enjoyed the idea of first configuring the tag, and then adding children. Otherwise, things like the id, class, styles would be at the end. This is harder to do on runtime without having to call some method at the end of the chain. Some thought is needed here!

Thread Thread
 
nombrekeff profile image
Keff

Hmm, just realized why you need that magic function at runtime xD

Thread Thread
 
efpage profile image
Eckehard

Oh, that`s a general pattern.... just a nice Javascipt gimmick, but sometimes really handy

Thread Thread
 
efpage profile image
Eckehard

VanJS objects can have an indifinite number of children given as an argument list, but I found this approach limiting and hard to read. DML mimicks the way HTML works: Any object created is immediately appended to the DOM. This gives you space to work with the objects. Styles and classes are given as a second argument, if needed.

Collapse
 
tayyabaakbar12 profile image
tayyaba

woow that is informative

Collapse
 
ben profile image
Ben Halpern

Good stuff. Pretty clean interfaces here, I could definitely see why you'd want this to exist.

Collapse
 
nombrekeff profile image
Keff • Edited

Cheers, I still think I could improve them a bit. But good enough for now!! Let's see if anyone finds a use for it 👍

Collapse
 
nostackdubsack profile image
Peter Weinberg • Edited

This is a cool idea! But one piece of feedback is that by abbreviating method names in the API I think it actually becomes less intuitive and creates a higher barrier for entry for any developer onboarding into a codebase that uses this or a similar API. For example, if I have no prior knowledge of hobo and I start reading some code written with it, it's very likely I'll have to visit the docs to understand things like aa, m, rc or mc, or even b which could be a little ambiguous (build or b tag?). Sure, context will help for methods like as or ac and I was able to infer many of the method meanings in the examples from the abbreviations, but addAttribute, removeClass , modify Class, addStyle, etc. don't sacrifice much space and make the code much easier to understand, especially for readers with no prior knowledge of your library (which is a very realistic scenario when a developer joins a new team and is thrown into a codebase). If other method names were expanded, then one could safely assume anything that's still abbreviated is just a tag name. Anyway, I know it's just a side project and kudos on seeing it though to completion! I also know how hard that can be. Just some food for thought. Thanks for sharing your experience! And btw, the library's code is also pretty cool - seems like there were some interesting challenges to solve here.

Collapse
 
nombrekeff profile image
Keff

Thanks for the feedback. Yup, I totally agree. I normally tend to not abbreviate methods for the exact reasons you mentioned. The only reason why I did it was to make it as compact as possible (to remove noise and make it faster to write). I know they can be a bit unintuitive, but the good thing is, there are just a few methods, so they can be learned quite quickly. I also never intended this to be used by other people, even though some might use it, so I decided to go that way.

I also thought of adding both options, the complete name and the abbreviation. And let the end user select the style they want to use. That has its own problems though xD

So yeah, I might leave it like this for now and might refactor it if enough people find it useful and start using it.

It was quite interesting to make, not too complicated, but challenging enough to keep me focused! Glad you like it, and cheers for the feedback again!

Collapse
 
redbeardjunior profile image
RedbeardJunior

Ohhhh jeej you pulled me in your story, I can related at the moment I'm working on a big project myself and always started and not finished something.

But at this project I'm currently working I'm hungry enough that I WILL FINISH this project.

Thank you for righting this article.

Good luck with your journey.

Collapse
 
nombrekeff profile image
Keff

You've got it man!! Best of luck with your project! Just don't make it a java applet 😜

Collapse
 
redbeardjunior profile image
RedbeardJunior

haha yes yes I will make it ! 65% is done I hope to release it end of the month !

Collapse
 
lionelrowe profile image
lionel-rowe

Congrats on finishing your project! I struggle with the same thing, my side projects consist of hundreds of half-finished PoCs and only a couple of "finished" ideas (of course, nothing is ever truly finished as you can always improve on it... but I consider any working MVP to be at least "temporarily finished").

If you're interested, check out Hyperscript (somewhat similar to your finished API, but more strictly declarative), as well as Emmet (a bit like your early DSL-based approach). And of course there's the OG DOM manipulation library, jQuery, as well as its smaller, lighter, server-side-friendly cousin Cheerio.

Collapse
 
nombrekeff profile image
Keff

Thanks! As you said, it's never really finished. I considered it finished as it does everything I wanted it to. But yeah, I'm sure I'll be working on it more xD

Ohh good old jQuery! It's been a while since I heard about it or used it! Good times :)

Collapse
 
phpfui profile image
Bruce Wells

I did the same thing, but in PHP. I hate writing HTML, and most people get it wrong anyhow. My sites validate at 100%.

Basic concept is most everything is an HTML5Element, then you call add() method to add child elements (nodes). You can check it out here on a site built entirely on this concept:

phpfui.com

Collapse
 
maeistrom profile image
Steven

Wanna hear something crazy? I have a side project I've been working on for 20 years that's 90% done. Almost the polar opposite of yours it's a language like PHP that adheres as closely to html as possible. The name of my language? STML.

Collapse
 
nombrekeff profile image
Keff

What does STML stand for in your project?? In my case it was an abbreviation of stimule. I had a reason back then, but can't remember why now 😂

Collapse
 
nombrekeff profile image
Keff

That's awesome!!! What a cool name 😜 ohh that remaining 10%. Best of luck!

Collapse
 
maeistrom profile image
Steven

Thanks I'll need it, I have nearly the exact same issues finishing a project that you describe. And it is a great name, just googled 'stml language' and there seems to be at least 10 different languages using it.

Collapse
 
sevapp profile image
Vsevolod

Comrade, congratulations!!! This is a really difficult matter, it is difficult to reach the end. I really appreciate it and wish you luck in your next endeavors!

Collapse
 
maeistrom profile image
Steven

Originally simple template markup language. Or Stevens. Or super/sucky. Was leaning towards making it superlative-of-your-choice since there's so many good words starting with a I could never quite pick a favourite

Collapse
 
princemuel profile image
Prince Muel

Everything you said is me right to the 't'. I take it though as a learning process, finding out more about myself and the way I think.