<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kacper Turowski</title>
    <description>The latest articles on Forem by Kacper Turowski (@turowski).</description>
    <link>https://forem.com/turowski</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F929789%2F4c1ae141-d1c0-46e5-95db-b144dc28e955.jpeg</url>
      <title>Forem: Kacper Turowski</title>
      <link>https://forem.com/turowski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/turowski"/>
    <language>en</language>
    <item>
      <title>Let's write a client-side hash router</title>
      <dc:creator>Kacper Turowski</dc:creator>
      <pubDate>Thu, 29 Sep 2022 03:09:15 +0000</pubDate>
      <link>https://forem.com/turowski/-lets-write-a-client-side-hash-router-k</link>
      <guid>https://forem.com/turowski/-lets-write-a-client-side-hash-router-k</guid>
      <description>&lt;p&gt;This will be a way more technical post than what I'm used to writing, so bear with me and feel free to point out all the dumb stuff I've managed to make an idiot out of myself with.&lt;/p&gt;

&lt;p&gt;And since I'm the talkative type, there will be three parts to this post, so we can at least have this somewhat structured. Firstly, we'll have some short banter on the backstory of this concept. Secondly, bit longer blabber on the technical background of the issue. Finally, the juicy part - writing the code. Feel free to use the table of contents below to skip ahead.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Banter on why did I need a custom router&lt;/li&gt;
&lt;li&gt;Boring technical blabber intro&lt;/li&gt;
&lt;li&gt;The juicy bits, let's write the code&lt;/li&gt;
&lt;li&gt;Wrap up&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why did I need a custom router? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;So, I did a really fun project recently, one I already managed to brag about. Feel free to check it out &lt;a href="[https://github.com/turowski-k/litesite-cms-js](https://github.com/turowski-k/litesite-cms-js)"&gt;here&lt;/a&gt;, although it's not &lt;em&gt;that&lt;/em&gt; interesting, honestly. Proof of concept kind of thing, rather than a viable product. I made it to get some basic understanding of writing JavaScript code. But perhaps you'll want to have a look so you can have some context.&lt;/p&gt;

&lt;p&gt;Anyway, please grab your beverage of choice and let's dissect the problem.&lt;/p&gt;

&lt;p&gt;This project of mine, Litesite, is a purely client-side web application with custom URL routing. Well, almost purely client-side - it still needs to fetch some files from the webhost. But beyond that, all the logic of what to fetch and how to process it happens in the browser.&lt;/p&gt;

&lt;p&gt;When I started toying with the idea of writing Litesite some good few months ago, I was endeared with GitHub Pages. They're free but they can only host static pages. While these days most hosting plans offer some kind of dynamicity &lt;em&gt;(and I think that'll be PHP most of the time)&lt;/em&gt;, the free or cheapest options might not offer this sort of functionality.&lt;/p&gt;

&lt;p&gt;So you'd have to resort to writing a completely static website, file after file...&lt;/p&gt;

&lt;p&gt;Or would you? &lt;em&gt;(&lt;a href="https://www.youtube.com/watch?v=TN25ghkfgQA" rel="noopener noreferrer"&gt;vsauce theme&lt;/a&gt; starts playing)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;See, even if the webhost offers only static hosting, you still can embed JavaScript in your pages. And today, there's a, uh... 13th edition of ECMA Script out there? It offers functionalities that were thought impossible back when I used to write simple programs in Turbo Pascal for compsci lessons at secondary school.&lt;/p&gt;

&lt;p&gt;So, I devised a plan to write something &lt;em&gt;similar to Express.js&lt;/em&gt; that could run in the browser. It should offer dynamic experience with static pages. And to make things more complicated, it would be written in pure JavaScript. No external or downloaded scripts. Then I also decided to make it a CMS (or a blogging engine, more like) while retaining the ability to modify it as needed, to complicate things bit further.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(although it might've made things bit easier)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I realized quickly that I'd need some kind of custom URL routing. The alternative would be creating a separate file for each sub-page and that's... not really dynamic. And if it was supposed to be ablog, I'd have to write each page separatedly, putting same layout into every single file, duplicating just. So. Much. HTML.&lt;/p&gt;

&lt;p&gt;The way around this? A single &lt;code&gt;.html&lt;/code&gt; file that would somehow interpret what I want from it, then fetch relevant content and layouts from the webhost and render them on the client-side. If it sounds ominous, &lt;del&gt;that's because it is&lt;/del&gt; don't worry - I'll try to cover more functionalities in next parts. But routing was the first thing I've created cause I knew that without it, there'd be no Litesite.&lt;/p&gt;

&lt;p&gt;So...&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical blabber intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;So, what exactly is a client-side hash router? Not a new concept, of course. It's been around for many long years now. But this doesn't really clear things up, does it.&lt;/p&gt;

&lt;p&gt;It's a script that runs client-side &lt;em&gt;(in the browser)&lt;/em&gt; and manages routing URLs &lt;em&gt;(takes care of interpreting and handling requests to the server)&lt;/em&gt;, and does it through use of hashes &lt;em&gt;(fragment identifiers)&lt;/em&gt;, since they don't cause page reloads.&lt;/p&gt;

&lt;p&gt;If you're still in the dark &lt;em&gt;(and I wouldn't blame you, as I re-read this while editing, hahah)&lt;/em&gt;, let's dive in deeper, into the basics. If you have a clear understanding of this concept, skip ahead to the juicy bits.&lt;/p&gt;

&lt;p&gt;Anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Routing
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;(not to be confused with physical routing, which requires routers and cables and has something to do with networks and IPs and stuff)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In layman's terms, routing is a way in which web servers handle incoming requests. Let's assume you have a server or a VPS deployed somewhere on the Internet. You send a very basic request - say &lt;code&gt;GET http://example.com/blog/my-first-post/index.html&lt;/code&gt;. That's the kind of stuff your browser does whenever you type in the address and hit Enter.&lt;/p&gt;

&lt;p&gt;Your server's response will be &lt;em&gt;"Okay, and..?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Well, not exactly that. Most likely you'll get zero response actually, but the takeaway is that it won't know how to handle the request. Now, if you have basic web server software - Apache HTTP Server maybe - installed and running, your web server will intercept and handle that request.&lt;/p&gt;

&lt;p&gt;By default, Apache uses file-based routing. That request above? Apache will look into the directory supposed to hold your website, try to find the &lt;code&gt;index.html&lt;/code&gt; file inside &lt;code&gt;blog/my-first-post/&lt;/code&gt; directory and that's what you'll get back. And what your browser will try to render in its window.&lt;/p&gt;

&lt;p&gt;This is fine and dandy, as long as you're fine with creating and managing a complex file structure. A single &lt;code&gt;.html&lt;/code&gt; file for every sub-page. So much refactoring in case of changing the layout 😵‍💫&lt;/p&gt;

&lt;p&gt;The way around this herculean task (or worse, sisyphean labor) is by using dynamic websites. Those are websites that serve you content that isn't static &lt;em&gt;(duh... but what I mean is that the content doesn't live inside &lt;code&gt;.html&lt;/code&gt; files but is prepared on the fly)&lt;/em&gt;. The content and the looks depend on what address you request, what's put in the headers of the request, and the specifics of your browsing session. Even your physical location, IP address, timezone and current time, system language or browser window size could affect the end result.&lt;/p&gt;

&lt;p&gt;Some years ago, query parameters were the industry standard for handling dynamic websites. You'd request &lt;code&gt;http://example.com/blog/index.php?post=6453&lt;/code&gt; and the PHP interpreter &lt;em&gt;(also the industry standard back then and still insanely popular today)&lt;/em&gt; would slap the HTML layout together with the content from the a database before sending it back to you.&lt;/p&gt;

&lt;p&gt;It's just that... the URL address could get ugly real fast. And it definitely ain't semantic.&lt;/p&gt;

&lt;p&gt;Now, if you use custom URL routing, you can intercept the request yourself and make your app interpret it as you please. For example with &lt;code&gt;http://example.com/blog/my-first-post&lt;/code&gt;, your router could extract some parameters from the route - &lt;code&gt;blog&lt;/code&gt; would define which template should be used and &lt;code&gt;my-first-post&lt;/code&gt; would denote the id of the post to be put into that template before being sent back.&lt;/p&gt;

&lt;p&gt;Now this is a semantically clean and readable URL.&lt;/p&gt;

&lt;p&gt;By the way, even default Apache routing config &lt;em&gt;(the file-based one)&lt;/em&gt; does some custom routing. When you request &lt;code&gt;http://example.com/blog/&lt;/code&gt;, the server will try to serve you &lt;code&gt;index.php&lt;/code&gt;, &lt;code&gt;index.html&lt;/code&gt; or &lt;code&gt;index.htm&lt;/code&gt; from the &lt;code&gt;blog/&lt;/code&gt; directory. And there's a way to introduce customized routing through editing the HTACCESS file. But that's just a curio we won't really be talking about here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-side routing
&lt;/h3&gt;

&lt;p&gt;Routing or not, putting a new address in the browser makes it reload the page. And reloading means you request a new file from your static webhost. If you want these semantically clean URLs, you'll run into an issue - most likely there will be no &lt;code&gt;blog/my-first-post/index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;How do you avoid that? Well, there's some ways one could go about this. Firstly, the old way, through query parameters. Make your links look something like that: &lt;code&gt;http://example.com/index.html?address=blog/my-first-post&lt;/code&gt; - and on the reload, your JavaScript code can take a look at the query parameters and interpret them accordingly.&lt;/p&gt;

&lt;p&gt;But... that's a reload still. How can you implement a more streamlined experience, where you only change a part of the website, say content only?&lt;/p&gt;

&lt;p&gt;Another way to handle this is through some pretty extensive scripting. Exchange your anchors &lt;em&gt;(yes, anchors! that's what "a" in &lt;code&gt;&amp;lt;a href=""&amp;gt;&lt;/code&gt; stands for and &lt;code&gt;href&lt;/code&gt; means "Hypertext REFerence"! I didn't know this until now)&lt;/em&gt; for non-link &lt;code&gt;&amp;lt;span onclick="navigate('blog/my-first-post')"&amp;gt;&lt;/code&gt; and then handle this in the JavaScript code. But... shucks pardner, what a chore and a non-natural way of writing websites. Additionally, you can't copy a link to a sub-page and send it to a friend. All content serving is based on scripts registering clicks.&lt;/p&gt;

&lt;p&gt;So, is there a way around this?&lt;/p&gt;

&lt;p&gt;Sure there is, otherwise I wouldn't make this huge buildup of tension leading to a hitchcockian plummet.&lt;/p&gt;

&lt;p&gt;Through hash routing.&lt;/p&gt;

&lt;h3&gt;
  
  
  So what are hashes?
&lt;/h3&gt;

&lt;p&gt;Ever notice a hashtag thingy in your browser's address bar? For example &lt;code&gt;www.example.com/index.html#introduction&lt;/code&gt; or something like that? Those are called fragment identifiers &lt;em&gt;(as they identify a specific fragment of the text - a heading, line, character, so on - but don't worry about that, we'll only focus on the basics)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;By default, they link to objects with set id. How does it work? Well, let's look at an example below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My cool website&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#intro"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Intro&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#about"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#summary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Summary&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"intro"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Introduction&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Intro text goes here...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"about"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;About the thing&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;About text goes here...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"summary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Summary&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Summary text goes here...&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever you click an anchor that has &lt;code&gt;href&lt;/code&gt; set to that hashtag thingy, the browser will scroll your window to the element with the same id as the hashtag &lt;em&gt;(or: fragment identifier)&lt;/em&gt;. Moreover, you can link another page and make it scroll the view on load to any named anchor in particular - simply by using &lt;code&gt;&amp;lt;a href="www.example.com/product.html#about"&amp;gt;&lt;/code&gt;. Neat, innit. Also, a reason why ids should be unique.&lt;/p&gt;

&lt;p&gt;But how does that bring us closer to implementing routing?&lt;/p&gt;

&lt;p&gt;See, the devil's in the details - whenever you try to follow a link to a fragment identifier that doesn't exist in the current page, nothing happens except for one thing. The address in the address bar of your browser changes. And that fires an event you can subscribe to.&lt;/p&gt;

&lt;p&gt;Now, if you subscribe to that event, you'll be able to fetch the fragment identifier part of the URL and based on that, you can script the behavior of the website &lt;em&gt;(loading new content and such)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Plenty websites use this kind of navigation &lt;em&gt;(even in form of hash routing)&lt;/em&gt;. Wikipedia lets you copy a link directly to a specific section of an article. Gmail links you to a specific folder in your email account. So on, so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  To sum it up
&lt;/h3&gt;

&lt;p&gt;Equipped with new knowledge, let's define client-side hash routing again.&lt;/p&gt;

&lt;p&gt;It's a method of navigating the website's structure &lt;em&gt;(different pages, different posts, etc)&lt;/em&gt; through custom URL addresses but without causing a website reload, since only fragment identifier anchors are used. Instead of reloading content, it relies on JavaScript to handle the process of fetching, processing and applying content to the existing view.&lt;/p&gt;

&lt;p&gt;An obvious downside is that while you can fetch the content from the webhost with ease, you can't really interact with it otherwise. After all, you'd have to implement the entirety of CRUD on the front-end part, with passwords being in the plain sight.&lt;/p&gt;

&lt;p&gt;So yeah, I am 100% positive you realize why this is a horrible, terrible idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, let's write the code (finally)&lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This part should be relatively short, as we won't be implementing many functionalities into our basic router. That's to &lt;del&gt;make sure I have something to keep writing about&lt;/del&gt; ensure that the code remains readable and easy to digest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Boilerplate and events
&lt;/h3&gt;

&lt;p&gt;Let's start with an empty project. We need a &lt;code&gt;index.html&lt;/code&gt; boilerplate &lt;em&gt;(or not even that, just the very basics)&lt;/em&gt; and a simple &lt;code&gt;app.js&lt;/code&gt; script patched into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&amp;gt; index.html

&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"app.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My awesome hashrouter&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yup, the body stays empty. That's it - the most basic HTML file I could think of. We'll do all our work in the &lt;code&gt;app.js&lt;/code&gt; file, so I'm not gonna denote what code goes where from now on.&lt;/p&gt;

&lt;p&gt;First, let's subscribe to the events that we need in order to make things work. There's actually two events we want to subscribe to. One is &lt;code&gt;load&lt;/code&gt;, which fires whenever the page is loaded. We need this to load the content whenever the user loads or reloads the website, either with or without the hash route.&lt;/p&gt;

&lt;p&gt;Second event is &lt;code&gt;hashchange&lt;/code&gt;. This one fires every time the hash &lt;em&gt;(fragment identifier)&lt;/em&gt; changes. We'll need this one because every non-external link on our website will point only to &lt;code&gt;index.html&lt;/code&gt;, just with different fragment part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChangeRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hashchange&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChangeRoute&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routing function scaffolding
&lt;/h3&gt;

&lt;p&gt;Now, what should the &lt;code&gt;onChangeRoute&lt;/code&gt; callback do? We'll write something really simple first, just a couple of calls we can fill out later on. We need a way to get the hash route from the address bar and we need to resolve it - get a callback function assigned to that route. And then we call that callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onChangeRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// first, we'll fetch the route from the address bar&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPathFromHashRoute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// then we'll resolve the route and get a callback function&lt;/span&gt;
    &lt;span class="c1"&gt;// and we need a way to register these routes too&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveRouteFromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// and now we call the callback&lt;/span&gt;
    &lt;span class="c1"&gt;// this should do whatever is needed to redraw the content of the page&lt;/span&gt;
    &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routes and registering them
&lt;/h3&gt;

&lt;p&gt;Let's tackle some sample route callbacks first, so we can immediately try them out and then plug them into &lt;code&gt;onChangeRoute&lt;/code&gt; logic when we eventually create it. These are probably the easiest to write too. We're just going to slap some basic text into our document view.&lt;/p&gt;

&lt;p&gt;We'll also define a separate function to apply whatever HTML we generate onto the document. So code looks nicer and is easier to refactor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;homeRouteHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h1&amp;gt;Home view&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;This is my home view. Wanna see the &amp;lt;a href="/#about-me"&amp;gt;about me&amp;lt;/a&amp;gt; section?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;aboutmeRouteHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h1&amp;gt;This is about me&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;I am J. Doe. I do websites for fun. This is my homepage. Go &amp;lt;a href="/"&amp;gt;home&amp;lt;/a&amp;gt;?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have this, we can test it out. Open &lt;code&gt;index.html&lt;/code&gt; in your browser and open developer tools. In the console, simply type &lt;code&gt;homeRouteHandler()&lt;/code&gt; and smash &lt;code&gt;[Enter]&lt;/code&gt;. You can do the same with &lt;code&gt;aboutmeRouteHandler()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;See? It just works!&lt;/p&gt;

&lt;p&gt;Now, let's assign these two handler callbacks to actual routes. We can simply store references to the functions in an array, their keys being the routes themselves. We could even store anonymous functions that way but this simply won't look pretty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;homeRouteHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about-me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;aboutmeRouteHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can assign same handlers to several routes, just so you know. This could be useful later on, when we decide to add more functionalities to our router. Mainly, parsing route parameters. But that's not on today's menu!&lt;/p&gt;

&lt;p&gt;Unfortunately, we can't test this out yet. We do have the routes registered, but we don't have a way to resolve them. As a sidenote, I'm using a naming system that's seen in Express.js and other apps that handle routes.&lt;/p&gt;

&lt;p&gt;This isn't the simplest way to go about this, as the hash will be prefixed with &lt;code&gt;#&lt;/code&gt; and not &lt;code&gt;/&lt;/code&gt;. But I'm doing this to maintain consistent naming across products &lt;em&gt;(like Express.js for example)&lt;/em&gt;. You could name them however you wish and process those however you want, of course.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolving routes
&lt;/h3&gt;

&lt;p&gt;Okay, we're back to our &lt;code&gt;onChangeRoute&lt;/code&gt; function. Let's write the logic for realsies now. First, for &lt;code&gt;getPathFromHashRoute&lt;/code&gt;, we need to extract the hash from the address bar. This is ridiculously simple, &lt;code&gt;window&lt;/code&gt; object holds a beautiful property called &lt;code&gt;location&lt;/code&gt;, which can tell you all about the contents of your address bar. We simply need the hash part, which luckily is available separatedly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPathFromHashRoute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's that simple.&lt;/p&gt;

&lt;p&gt;There's a safeguard in case the hash is not defined &lt;em&gt;(say someone requests &lt;code&gt;index.html&lt;/code&gt;)&lt;/em&gt;. And like I said, we're prefixing the route with a forward slash to keep route naming convention consistent across products.&lt;/p&gt;

&lt;p&gt;Resolving the hash route is just as simple to implement. We just fetch the callback from our array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveRouteFromPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we're safeguarding our router in case the requested route doesn't exist. In that case, we'll redirect the user to home view &lt;code&gt;"/"&lt;/code&gt;, which should be defined for every app anyway. Although you could definitely implement it differently, with a custom made 404 page or something else.&lt;/p&gt;

&lt;p&gt;And that's it, in not even 50 lines of JavaScript code, we've managed to implement a client-side hash router. Reopen the &lt;code&gt;index.html&lt;/code&gt; file and see the magic happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here we are, at the end of our short journey through the basics of routing. Our beautiful router could use some improvements for sure, it's very lacking compared to what Express.js and others offer. But it works and today, that's all we've been aiming for. In some future part of these series, we'll talk about the more complex stuff. Like handling parameters.&lt;/p&gt;

&lt;p&gt;And this... this wasn't a piece of cake to write. I'm still not sure how or why it works, but this is the reason why I'm writing these articles. Of course, I want to share this with you, dear reader. But writing is also a great way to cement your newfound knowledge.&lt;/p&gt;

&lt;p&gt;So in case you've learned something really fun, consider writing an article yourself. And otherwise, until next time. Thanks for reading! 💖&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>codenewbie</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Lessons from writing a dynamic CMS for static hosting</title>
      <dc:creator>Kacper Turowski</dc:creator>
      <pubDate>Wed, 21 Sep 2022 15:07:17 +0000</pubDate>
      <link>https://forem.com/turowski/lessons-from-writing-a-dynamic-cms-for-static-hosting-4a1g</link>
      <guid>https://forem.com/turowski/lessons-from-writing-a-dynamic-cms-for-static-hosting-4a1g</guid>
      <description>&lt;p&gt;&lt;em&gt;or: How I Learned To Stop Worrying and Love the Code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, what's this about?&lt;/p&gt;

&lt;p&gt;First things first, the #codenewbie tag is tad misleading, I have been coding for a short while already. But recently, I have blasted through a Udemy course on web-development. Aside from learning a lot about silkie chickens, I got to standardize and bring up-to-date my knowledge in a structured learning environment (in other words: I've binged the videos in correct order). And what better place to put new skills to use than in a personal project for my portfolio?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(the answer is: at a paid job, but anyway)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So what is it that I made?&lt;/p&gt;

&lt;p&gt;A CMS engine made in pure JavaScript, running entire logic client-side. No installation and setup (beyond editing config and content files, and uploading to webhost), no backend setup, no database. Can be hosted on a static host and serves dynamic content.&lt;/p&gt;

&lt;p&gt;Here's the shameless plug part: you can find the repo at &lt;a href="https://github.com/turowski-k/litesite-cms-js" rel="noopener noreferrer"&gt;https://github.com/turowski-k/litesite-cms-js&lt;/a&gt;. The code leaves much to be desired and is a result of manic, caffeine-boosted typing from midnight till sunrise, but it works. It's demo-able &lt;em&gt;(-ish)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Why did I do it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It was fun to create&lt;/li&gt;
&lt;li&gt;Why not?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How did I do it?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I might write a piece on it&lt;/li&gt;
&lt;li&gt;Some day&lt;/li&gt;
&lt;li&gt;Maybe&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The lessons learned
&lt;/h2&gt;

&lt;p&gt;It's about time we moved onto the clou of this article. The main takeaway. &lt;del&gt;The final nail in the coffin that will prove it's not just an Impostor Syndrome and I'm simply a horribly inept software developer who can't write clean code.&lt;/del&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Making it means understanding it
&lt;/h3&gt;

&lt;p&gt;Litesite's engine is &lt;em&gt;kinda&lt;/em&gt; like Express.js, only running in the requestee's browser. It comes with several downsides (ex. all files are ripe for scraping) and it's a... bunch of code held together with spit and duct tape. I didn't wanna create a product able to compete with anything widely used, but I sure did learn a lot.&lt;/p&gt;

&lt;p&gt;And not just things I would learn doing webdev the usual, modern way - bunch of &lt;code&gt;npm i&lt;/code&gt; commands followed by combining building blocks together and configuring them. Not that there's anything wrong with this way. It's more reliable usually and speeds up the process tenfold.&lt;/p&gt;

&lt;p&gt;Dunno about you, dear reader, but I always felt a lot of angst when working with these frameworks and modules. Because I didn't understand them, I had to rely on these mystery boxes - put in something, take out something new, pass it around to the next mystery box.&lt;/p&gt;

&lt;p&gt;After publishing the preview version of Litesite, I realized this anxiety is now gone. I've written routing, rendering and - frick - even very basic Markdown parsing. Is it efficient? No. Am I now more confident about working with modules? Yes, perhaps because now, in case of fire at the npm repository, I will be able to write all of this myself. Even if to plug it in only temporarily.&lt;/p&gt;

&lt;h3&gt;
  
  
  It makes you learn interesting things
&lt;/h3&gt;

&lt;p&gt;Bit obvious, innit? To make something you've never done before, you have to learn a lot. It's not the point, tho.&lt;/p&gt;

&lt;p&gt;I touched on things I wouldn't even have to worry about when creating a good ol' Express.js webapp. Routing for example, you just plug in the endpoints strings and callbacks into your app and let the framework handle it.&lt;/p&gt;

&lt;p&gt;Previous point touches on getting yourself more comfortable with the &lt;em&gt;"how does &lt;code&gt;this&lt;/code&gt; interact with that?"&lt;/em&gt; part, boosting your confidence. But it's also about learning &lt;em&gt;"what makes &lt;code&gt;this&lt;/code&gt; tick?"&lt;/em&gt; and even &lt;em&gt;"how do I make it tick?"&lt;/em&gt;. And about overcoming the challenges.&lt;/p&gt;

&lt;p&gt;See, Litesite runs client-side, so I couldn't implement &lt;em&gt;actual&lt;/em&gt; routing. For all I know, there's probably a regular http server running on the host. I send a GET to the endpoint, and I get what? The &lt;code&gt;index.html&lt;/code&gt; if there's one in the dir path of my "endpoint". Or a 404, which is more likely. So how can one get around this?&lt;/p&gt;

&lt;p&gt;Using hash routing. Which isn't a new concept, but not something I would've thought of in the first place. Obviously, after merely completing a single Udemy course (even if it proudly calls itself a "Bootcamp"), I didn't know where to get the current url from.&lt;/p&gt;

&lt;p&gt;Alas, a single google later, I was equipped with the knowledge about &lt;code&gt;window&lt;/code&gt; property. Basic knowledge, I realize that. Your friend's grandma's dog knows about &lt;code&gt;window.location&lt;/code&gt;, but I didn't - and I didn't know about many other concepts I learned along the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  The joy of coding
&lt;/h3&gt;

&lt;p&gt;When coding Litesite, I could have pulled some Node modules or embedded bunch of JS scripts off the Internet. Frick, the entire functionality covered by Litesite can be outsourced to an existing solution - like Wordpress or Drupal or even (closer to what my app is) Jenkins.&lt;/p&gt;

&lt;p&gt;But that's not the point!&lt;/p&gt;

&lt;p&gt;This project is &lt;em&gt;(was, more like, cause the most fun part is done)&lt;/em&gt; pure, unrestricted, manic coding. Sitting in front of the keyboard until wee hours of the morning and tapping away &lt;em&gt;(not thocking, unfortunately, I'm a filthy membrane user, sue me)&lt;/em&gt; line after line after line of code. All I've had was a &lt;a href="https://github.com/turowski-k/litesite-cms-js/issues/1" rel="noopener noreferrer"&gt;very simple idea on how things should be glued together&lt;/a&gt; and &lt;strong&gt;a lot&lt;/strong&gt; of empty space to fill it up with my creativity.&lt;/p&gt;

&lt;p&gt;And I loved it! Every second of it! Each night felt like it was 30 minutes long!&lt;/p&gt;

&lt;p&gt;But the article is about what this project has &lt;em&gt;taught&lt;/em&gt; me. Let's just say it also reminded me how ridiculously fun programming can be.&lt;/p&gt;

&lt;p&gt;And it absolutely can be fun, when it's not about creating yet another endpoint to pull data off the DB or changing the size of a modal so it fits client's requirements. Worse yet, by using &lt;code&gt;!important&lt;/code&gt; rule, because the codebase is 10 years old and nobody understands what part is responsible for what. Like we're bunch of code monkeys stuck in a cage, not really understanding &lt;em&gt;why&lt;/em&gt; we're not allowed to climb the ladder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Engineering/architecture experience
&lt;/h3&gt;

&lt;p&gt;This is another obvious point and I don't really have a witty comment about this one. I've had to plan out the entire app and write it myself, making sure everything connects with other parts correctly. Well, correctly might be a bit of an overstatement, the project is nowhere near cleanly written or perfectly designed, but...&lt;/p&gt;

&lt;p&gt;Experience comes from making bad decisions. And with experience, comes perspective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Honestly, I don't really know how to summarize this stuff I've somehow managed to write down &lt;em&gt;(and be happy about the quality of the writing)&lt;/em&gt;. It's not that long, go read it if you haven't, there's no tl;dr.&lt;/p&gt;

&lt;p&gt;So, I made a thing and I've had a lot of fun. But I also learned a lot. Like, &lt;strong&gt;a lot&lt;/strong&gt; lot. Am I happy with the end result?&lt;/p&gt;

&lt;p&gt;Meh, yes and no.&lt;/p&gt;

&lt;p&gt;Yes, because it was really fun to see the app come to life as I envisioned it &lt;em&gt;(mostly)&lt;/em&gt;. And it even works &lt;em&gt;(-ish)&lt;/em&gt; too. No, because equipped with my new experience, I see how I could've done this or that way better.&lt;/p&gt;

&lt;p&gt;But here's the kicker - I wouldn't be equipped with said experience if I didn't commit the crime of writing Litesite in the first place.&lt;/p&gt;

</description>
      <category>writing</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>codenewbie</category>
    </item>
  </channel>
</rss>
