<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>vixalien&apos;s site</title><description>Welcome to my website!</description><link>https://www.vixalien.com/</link><item><title>A love letter to mise</title><link>https://www.vixalien.com/blog/mise/</link><guid isPermaLink="true">https://www.vixalien.com/blog/mise/</guid><description>using mise to install packages on an immutable distro</description><pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I have been using &lt;a href=&quot;https://os.gnome.org/&quot;&gt;GNOME OS&lt;/a&gt;, as my daily driver.&lt;/p&gt;
&lt;p&gt;After being a seasoned Linux for long, dabbling in distros like &lt;a href=&quot;https://alpinelinux.org&quot;&gt;Alpine Linux&lt;/a&gt;, &lt;a href=&quot;https://archlinux.org&quot;&gt;Arch Linux&lt;/a&gt;, Fedora (and even Silverblue), I tried switching to something more &lt;em&gt;opinionated&lt;/em&gt; and that &quot;works by default&quot; all while being hard to break.&lt;/p&gt;
&lt;p&gt;And given my existing &lt;a href=&quot;https://teams.pages.gitlab.gnome.org/Websites/people.gnome.org/#jimmac:~:text=Angelo%20Verlain%20Shema&quot;&gt;relationship with GNOME&lt;/a&gt;, GNOME OS was a choice worth looking into.&lt;/p&gt;
&lt;p&gt;One feature of GNOME OS is that it is immutable (i.e. system files are read-only). It also doesn&apos;t ship with a package manager, so it doesn&apos;t have functionality built-in to install extra packages.&lt;/p&gt;
&lt;p&gt;You can install GUI Applications normally using &lt;a href=&quot;https://flathub.org/en&quot;&gt;Flathub&lt;/a&gt; (and Snap/AppImage), but installing non-GUI applications like development tools or CLI packages is not built-in.&lt;/p&gt;
&lt;p&gt;There are of course several solutions you can use, such as &lt;a href=&quot;https://brew.sh/&quot;&gt;homebrew&lt;/a&gt;, &lt;a href=&quot;https://gitlab.postmarketos.org/postmarketOS/coldbrew&quot;&gt;coldbrew&lt;/a&gt;, but today we will focus on &lt;a href=&quot;https://mise.jdx.dev/&quot;&gt;mise&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What is mise?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://mise.jdx.dev/&quot;&gt;mise&lt;/a&gt; pitches itself as &quot;One tool to manage languages, env vars, and tasks per project, reproducibly.&quot;&lt;/p&gt;
&lt;p&gt;However, I only use a fraction of it&apos;s functionality, in that I only use it to install packages.&lt;/p&gt;
&lt;h2&gt;How to install it?&lt;/h2&gt;
&lt;p&gt;The instructions are here: https://mise.jdx.dev/getting-started.html&lt;/p&gt;
&lt;p&gt;But essentially it&apos;s as easy as running this (remember to read the source of the installer first):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl https://mise.run | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Activating mise&lt;/h2&gt;
&lt;p&gt;Then you will need to &quot;activate&quot; mise, which essentially makes tools installed by mise available by modifying your &lt;code&gt;$PATH&lt;/code&gt; variable&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &apos;eval &quot;$(~/.local/bin/mise activate bash --shims)&quot;&apos; &amp;gt;&amp;gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The instructions above are for bash, so you will need to consult the docs to get instructions for your shell.&lt;/p&gt;
&lt;p&gt;You will need to re-login for the &lt;code&gt;mise&lt;/code&gt; command to be available, or open a new shell.&lt;/p&gt;
&lt;h3&gt;A note on shims&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Feel free to skip this section, as it&apos;s just an explainer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Also, note that the above command use the &lt;code&gt;--shims&lt;/code&gt; flag, which is NOT the default. It essentially means that mise will modify the &lt;code&gt;$PATH&lt;/code&gt; variable, instead of doing a weird thing where it will re-activate itself after each command you run.&lt;/p&gt;
&lt;p&gt;The non-shim way to activate mise is useful when you use mise to install different package versions across different repositories, but that sometimes breaks IDEs and is our of the scope of this blog post.&lt;/p&gt;
&lt;h2&gt;Installing packages&lt;/h2&gt;
&lt;p&gt;You can start installing your first package with &lt;code&gt;mise&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g java
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command installs &lt;code&gt;java&lt;/code&gt; globally (hence the &lt;code&gt;-g&lt;/code&gt; flag), which you can now confirm by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ java --version
openjdk 26.0.1 2026-04-21
OpenJDK Runtime Environment (build 26.0.1+8-34)
OpenJDK 64-Bit Server VM (build 26.0.1+8-34, mixed mode, sharing)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can install much more tools, of which you can find a non-complete list here: &lt;a href=&quot;https://mise-tools.jdx.dev/&quot;&gt;mise-tools&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, you can similarly install a specific major version of nodejs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g node@22
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or install the latest LTS version of node&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g node@lts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can be overlay specific&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g node@v25.9.0
mise use -g node@25.9.0 # this works too!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Searching&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;mise search&lt;/code&gt; to find packages.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise search typ
Tool       Description                                                                                                                            
typos      Source code spell checker. https://github.com/crate-ci/typos
typst      A new markup-based typesetting system that is powerful and easy to learn. https://github.com/typst/typst
typstyle   Beautiful and reliable typst code formatter. https://github.com/Enter-tainer/typstyle
quicktype  Generate types and converters from JSON, Schema, and GraphQL provided by https://quicktype.io. https://www.npmjs.com/package/quicktype
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Uninstalling&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mise unuse -g node
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Updating&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mise self-update # updating mise itself
mise up          # updating tools installed by mise
mise outdated    # checking if you have outdated tools
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Config File&lt;/h2&gt;
&lt;p&gt;Tools you install with mise globally will be saved in the file &lt;code&gt;~/.config/mise/config.toml&lt;/code&gt;, which you can commit to your dotfiles so you can have similar tools across different machines.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example of my mise config file at the time of writing this blog post.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.config/mise/config.toml
[tools]
bat = &quot;latest&quot;
btop = &quot;latest&quot;
bun = &quot;latest&quot;
caddy = &quot;latest&quot;
&quot;cargo:mergiraf&quot; = &quot;latest&quot;
deno = &quot;latest&quot;
difftastic = &quot;latest&quot;
doggo = &quot;latest&quot;
fastfetch = &quot;latest&quot;
fzf = &quot;latest&quot;
github-cli = &quot;latest&quot;
&quot;github:railwayapp/railpack&quot; = &quot;latest&quot;
glab = &quot;latest&quot;
helix = &quot;latest&quot;
java = &quot;latest&quot;
lazygit = &quot;latest&quot;
node = &quot;latest&quot;
&quot;npm:vscode-langservers-extracted&quot; = &quot;latest&quot;
oha = &quot;latest&quot;
pipx = &quot;latest&quot;
pnpm = &quot;latest&quot;
prettier = &quot;latest&quot;
rust = &quot;latest&quot;
scooter = &quot;latest&quot;
tmux = &quot;latest&quot;
usage = &quot;latest&quot;
yt-dlp = { version = &quot;latest&quot;, rename_exe = &quot;yt-dlp&quot; }
zellij = &quot;latest&quot;
&quot;github:patryk-ku/music-discord-rpc&quot; = { version = &quot;latest&quot;, asset_pattern = &quot;music-discord-rpc&quot; }
rclone = &quot;latest&quot;
mc = &quot;latest&quot;
go = &quot;latest&quot;
&quot;go:git.sr.ht/~migadu/alps/cmd/alps&quot; = &quot;latest&quot;
&quot;npm:localtunnel&quot; = &quot;latest&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the tools inside the config has changed, you can run the following comand to make mise re-install packages from the config file&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise install
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Mise Backends&lt;/h2&gt;
&lt;p&gt;Mise is able to install packages from multiple sources. These sources are called &quot;backends&quot; by mise.&lt;/p&gt;
&lt;p&gt;When you type &lt;code&gt;mise use -g node@22&lt;/code&gt;, it will resolve &lt;code&gt;node&lt;/code&gt; against the registry and figure out that the default backend for &lt;code&gt;node&lt;/code&gt; is &lt;code&gt;core&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Core&lt;/h3&gt;
&lt;p&gt;The default backend is called &lt;code&gt;core&lt;/code&gt; and tools from this backend are usually provided from the official source.&lt;/p&gt;
&lt;p&gt;Other tools that are available from &lt;code&gt;core&lt;/code&gt; include Node.js, Ruby, Python, etc...&lt;/p&gt;
&lt;p&gt;We could also have been explicit with the backend we want to use&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g core:node
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can find &lt;a href=&quot;https://mise.jdx.dev/core-tools.html&quot;&gt;a list of all core packages here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Aqua&lt;/h3&gt;
&lt;p&gt;You can also install packages from the &lt;a href=&quot;https://aquaproj.github.io/&quot;&gt;Aqua&lt;/a&gt; registry.&lt;/p&gt;
&lt;h3&gt;Language Package Managers&lt;/h3&gt;
&lt;p&gt;You can also install tools from their respective package managers. Here are a few examples&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You can install prettier, typescript, oxlint and other JavaScript/TypeScript tools published on the npm registry. Find the tools on &lt;a href=&quot;https://www.npmjs.com/search?q=keywords:cli&quot;&gt;npm&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g npm:prettier
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;pipx&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You can install black, poetry and other Python tools from pypi. Find the tools on &lt;a href=&quot;https://pypi.org/&quot;&gt;pypi&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g pipx:black
pipx:git+https://github.com/psf/black.git # from a github repo
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;cargo&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You can install cargo packages with this backed. You need to have rust installed beforehand though, which you can do with mise&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g rust
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install your packages&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g cargo:eza
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are more &lt;a href=&quot;https://mise.jdx.dev/dev-tools/backends/&quot;&gt;language package manager backends&lt;/a&gt; like: &lt;code&gt;gem&lt;/code&gt;, &lt;code&gt;go&lt;/code&gt; and more.&lt;/p&gt;
&lt;h3&gt;Github&lt;/h3&gt;
&lt;p&gt;You can install packages from Github directly, as long as the project you are trying to install from uses Github releases&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise use -g github:railwayapp/railpack
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;mise will usually auto-detect which asset you want to use, but you can also specify the asset glob in &lt;code&gt;~/.config/mise/config.toml&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tools]
&quot;github:patryk-ku/music-discord-rpc&quot; = { version = &quot;latest&quot;, asset_pattern = &quot;music-discord-rpc&quot; }
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>projects</category><category>explained</category></item><item><title>2024</title><link>https://www.vixalien.com/blog/2024/</link><guid isPermaLink="true">https://www.vixalien.com/blog/2024/</guid><description>One of the years ever.</description><pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello, reader.&lt;/p&gt;
&lt;p&gt;This is my latest attempt at trying to write a wrap-up of my year, and also a last-ditch attempt to save this blog from all the tumbleweeds, dust and spiderwebs.&lt;/p&gt;
&lt;p&gt;I&apos;ll be writing about what I did this year, and this year it&apos;s going to be a bit more structured and brief, atleast that&apos;s what I&apos;m aiming for. Please read this, and maybe give me some feedback!&lt;/p&gt;
&lt;h2&gt;Life&lt;/h2&gt;
&lt;p&gt;I&apos;d say that life has been pretty okay. I&apos;ve only got sick once towards the end of the year, and only for a few days.&lt;/p&gt;
&lt;h3&gt;memories.vixalien.com&lt;/h3&gt;
&lt;p&gt;I now host a simple website at [memories.vixalien.com], which you can visit to see a few pictures I took along this journey of life. I wish to improve it, possibly using the new &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Masonry_layout&quot;&gt;Masonry layout in CSS&lt;/a&gt;, so that the images can display better, and faster. I might also try rewriting the site in Astro, to get rid of all the JavaScript. You can read more about how it works &lt;a href=&quot;https://www.vixalien.com/blog/river&quot;&gt;in this blog post I wrote earlier this year about &lt;code&gt;river&lt;/code&gt;&lt;/a&gt;, the name of the metaframework it uses under the hood.&lt;/p&gt;
&lt;h3&gt;Budgeting&lt;/h3&gt;
&lt;p&gt;I started using &lt;a href=&quot;https://ynab.com/&quot;&gt;YNAB&lt;/a&gt; to make budgets and track my personal expenses. I wanted to do it to reduce spending on luxuries instead of saving up, and I would say it paid a huge role. Actually tracking your expenses and knowing where your money goes is such an eye-opening experience, and you will quickly realise how much money you spend on the &quot;small things&quot; and on the &quot;I need to take care of myself&quot; expenses.&lt;/p&gt;
&lt;p&gt;The next step of YNAB is actually budgeting, instead of just tracking expenses. To be totally frank with you, this is not my strongest suit, as I still adjust the budget from time to time to cover overspending in a &lt;em&gt;wants&lt;/em&gt; category.&lt;/p&gt;
&lt;p&gt;If you want to try out budgeting for yourself, I recommend you follow YNAB&apos;s 4 rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Give every dollar a job&lt;/li&gt;
&lt;li&gt;Embrace your true expenses&lt;/li&gt;
&lt;li&gt;Roll with the punches&lt;/li&gt;
&lt;li&gt;Age Your Money&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can &lt;a href=&quot;https://www.youtube.com/watch?v=R4VbZCxDDvY&quot;&gt;learn more about the 4 rules with Hannah&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Social&lt;/h2&gt;
&lt;h3&gt;Twitter/X&lt;/h3&gt;
&lt;p&gt;This year, I broke away from Twitter/X, as it has been... well, unusable recently. Instead, I switched to Mastodon, where you can follow me as &lt;a href=&quot;https://mas.to/@vixalientoots&quot;&gt;@vixalientoots@mas.to&lt;/a&gt;. My fediverse client of choice is &lt;a href=&quot;https://phanpy.social/&quot;&gt;Phanpy&lt;/a&gt; on desktop and mobile linux, and &lt;a href=&quot;https://apps.apple.com/us/app/ice-cubes-for-mastodon/id6444915884&quot;&gt;Ice Cubes&lt;/a&gt; on my phone.&lt;/p&gt;
&lt;h3&gt;Instagram&lt;/h3&gt;
&lt;p&gt;Instagram is probably the social media I use the most. I use it to sink a lot of my important hours watching reels, chatting with friends, and seldomly posting about pictures I take.&lt;/p&gt;
&lt;p&gt;I took a brief break in the timeframe northern hemisphere folks call summer. We call that season the long dry season, because we are close to the equator, and our seasons are not influenced by the tilt of the earth, but rather due to the rainfall patterns. Anyway, during that break I accidentally deleted my account instead of disabling it, and made the shocking realisation a few days after the 30-day window Instagram gives you before deleting your account for good. This was bittersweet, but I made the (bad) decision to come back to Instagram again, as my life was honestly feeling very empty.&lt;/p&gt;
&lt;h2&gt;School&lt;/h2&gt;
&lt;h3&gt;Uni&lt;/h3&gt;
&lt;p&gt;This year I also tried being more serious and attentive to classes, instead of the usual lazy and unproductive self I am. This work paid off very well, and I&apos;m no longer in the lower ranks and actually make an effort to understand course concepts before moving on. If you are in University too, or doing something else, I would recommend you give it your all.&lt;/p&gt;
&lt;p&gt;This came after a rather stoic realisation that I&apos;m actually paying to be at University, and that when I&apos;m not giving it my all, I&apos;m actually losing time and money, and you know what they say: « Time wasted does not come back ». Surely enough, money spent does not come back either!!&lt;/p&gt;
&lt;h2&gt;Open Source&lt;/h2&gt;
&lt;h3&gt;GNOME&lt;/h3&gt;
&lt;p&gt;I&apos;ve been contributing to GNOME Projects for a while now, usually making bug reports, contributing to some libraries and applications (sometimes unsucessfully), writing some of my own GNOME apps, and more.&lt;/p&gt;
&lt;p&gt;The year started of well, &lt;a href=&quot;https://discourse.gnome.org/t/new-gnome-foundation-and-emeritus-members-2024-1/18779&quot;&gt;because I became a GNOME Foundation Member&lt;/a&gt; which is a very notable title (at least for me), and I feel like my contributions are now more valued.&lt;/p&gt;
&lt;p&gt;I did apply and get accepted for Google Summer of Code in the... summer! It was quite a nice experience initially, since I got to work on Workbench, specifically adding TypeScript support to the app. I would implore you to check the application out if you want to make desktop applications (linux, mac, windows and &lt;a href=&quot;https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/7555&quot;&gt;android soon?&lt;/a&gt;) using GTK. The application allows you to write applications in many languages, including Rust, Vala, JavaScript, Python and now TypeScript. The application was developed by Sonny Piers, an amazing person, who was also my mentor for the Google Summer of Code intern, at least initially. I wrote about &lt;a href=&quot;https://www.vixalien.com/blog/workbench-typescript&quot;&gt;my experience in GSoC in a blog post you can also read&lt;/a&gt;, if you&apos;re interested.&lt;/p&gt;
&lt;p&gt;Things took a weird turn when Sonny was inexplicably and mysteriously banned from the GNOME Foundation, a sudden move that left me without a mentor for a while before they found a replacement. We (GNOME Contributors) have not yet got closure about the ban, even after we voted for new GNOME Foundation Board members, and this honestly led to the reduced trust and faith I now have in the GNOME Foundation. A side effect of this is that I&apos;ve reduced my contributions to GNOME, as I&apos;m no longer ecstatic to be a contributor anymore.&lt;/p&gt;
&lt;h3&gt;My Linux Apps&lt;/h3&gt;
&lt;p&gt;I write and maintain(ed) about 3 linux GTK applications, which have had limited contributions because of said previous issues with the GNOME Foundation.&lt;/p&gt;
&lt;p&gt;The first, &lt;a href=&quot;https://flathub.org/apps/com.vixalien.sticky&quot;&gt;Sticky Notes&lt;/a&gt;, is basically on life support, as I didn&apos;t really work on it much this year. Nevertheless, I&apos;m still sending out maintenance updates, usually just updating the app for the new GNOME SDK, but never implementing any new features. I did plan and work on a new branch targetting markdown for a while, but never finalised it, and I don&apos;t think I have the energy, will and time to work on it anymore. Sorry folks, maybe next year!&lt;/p&gt;
&lt;p&gt;The second application I wrote, &lt;a href=&quot;https://github.com/vixalien/muzika&quot;&gt;Muzika&lt;/a&gt; is now officially dead, &lt;a href=&quot;https://mas.to/@vixalientoots/113555145244776471&quot;&gt;as I wrote about in detail in a mastodon post&lt;/a&gt;. This is due to a lot of factors, but mainly due to YouTube breaking the current mechanism we used to log users in, and me not willing to play the cat and mouse game anymore. This is very unfortunate, as Muzika was basically my most famous application, and I&apos;ve sunk a lot of hours into it, which made it sad to go. Anyway, we move!&lt;/p&gt;
&lt;p&gt;The last, and newest application is called &lt;a href=&quot;https://apps.gnome.org/en-GB/Decibels/&quot;&gt;Decibels&lt;/a&gt;, and as you can probably guess from the name, is an audio (NOT music!!) player. It is a minimal application and requires minimal maintenance, which I like. I also have a co-maintainer (hey David!), which makes maintaining the app a breeze. It&apos;s already a GNOME Circle (basically recognised as a good GNOME application), and it&apos;s incubating to be part of the Core apps, which means you might soon have it installed by default in a GNOME installation!&lt;/p&gt;
&lt;h2&gt;Work&lt;/h2&gt;
&lt;h3&gt;Karabo&lt;/h3&gt;
&lt;p&gt;It&apos;s that time in University where you have to make a project, partly to get grades, and partly to validate all the hours you&apos;ve sunk doing coursework or attending lectures.&lt;/p&gt;
&lt;p&gt;My venture is called Karabo, and I&apos;ve registered the company and website here: &lt;a href=&quot;https://karabo.io&quot;&gt;karabo.io&lt;/a&gt;. I will probably write about it more, as time goes on.&lt;/p&gt;
</content:encoded><category>life</category></item><item><title>Workbench + TypeScript</title><link>https://www.vixalien.com/blog/workbench-typescript/</link><guid isPermaLink="true">https://www.vixalien.com/blog/workbench-typescript/</guid><description>Quickly prototyping and iterating on typesafe GNOME apps.</description><pubDate>Mon, 26 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Prologue&lt;/h2&gt;
&lt;p&gt;When I started writing &lt;a href=&quot;https://www.gtk.org/&quot;&gt;GTK&lt;/a&gt; apps (GTK is a cross-platform toolkit for creating
graphical user interfaces. Particularly common with Linux app developers), I
noticed that writing apps was noticeably more difficult.&lt;/p&gt;
&lt;p&gt;From the perspective of a web developer: Writing desktop apps is hard. Not
entirely because the languages used in writing desktop apps are harder, but I&apos;d
argue that it&apos;s because of the accomodations and tools web developers have at
their disposal.&lt;/p&gt;
&lt;h3&gt;TypeScript&lt;/h3&gt;
&lt;p&gt;One of such tools that help massively with developer experience in the web
development world is &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt;. It&apos;s a programming language introduced by
Microsoft in 2012. It&apos;s a superset of JavaScript, but adds static types with
optional type annotations.&lt;/p&gt;
&lt;p&gt;This means at a basic level it&apos;s just JavaScript, with the added possibility of
adding types to your variables and parameters. So if you had code like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function sayHello(name) {
  console.log(`Hello ${name}. Your name is ${name.length} characters long.`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would instead become:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Notice the `: string`
function sayHello(name: string) {
  console.log(`Hello ${name}. Your name is ${name.length} characters long.`);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For platform developers, this looks seemingly normal, as most of the languages
they use are probably already typed (C, C++, Rust, and more). But for JavaScript
developers, this was a very novel idea and as you can probably guess, it reduces
the bugs you write in the first place by a huge margin.&lt;/p&gt;
&lt;p&gt;In the example above, in the JavaScript code you could pass a number instead of
a string to the &lt;code&gt;sayHello&lt;/code&gt; function. JavaScript will happily execute that, but
then you will run into bugs sooner or later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sayHello(12);
// Hello 12. Your name is undefined characters long.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TypeScript will easily catch these errors, though:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sayHello(12);
// ts: Argument of type &apos;number&apos; is not assignable to parameter of type &apos;string&apos;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is just the surface of what&apos;s available with TypeScript, but at this point
I think you and me both can see how it massively improves the developer&apos;s
experience.&lt;/p&gt;
&lt;h3&gt;Developing Desktop apps is hard&lt;/h3&gt;
&lt;p&gt;Now writing desktop apps is hard. Usually, developers use languages like C to
write apps. One of the first shortcomings I found is that when you are writing
apps this way, you first need to write the desired changes, compile your app and
then run the app to see your changes. This is in sharp contrast with the way web
development works. There is a neat feature called
&lt;a href=&quot;https://webpack.js.org/concepts/hot-module-replacement/&quot;&gt;Hot Module Replacement&lt;/a&gt; where the compiler will replace the changed modules in your
code while the application is running, without reloading anything. We don&apos;t
(AFAIK) have this feature in GTK app development yet (this was actually supposed
to be my other proposal) but we can achieve something similar with a neat little
app called &lt;a href=&quot;https://apps.gnome.org/Workbench/&quot;&gt;Workbench&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Workbench&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/workbench-typescript/workbench-main-window.png&quot; alt=&quot;Workbench&apos;s main window&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://apps.gnome.org/Workbench/&quot;&gt;Workbench&lt;/a&gt; is an app that simply allows you to write the UI interface of your
app, write some accompanying behavior code and with a simple click of the &quot;Run&quot;
button, the UI you are iterating on becomes readily available. When you are done
working on this particular piece of UI, you are supposed to copy the code you&apos;ve
written into your application, knowing the code will work and hence skipping the
complex iteration loops.&lt;/p&gt;
&lt;p&gt;I&apos;ve used Workbench quite consistently while working on my GTK applications. GTK
(and the underlying libraries) are quite awesome because they allow you to write
apps in a number of different languages (called language bindings). And you can write GTK
apps using your favourite language, whether it&apos;s JavaScript, Rust, C, C++,
Python and more. Since I was coming from web development, I chose to use
JavaScript for writing GTK apps as it was quite familiar to me already.&lt;/p&gt;
&lt;h3&gt;TypeScript with GTK&lt;/h3&gt;
&lt;p&gt;But in the GTK app development ecosystem,
&lt;a href=&quot;https://blogs.gnome.org/christopherdavis/2022/08/25/trying-typescript-for-gnome-apps/&quot;&gt;there was a growing interest to write
apps in TypeScript instead of JavaScript&lt;/a&gt; where possible, and I
was intrigued as well. I started using TypeScript instead of JavaScript for my
apps, and loved it. There was one piece missing however, Workbench, the code
playground, still did not support TypeScript. To make my TypeScript code work in
Workbench, I used to compile the TypeScript code into JavaScript, put it in
Workbench, then rewrite the code back into TypeScript for my app. Very
inconvinient.&lt;/p&gt;
&lt;p&gt;At some time, I began talking with &lt;a href=&quot;https://sonny.re&quot;&gt;Sonny Piers&lt;/a&gt;, and we began talking
about possibly adding TypeScript support to Workbench, to allow developers to
use the language rapidly gaining foothold in the GNOME/GTK app development
arena. This would also help welcome new developers into GNOME, as more and more
web developers are familiar with TypeScript and would provide a familiar
platform language to them.&lt;/p&gt;
&lt;h2&gt;GSoC&lt;/h2&gt;
&lt;p&gt;With that in mind, I wrote a &lt;a href=&quot;https://summerofcode.withgoogle.com/&quot;&gt;Google Summer of Code&lt;/a&gt; proposal titled &quot;Add
TypeScript Support to Workbench&quot;. As early as hitting the &quot;Send&quot; button, I
started working on the project.&lt;/p&gt;
&lt;h3&gt;Adding the TypeScript View to Workbench&lt;/h3&gt;
&lt;p&gt;The first task I ever did was to
&lt;a href=&quot;https://github.com/workbenchdev/Workbench/pull/938&quot;&gt;add an option for &quot;TypeScript&quot; in the Workbench app&lt;/a&gt;. This was relatively
simple enough in the sense that all it did was add an option to the dropdown at
the top allowing the user to select &quot;TypeScript&quot; as their preferred language.&lt;/p&gt;
&lt;p&gt;I used the pre-existing &lt;a href=&quot;https://github.com/flathub/org.freedesktop.Sdk.Extension.typescript&quot;&gt;TypeScript SDK Extension&lt;/a&gt; written
Christopher Davis. This SDK Extension provided the &lt;code&gt;tsc&lt;/code&gt; (TypeScript Compiler)
and &lt;code&gt;typescript-language-server&lt;/code&gt; executables to the Flatpak environment
Workbench works in.&lt;/p&gt;
&lt;h3&gt;Compiling the TypeScript to JavaScript&lt;/h3&gt;
&lt;p&gt;Even though there was a TypeScript View, the TypeScript code you entered was
simply interpreted as Javascript. This means that you were not able to use
TypeScript language features just yet, and hence the next step to implement
would be to
&lt;a href=&quot;https://github.com/workbenchdev/Workbench/pull/941&quot;&gt;compile TypeScript into JavaScript and then
execute the JavaScript code&lt;/a&gt;
when you clicked the &quot;Run&quot; button.&lt;/p&gt;
&lt;p&gt;This was all trivial thanks to the aforementioned TypeScript SDK Extension. All
we had to do was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write the TypeScript code into a &lt;code&gt;main.ts&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Compile the TypeScript code into a &lt;code&gt;main.js&lt;/code&gt; file using &lt;code&gt;tsc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Import the &lt;code&gt;main.js&lt;/code&gt; file and then execute it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this in place, we had real support for running TypeScript in Workbench.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/workbench-typescript/typescript-in-workbench.png&quot; alt=&quot;TypeScript code running in Workbench&quot; title=&quot;Source --- https://floss.social/@sonny/112436298323911287&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Adding GObject modules types&lt;/h3&gt;
&lt;p&gt;With that set in place, there was still an issue, and that, the types of
&lt;a href=&quot;https://docs.gtk.org/gobject/&quot;&gt;GObject&lt;/a&gt; modules.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.gtk.org/gobject/&quot;&gt;GObject&lt;/a&gt;, or the GLib Object System is is a library providing a portable object
system and transparent cross-language interoperability.
&lt;a href=&quot;https://docs.gtk.org/girepository/&quot;&gt;GObject Introspection&lt;/a&gt; (GI or GIR) is a related library providing access to
typelibs and introspection data which describes C APIs.&lt;/p&gt;
&lt;p&gt;Essentialy, this system provides a way to import and use &quot;modules&quot; in many
languages. &lt;a href=&quot;https://www.gtk.org/&quot;&gt;Gtk&lt;/a&gt; and many other related libraries like GLib, Pango, Gio are all
GI modules and you import them to use them. For example, here is how you create
a Gtk Label in GJS (GNOME JavaScript):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Gtk from &quot;gi://Gtk?version=4.0&quot;;

const label = new Gtk.Label({
  label: &quot;Hello, World!&quot;,
});

// present the label
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For TypeScript to be useful, we need to provide &quot;TypeScript definition files&quot;
(&lt;code&gt;.d.ts&lt;/code&gt;) that will essentially tell us the type of the &lt;code&gt;gi://Gtk?version=4.0&lt;/code&gt;
and other imports. Because these files must be statically available, we need a
way to source them.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;gi.ts&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Thankfully for us, there was a project named &lt;a href=&quot;https://github.com/gjsify/gi.ts&quot;&gt;gi.ts&lt;/a&gt; by Evan Welsh that did
exactly that. It was a binary that you would execute and point it to a GIR file,
say &lt;code&gt;/usr/share/gir-1.0/Gtk-4.0.gir&lt;/code&gt; and then it would generate the TypeScript
definition files (&lt;code&gt;gtk4.d.ts&lt;/code&gt;) and all their dependencies as we needed.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;gi-typescript-definitions&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Christopher Davis then created a repository called
&lt;a href=&quot;https://gitlab.gnome.org/BrainBlasted/gi-typescript-definitions&quot;&gt;gi-typescript-definitions&lt;/a&gt;
where they would regenerate the typescript definition files regularly, and we
would consume the definition files instead of regenerating them ourselves.&lt;/p&gt;
&lt;h4&gt;Adding the TypeScript generation files to Workbench&lt;/h4&gt;
&lt;p&gt;I hence added &lt;code&gt;gi-typescript-definitions&lt;/code&gt; as a submodule in Workbench, and
pointed the typescript compiler to these definition files. And with everything
all together, &lt;a href=&quot;https://github.com/workbenchdev/Workbench/pull/946&quot;&gt;we now had Typechecking in Workbench&lt;/a&gt; and as a side effect,
we had diagnostics and completions too!!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/workbench-typescript/typechecking-in-workbench.png&quot; alt=&quot;Type diagnostics and completions in Workbench&quot; title=&quot;Source --- https://floss.social/@sonny/112786875907926117&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Typechecking with JavaScript??&lt;/h3&gt;
&lt;p&gt;As it turns out, we could use the new features (diagnostics and completions)
from TypeScript in JavaScript! This is by using a special &lt;code&gt;jsconfig.json&lt;/code&gt; file
and using the aforementioned &lt;code&gt;typescript-language-server&lt;/code&gt; for JavaScript files
too, which it supports.&lt;/p&gt;
&lt;p&gt;This means
&lt;a href=&quot;https://github.com/workbenchdev/Workbench/pull/972&quot;&gt;we got similar diagnostics and completions even for JavaScript!!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/workbench-typescript/typechecking-javascript.png&quot; alt=&quot;Typechecking Javascript? Heck yeah!!&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;ts-for-gir&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Remember the tool named &lt;a href=&quot;https://github.com/gjsify/gi.ts&quot;&gt;gi.ts&lt;/a&gt; used to generate typescript definition files I
talked about earlier, turns out there is an exceedingly similar tool named
&lt;a href=&quot;https://github.com/gjsify/ts-for-gir&quot;&gt;ts-for-gir&lt;/a&gt; by Pascal Garber that does the same thing!!&lt;/p&gt;
&lt;p&gt;After the developers of both tools noticed the other one, they started working
on &lt;a href=&quot;https://github.com/gjsify/ts-for-gir/issues/120&quot;&gt;marrying the two projects together&lt;/a&gt; and called
the resulting project &lt;code&gt;ts-for-gir&lt;/code&gt; v4, which is still in beta at the time of
writing.&lt;/p&gt;
&lt;p&gt;I knew that I had to start working on a way to use types generated by this new
version of &lt;code&gt;ts-for-gir&lt;/code&gt; instead of relying on &lt;code&gt;gi.ts&lt;/code&gt;, which has now been
archived and hence not receiving updates. So I now have a
&lt;a href=&quot;https://github.com/workbenchdev/Workbench/pull/979&quot;&gt;draft PR that switches from &lt;code&gt;gi.ts&lt;/code&gt; to &lt;code&gt;ts-for-gir&lt;/code&gt;&lt;/a&gt;, but the question of
&lt;a href=&quot;https://github.com/workbenchdev/Workbench/pull/979#issuecomment-2299187978&quot;&gt;how we generate the typescript definition files is still unsolved&lt;/a&gt;
because if you remember correctly, we were using pregenerated typescript
definition files from &lt;code&gt;gi-typescript-definitions&lt;/code&gt; and now we will need to
generate them ourselves in Workbench. Unfortunately,
&lt;a href=&quot;https://github.com/workbenchdev/Workbench/issues/980&quot;&gt;there is currently an issue blocking this&lt;/a&gt;, but hopefully it will be
resolved soon.&lt;/p&gt;
&lt;h3&gt;Porting the demos&lt;/h3&gt;
&lt;p&gt;Workbench has a huge library of demos, which I would also like to recommend the next
time you need to familiarise yourself with how a certain platform feature works.
The &lt;a href=&quot;https://github.com/workbenchdev/demos&quot;&gt;demos library&lt;/a&gt; has different code samples in various languages:
JavaScript, Vala, Rust and Python.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/workbench-typescript/demos.png&quot; alt=&quot;Workbench demos library&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My work would hence be incomplete without
&lt;a href=&quot;https://github.com/workbenchdev/demos/pull/201&quot;&gt;porting all the JavaScript demos to TypeScript&lt;/a&gt;, a noticeably complex
task that is currently ongoing.&lt;/p&gt;
&lt;p&gt;Because TypeScript compiles into JavaScript, we chose to remove all JavaScript
demos and instead add TypeScript demos, and this way we can compile all the
TypeScript demos into JavaScript at build time, and this way we get both TS and
JS demos for the price of one. Pretty cool, right?&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;I hope you enjoy the TypeScript support in Workbench that will hopefully be
available in the next release, and start writing GNOME apps!! I also hope you liked
this write-up I made about all this and don&apos;t hesitate to reach out to me if you
have any questions or feedback.&lt;/p&gt;
&lt;h3&gt;Acknowledgements&lt;/h3&gt;
&lt;p&gt;I would like to thank &lt;a href=&quot;https://summerofcode.withgoogle.com/&quot;&gt;Google Summer of Code&lt;/a&gt; for providing me with the
opportunity of working on this, and the &lt;a href=&quot;https://gnome.org&quot;&gt;GNOME Foundation&lt;/a&gt; which
administered the programme.&lt;/p&gt;
&lt;p&gt;I would like to thank my mentors, Sonny Piers and Andy Holmes for providing me
with unceasing feedback and guidance, I really appreaciate them!!&lt;/p&gt;
&lt;p&gt;I would also like to thank Christopher Davis, for their early experimentations
on working with TypeScript on GNOME, &lt;code&gt;gi-typescript-definitions&lt;/code&gt;, the TypeScript
SDK Extension and many many more. You&apos;re awesome!&lt;/p&gt;
&lt;p&gt;I would also like to extend my thanks to Pascal Garber, for working on
&lt;code&gt;ts-for-gir&lt;/code&gt;, their feedback and putting up with me on my wild feature requests
😅.&lt;/p&gt;
&lt;p&gt;Finally, let me thank all the other people that helped me in the internship,
from Bharat, a fellow GSoCer to the whole GNOME community for helping me out
when stuck.&lt;/p&gt;
&lt;p&gt;Thank you everyone!&lt;/p&gt;
</content:encoded><category>linux</category></item><item><title>My Alpine Setup</title><link>https://www.vixalien.com/blog/an-alpine-setup/</link><guid isPermaLink="true">https://www.vixalien.com/blog/an-alpine-setup/</guid><description>Exploring a minimal but nice alpine setup.</description><pubDate>Sun, 18 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This is a guide to install alpine, based on my own likings. It is a relatively
easy install.&lt;/p&gt;
&lt;p&gt;This installation guide is very inspired and based on
&lt;a href=&quot;https://whynothugo.nl/journal/2023/11/19/setting-up-an-alpine-linux-workstation/&quot;&gt;Hugo&apos;s Installation Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;(Some) security considerations&lt;/h2&gt;
&lt;p&gt;This install is a bit more secure because it uses an encrypted filesystem (LUKS
on top of LVM).&lt;/p&gt;
&lt;p&gt;The whole is configured to work with UEFI + Secureboot, and the disk is
automatically decrypted with the TPM (this secure chip inside your laptop) using
&lt;a href=&quot;https://wiki.archlinux.org/title/Clevis&quot;&gt;Clevis&lt;/a&gt;, meaning, if you boot on your laptop, your file contents will be
decrypted automatically. You will need to manually enter your password if the
storage drive is used outside of your computer (evil maid attack).&lt;/p&gt;
&lt;p&gt;To secure your laptop better, enable Secure Boot, and set a very strong
BIOS/UEFI password.&lt;/p&gt;
&lt;p&gt;A more secure approach would be to use something like &lt;a href=&quot;https://systemd.io/HOME_DIRECTORY/&quot;&gt;systemd-homed&lt;/a&gt; that
encrypts each user&apos;s home directory separately, but that requires systemd...
There&apos;s a similar tool for alpine called &lt;a href=&quot;https://wiki.archlinux.org/title/pam_mount&quot;&gt;pam_mount&lt;/a&gt;, but it&apos;s not compatible
with home directories created with systemd-homed, and I haven&apos;t figured a very
nice way to make it work on Alpine.&lt;/p&gt;
&lt;h2&gt;Setup the installer&lt;/h2&gt;
&lt;p&gt;Download the alpine installation disk from the
&lt;a href=&quot;https://alpinelinux.org/downloads/&quot;&gt;Alpine downloads page&lt;/a&gt;, flash it to a USB and boot it up.&lt;/p&gt;
&lt;h2&gt;Configuring Alpine&lt;/h2&gt;
&lt;p&gt;Alpine has a very nice &lt;code&gt;setup-alpine&lt;/code&gt; script that setups the Alpine
installation. This is different from Arch where you&apos;d need to do everything
manually. We are going to do it to configure most stup, until we arrive to the
Partitioning stage.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keyboard: &lt;code&gt;us&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keyboard variation: &lt;code&gt;us&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Hostname: &lt;code&gt;name.vixalien.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Network adapter: &lt;code&gt;wlan0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Wifi network.&lt;/li&gt;
&lt;li&gt;Wifi password.&lt;/li&gt;
&lt;li&gt;IP address: &lt;code&gt;dhcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Networks: &lt;code&gt;done&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Manual network configuration: &lt;code&gt;n&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;root password: &lt;code&gt;stronkpassword&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Time zone: &lt;code&gt;Africa/Kigali&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;User account: &lt;code&gt;alien&lt;/code&gt;. This user will automatically be a member of the wheel
group, and has (by default) privileges to use &lt;code&gt;doas&lt;/code&gt; (alpine&apos;s alternative to
&lt;code&gt;sudo&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The defaults are fine for all remaining steps.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Make Sure to stop the &lt;code&gt;setup-alpine&lt;/code&gt; tool when they ask for disk, as we will
do it manually.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Partitioning&lt;/h2&gt;
&lt;p&gt;Firstly, we will need to create a boot partition (aka EFI System Partition) to
hold all our kernel information and bootloader (optional). Please make the
partition 500 MB large, otherwise some distros like Fedora might refuse it, and
the space might run out faster than you think if you decide to make backups of
your UKIs or go distro-hopping, so 1GB is actually a better recommendation.&lt;/p&gt;
&lt;p&gt;Then we are going to create an &lt;a href=&quot;https://wiki.archlinux.org/title/LVM&quot;&gt;LVM&lt;/a&gt; partition that will be encrypted and have
volumes for all our other partitions (alpine, home, swap...). I (&apos;d) like to
setup one partition called &lt;code&gt;linux&lt;/code&gt; so it can be reused by my other linux
installations.&lt;/p&gt;
&lt;h3&gt;Installing the necessary packages.&lt;/h3&gt;
&lt;p&gt;For partitioning, we will use gdisk. Let&apos;s also install other packages for ext4
and btrfs filesystems.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add lsblk gptfdisk btrfs-progs e2fsprogs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Activate the btrfs kernel module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;modprobe btrfs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cryptsetup is needed for LUKS encryption&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add cryptsetup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LVM&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add lvm2
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Overwriting the disk&lt;/h3&gt;
&lt;p&gt;It might be a good idea to overwrite the disk using a tool like &lt;code&gt;haveged&lt;/code&gt; to
clear any leftover data.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TODO: there is probably an alternative for SSDs&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Setup Disks&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;gdisk /dev/nvme0n1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;n&lt;/code&gt; to create 2 partitions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;EFI partition. size: &lt;code&gt;512M&lt;/code&gt;. GUID: &lt;code&gt;ef00&lt;/code&gt; (EFI System Partition)&lt;/li&gt;
&lt;li&gt;LVM partition. size: &lt;code&gt;(leave blank)&lt;/code&gt;. GUID: &lt;code&gt;8309&lt;/code&gt; (Linux LUKS)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;For more info about gdisk Hex codes see
https://wiki.archlinux.org/title/GPT_fdisk#Partition_type&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Populate &lt;code&gt;/dev&lt;/code&gt; with new partitions:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;partprobe /dev/nvme0n1&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Identify your partitions&lt;/h3&gt;
&lt;p&gt;Find your partition names using &lt;code&gt;lsblk&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NAME         MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
nvme0n1      259:0    0 511.9G  0 disk  
├─nvme0n1p1  259:1    0   500M  0 part  
└─nvme0n1p2  259:2    0 511.5M  0 part
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, &lt;code&gt;/dev/nvme0n1p1&lt;/code&gt; is the EFI partition, while &lt;code&gt;/dev/nvme0n1p2&lt;/code&gt; will
be our LUKS partition.&lt;/p&gt;
&lt;h3&gt;Configuring LUKS&lt;/h3&gt;
&lt;p&gt;This step will ask for a password to encrypt your whole disk with. Remember it,
and make it strong.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cryptsetup luksFormat /dev/nvme0n1p2
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You might want to familiarise yourself with
&lt;a href=&quot;https://wiki.archlinux.org/title/Dm-crypt/Device_encryption#Encryption_options_for_LUKS_mode&quot;&gt;different LUKS options&lt;/a&gt;. I find the defaults okay.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Open the LUKS partition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cryptsetup luksOpen /dev/nvme0n1p2 lvmcrypt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;LVM Physical and Logical Volumes&lt;/h3&gt;
&lt;p&gt;Create a physical volume&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pvcreate /dev/vg0/lvmcrypt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a virtual volume named &lt;code&gt;vg0&lt;/code&gt; (or something else, this one is memorable)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vgcreate vg0 /dev/vg0/lvmcrypt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Create partitions&lt;/h4&gt;
&lt;p&gt;Swap partition. I have a 32GB RAM laptop:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lvcreate -L 32G vg0 -n swap # I have a 32GB RAM laptop
lvcreate -L 50G vg0 -n alpine # root
lvcreate l 100%FREE vg0 -n home
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Create file systems&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mkfs.exfat /dev/nvme0n1p1
mkfs.btrfs /dev/vg0/alpine
mkfs.ext4 /dev/vg0/home
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Activate swap&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mkswap /dev/vg0/swap
swapon /dev/vg0/swap
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Create Btrfs Subvolumes&lt;/h4&gt;
&lt;p&gt;Temporarily mount the &lt;code&gt;alpine&lt;/code&gt; partition to create subvolumes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mount /dev/vg0/alpine /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the subvolumes adapted from
&lt;a href=&quot;https://wiki.archlinux.org/title/Snapper#Suggested_filesystem_layout&quot;&gt;Snapper&apos;s Suggested filesystem layout&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;btrfs subvolume create /mnt/@ # /
btrfs subvolume create /mnt/@var_log # /var/log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the id of the &lt;code&gt;@&lt;/code&gt; subvolume. Note it as &lt;code&gt;&amp;lt;root-subvol-id&amp;gt;&lt;/code&gt;. It will
probably be 256 or something.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;btrfs subvolume list /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change the default subvolume to &lt;code&gt;@&lt;/code&gt; (Replace &lt;code&gt;&amp;lt;root-subvol-id&amp;gt;&lt;/code&gt; with the ID you
got from the previous step).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;btrfs subvolume set-default &amp;lt;root-subvol-id&amp;gt; /
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unmount the partition&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;umount /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Mount partitions&lt;/h4&gt;
&lt;p&gt;Create mountpoints and mount our partitions and subvolumes&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mount /dev/vg0/alpine -o subvol=@ /mnt/

# Create mountpoints
mkdir -p /mnt/boot /mnt/boot /mnt/var/log

# Mount the remaining subvolumes
mount /dev/vg0/alpine -o subvol=@var_log /mnt/var/log

# Mount the efi system partition
mount /dev/nvme0n1p1 /mnt/boot

# Mount the home partition
mount /dev/vg0/home /mnt/home
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;h3&gt;Install a base alpine system&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;BOOTLOADER=none setup-disk -k edge /mnt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;BOOTLOADER=none&lt;/code&gt; tells the script to not install any bootloader (grub is
the default), and &lt;code&gt;-k edge&lt;/code&gt; tells the script to install the &lt;code&gt;edge&lt;/code&gt; kernel
instead of the &lt;code&gt;lts&lt;/code&gt; one.&lt;/p&gt;
&lt;h3&gt;Chroot into the filesystem&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;chroot /mnt
mount -t proc proc /proc
mount -t devtmpfs dev /dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Switch to the edge branch, and enable the community and testing repositories.
This is done by editing &lt;code&gt;/etc/apk/repositories&lt;/code&gt; and replacing its contents with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://dl-cdn.alpinelinux.org/alpine/edge/main
http://dl-cdn.alpinelinux.org/alpine/edge/community
http://dl-cdn.alpinelinux.org/alpine/edge/testing
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You can choose a &lt;a href=&quot;https://mirrors.alpinelinux.org/&quot;&gt;mirror&lt;/a&gt; closer to you if you want&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Setup a local apk cache&lt;/h4&gt;
&lt;p&gt;I like having my downloaded apks available locally. In case I want to reinstall
them or something... You can skip this step if you want a really minimal design.
When prompted, say &lt;code&gt;/var/cache/apk&lt;/code&gt; for the cache directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setup-apkcache
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;(No) bootloader&lt;/h3&gt;
&lt;p&gt;The next step would be to install a bootloader like GRUB or
systemd-boot/gummiboot. However, we don&apos;t need one of them since we instead
create a &lt;a href=&quot;https://wiki.archlinux.org/title/Unified_kernel_image&quot;&gt;Unified Kernel Image&lt;/a&gt; (UKI) which can be directly booted by the
UEFI firmware, hence removing the need for a traditional bootloader. A UKI
contains the following, and some more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The kernel itself&lt;/li&gt;
&lt;li&gt;The kernel’s command line parameters&lt;/li&gt;
&lt;li&gt;A small stub that execute the kernels with that command line&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://wiki.archlinux.org/title/Arch_boot_process#initramfs&quot;&gt;initramfs&lt;/a&gt; (or initrd): a small read-only filesystem with the necessary
userspace utilities to boot into the main system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stub itself is provided by the &lt;code&gt;gummiboot-efistub&lt;/code&gt; package. It is considered
deprecated, but no solid alternative is available. The bundle itself is built by
&lt;code&gt;efi-mkuki&lt;/code&gt;, and &lt;code&gt;secureboot-hook&lt;/code&gt; will rebuild the bundle after each kernel
upgrade.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add secureboot-hook gummiboot-efistub
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install &lt;code&gt;blkid&lt;/code&gt; which will be used in a moment. This tool prints the UUID (and a
few other details) for a specified partition. This is the recommended way to
address a partition ambiguously:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add blkid
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;secureboot-hook&lt;/h4&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/kernel-hooks.d/secureboot.conf&lt;/code&gt; with the following contents.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmdline=/etc/kernel/cmdline
signing_disabled=yes
output_dir=&quot;/boot/EFI/Linux&quot;
output_name=&quot;alpine-linux-{flavor}.efi&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/&amp;lt;efi&amp;gt;/EFI/Linux&lt;/code&gt; is a more or less standard directory, and will be discovered
by &lt;code&gt;systemd-boot&lt;/code&gt; if you have that installed.&lt;/p&gt;
&lt;p&gt;Signing is disabled only temporarily until I install the proper keys.&lt;/p&gt;
&lt;h4&gt;/etc/kernel/cmdline&lt;/h4&gt;
&lt;p&gt;Also create a file &lt;code&gt;/etc/kernel/cmdline&lt;/code&gt; that will contain arguments passed to
the kernel from the bootloader (UKI, in this case).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root=UUID=5021db58-cc3a-4829-a630-2d468f8d1761
rootflags=subvol=@
rootfstype=btrfs
cryptroot=UUID=0db973a0-1b95-4a23-a63f-cb6248fe2bf7
cryptdm=lvmcrypt
cryptkey
modules=sd-mod,btrfs,nvme
quiet
ro
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;root&lt;/code&gt; UUID can be determined with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blkid /dev/vg0/alpine &amp;gt;&amp;gt; /etc/kernel/cmdline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;cryptroot&lt;/code&gt; UUID:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blkid /dev/nvme0n1p2 &amp;gt;&amp;gt; /etc/kernel/cmdline
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;initrams&lt;/h4&gt;
&lt;p&gt;Edit &lt;code&gt;/etc/mkinitfs/mkinitfs.conf&lt;/code&gt; to add &lt;code&gt;features&lt;/code&gt; which are needed for our
encrypted root setup to work. While editing this line, it is also safe to delete
&lt;code&gt;virtio&lt;/code&gt;, which is used only in virtual machines. Also add &lt;code&gt;kms&lt;/code&gt; to enable
kernel mode setting. These features will be included in the generated
&lt;a href=&quot;https://wiki.archlinux.org/title/Arch_boot_process#initramfs&quot;&gt;initramfs&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;features=&quot;ata base ide scsi usb btrfs ext4 lvm kms keymap nvme cryptsetup cryptkey resume&quot;
disable_trigger=yes
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Boot Entry&lt;/h4&gt;
&lt;p&gt;We use &lt;code&gt;efibootmgr&lt;/code&gt; to create a boot entry that (hopefully) shows in the EFI
firmware, although you can always use the UFI&apos;s boot from file function or
execute the UKI from a UEFI shell. I heard reports that the boot entries may
disappear after firmware upgrades or other vendor EFI quirks, so bear that in
mind.&lt;/p&gt;
&lt;p&gt;First exit the chroot using &lt;code&gt;exit&lt;/code&gt;. We can&apos;t use &lt;code&gt;efibootmgr&lt;/code&gt; inside the chroot
because it won&apos;t be able to read and set the EFI variables, mounted at
&lt;code&gt;/sys/firmware/efi/efivars&lt;/code&gt;. Then install efibootmgr:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add efibootmgr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then create a boot entry named &quot;Alpine Linux&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;efibootmgr --disk /dev/nvme0n1 --part 1 --create --label &apos;Alpine Linux&apos; --load /EFI/Linux/alpine-linux-edge.efi --verbose
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: This procedure only needs to be done once; after that the Unified Kernel
Image will be generated automatically every time the kernel is upgraded.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Finally, trigger the newly created kernel hook so that all the right files are
copied into &lt;code&gt;/boot&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk fix kernel-hooks
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Hibernation&lt;/h3&gt;
&lt;p&gt;To setup hibernation, we&apos;ll first need to find the UUID of our swap partition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lsblk -f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit the &lt;code&gt;/etc/kernel/cmdline&lt;/code&gt; to let the system know where you will be resuming
from.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resume=UUID=&amp;lt;UUID of /dev/vg0/swap&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable the swap service during boot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-update add swap default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install &lt;code&gt;zzz&lt;/code&gt; and test hibernation&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add zzz
zzz -Z # or ZZZ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now reboot and test your system&lt;/p&gt;
&lt;h2&gt;Desktop&lt;/h2&gt;
&lt;h3&gt;Fonts&lt;/h3&gt;
&lt;p&gt;Installing the Noto fonts make almost every characters rendered (CJK and emoji):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add font-noto font-noto-cjk font-noto-extra font-noto-emoji
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install my preferred fonts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add font-jetbrains-mono font-liberation-serif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And configure fontconfig to use them at &lt;code&gt;/etc/fonts/local.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot;?&amp;gt;
&amp;lt;!DOCTYPE fontconfig SYSTEM &quot;fonts.dtd&quot;&amp;gt;
&amp;lt;fontconfig&amp;gt;
  &amp;lt;alias&amp;gt;
    &amp;lt;family&amp;gt;sans-serif&amp;lt;/family&amp;gt;
    &amp;lt;prefer&amp;gt;
      &amp;lt;family&amp;gt;Cantarell&amp;lt;/family&amp;gt;
    &amp;lt;/prefer&amp;gt;
  &amp;lt;/alias&amp;gt;
  &amp;lt;alias&amp;gt;
    &amp;lt;family&amp;gt;monospace&amp;lt;/family&amp;gt;
    &amp;lt;prefer&amp;gt;
      &amp;lt;family&amp;gt;JetBrains Mono&amp;lt;/family&amp;gt;
    &amp;lt;/prefer&amp;gt;
  &amp;lt;/alias&amp;gt;
  &amp;lt;alias&amp;gt;
    &amp;lt;family&amp;gt;serif&amp;lt;/family&amp;gt;
    &amp;lt;prefer&amp;gt;
      &amp;lt;family&amp;gt;Liberation Serif&amp;lt;/family&amp;gt;
    &amp;lt;/prefer&amp;gt;
  &amp;lt;/alias&amp;gt;
&amp;lt;/fontconfig&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Snapper&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.archlinux.org/title/Snapper&quot;&gt;Snapper&lt;/a&gt; is a tool to automatically or manually take snapshots of btrfs
systems. I use it because I sometimes mess up my root system. Let&apos;s install it
and setup up our first snapshot.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Remember: Snapshots are &lt;strong&gt;NOT&lt;/strong&gt; backups.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;apk add snapper

snapper -c root create-config /
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a new subvolume at &lt;code&gt;/.snapshots&lt;/code&gt;. Each snapshot will be stored
at &lt;code&gt;/.snapshot/&amp;lt;snapshot-number&amp;gt;/snapshot&lt;/code&gt;. It will also add a new line to
&lt;code&gt;/etc/conf.d/snapper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create a first snapshot:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;snapper -c config create --description &apos;Base Installation&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use LVM snaphots, but that is an alternative I have not explored
yet. It like a more interesting option tbf.&lt;/p&gt;
&lt;h3&gt;Enable &amp;amp; Start function&lt;/h3&gt;
&lt;p&gt;I like to have this function handy. It is synonymous to
&lt;code&gt;systemctl enable --now&lt;/code&gt;. You can put it in /etc/profile or somewhere&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-init() {
  if [ $# -lt 1 ] || [ $# -gt 2 ]
  then
    &amp;gt;&amp;amp;2 echo &quot;Invalid number of arguments provided (1-2 acceptable)&quot;
    return 1
  fi

  RUNLEVEL=&quot;${2:-default}&quot;;
  rc-service $1 start
  rc-update add $1 $RUNLEVEL;
}

rc-deinit() {
  if [ $# -lt 1 ] || [ $# -gt 2 ]
  then
    &amp;gt;&amp;amp;2 echo &quot;Invalid number of arguments provided (1-2 acceptable)&quot;
    return 1
  fi

  RUNLEVEL=&quot;${2:-default}&quot;;
  rc-service $1 stop
  rc-update del $1 $RUNLEVEL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GNOME&lt;/h3&gt;
&lt;p&gt;Setup the GNOME Desktop Environment (what I use, No I don&apos;t use sway or hyprland
yet:))&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setup-desktop gnome
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Allow updates to be carried out in GNOME Software.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-init apk-polkit-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Allow switching the power profiles in the quick settings&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add power-profiles-daemon
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have a convertible, allow turning your laptop to flip it&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add iio-sensor-proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hardware acceleration&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add intel-media-driver
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;NetworkManager&lt;/h3&gt;
&lt;p&gt;Setup NetworkManager to manage your... network. Also setup WiFi and a TUI
(&lt;code&gt;nmtui&lt;/code&gt;). I also prefer using &lt;code&gt;iwd&lt;/code&gt; instead of &lt;code&gt;wpa_supplicant&lt;/code&gt; as the actual
WiFi backend, since it&apos;s what I&apos;m familiar with.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk add networkmanager networkmanager-wifi networkmanager-wifi networkmanager-dnsmasq
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I like to use this configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[main] 
dhcp=internal
plugins=keyfile
dns=dnsmasq

[device]
wifi.scan-rand-mac-address=yes
wifi.backend=wpa_supplicant

[connectivity]
uri=http://nmcheck.gnome.org/check_network_status.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are probably other configuration options to set.&lt;/p&gt;
&lt;p&gt;Enable and activate the service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-init networkmanager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we are now using NetworkManager to manage our connections, we can disable
the default networking service.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-update del networking
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might also see that &lt;code&gt;chronyd&lt;/code&gt; takes a while to sync on boot. We can tell it
to do that in the background on boot instead by editing &lt;code&gt;/etc/conf.d/chronyd&lt;/code&gt;
and setting&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FAST_STARTUP=yes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Bluetooth&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;apk add bluez bluez-openrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable the Bluetooth experimental features to view the battery charge of your
bluetooth earphones at &lt;code&gt;/etc/bluetooth/main.conf&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Experimental=True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reboot or load the kernel module&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;modprobe btusb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start &amp;amp; enable the bluetooth service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rc-service bluetooth start
rc-update add bluetooth default
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Sound&lt;/h3&gt;
&lt;p&gt;By default, checking &lt;code&gt;dmesg&lt;/code&gt; seems to indicate missing firmware:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; dmesg | grep firmw
[    1.096997] i915 0000:00:02.0: [drm] Finished loading DMC firmware i915/tgl_dmc_ver2_12.bin (v2.12)
[   36.239398] iwlwifi 0000:00:14.3: loaded firmware version 77.2df8986f.0 QuZ-a0-hr-b0-77.ucode op_mode iwlmvm
[   36.363387] Bluetooth: hci0: Minimum firmware build 1 week 10 2014
[   36.365863] Bluetooth: hci0: Found device firmware: intel/ibt-19-0-4.sfi
[   36.447917] sof-audio-pci-intel-tgl 0000:00:1f.3: Direct firmware load for intel/sof/sof-tgl.ri failed with error -2
[   36.447919] sof-audio-pci-intel-tgl 0000:00:1f.3: error: sof firmware file is missing, you might need to
[   36.447921] sof-audio-pci-intel-tgl 0000:00:1f.3: error: failed to load DSP firmware -2
[   36.714932] psmouse serio1: trackpoint: Elan TrackPoint firmware: 0xa1, buttons: 3/3
[   38.519487] Bluetooth: hci0: Waiting for firmware download to complete
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;apk add sof-firmware
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reboot or try to load the kernel module using the relevant soundcard name found
using &lt;code&gt;find /lib/modules/* -type f -name &apos;*.zst&apos; -name &apos;*sof*&apos; -name &apos;*tgl&apos;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;modprobe sof-audio-pci-intel-tgl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install PipeWire packages and friends.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk pipewire wireplumber pipewire-pulse pipewire-alsa pipewire-spa-bluez gst-plugin-pipewire
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Future considerations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use Unl0kr&lt;/li&gt;
&lt;li&gt;Use Clevis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://whynothugo.nl/journal/2023/11/19/setting-up-an-alpine-linux-workstation/&quot;&gt;&quot;Setting up an Alpine Linux workstation&quot; by Hugo Osvaldo Barrera&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://whynothugo.nl/journal/2023/02/18/in-praise-of-alpine-and-apk/&quot;&gt;&quot;In Praise of Alpine and APK&quot; by Hugo Osvaldo Barrera&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drewdevault.com/2021/05/06/Praise-for-Alpine-Linux.html&quot;&gt;&quot;In Praise of Alpine&quot; by Drew Devault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Full_disk_encryption_secure_boot&quot;&gt;Full disk encryption secure boot - Alpine Wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.alpinelinux.org/wiki/UEFI_Secure_Boot&quot;&gt;UEFI Secure Boot - Alpine Wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>linux</category></item><item><title>How River Works</title><link>https://www.vixalien.com/blog/river/</link><guid isPermaLink="true">https://www.vixalien.com/blog/river/</guid><description>A simple photostream webapp that (currently) gets images from VSCO.</description><pubDate>Thu, 18 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The other day, I saw a &lt;a href=&quot;https://mastodon.social/@jimmac/111708348377993550&quot;&gt;mastodon post&lt;/a&gt; by &lt;a href=&quot;https://jimmac.eu/&quot;&gt;Jakub Steiner (jimmac)&lt;/a&gt;,
a seasoned artist whom I well respect, about a site he&apos;s made to house his
collection of arts. The website is hosted at https://art.jimmac.eu/ and I
encourage you to visit the website to see the art for yourself.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/river/art.jimmac.eu.png&quot; alt=&quot;art.jimmac.eu by jimmac&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While I&apos;m fascinated by the art, I was even more fascinated by the website
itself. It&apos;s a very minimalistic single-page app that shows a stream (grid) of
images and allows you to click on each image to see it in full size. It&apos;s
beautiful, and I&apos;ve always been looking for a way to make something similar for
myself, basically a way to show off a few of my photos.&lt;/p&gt;
&lt;h2&gt;source code&lt;/h2&gt;
&lt;p&gt;I learnt that Jakub has another similar site for his photos,
https://photo.jimmac.eu/, and decided to check out
&lt;a href=&quot;https://github.com/jimmac/photo.jimmac.eu&quot;&gt;the source code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While checking out the implementation, I saw that the website(s) themselves are
forked versions of &lt;a href=&quot;https://github.com/waschinski/photo-stream&quot;&gt;photo-stream&lt;/a&gt;, a &quot;home for your photos&quot; initially created by
maxvoltar.&lt;/p&gt;
&lt;h2&gt;photo-stream&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/waschinski/photo-stream&quot;&gt;photo-stream&lt;/a&gt; is a git repository that contains a jekyll site that shows the
photos in a predefined location &lt;em&gt;inside the git repo&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I was not the best fan of this approach, as I don&apos;t really like using git for
non-code stuff (in this case images), as git repositories can get quite large if
you put too many binaries in them. It was hence not a good fit for me, as where
I live bandwidth is not cheap nor fast.&lt;/p&gt;
&lt;p&gt;Hence, I decided a more sane and ingenious solution would be to utilise images
stored somewhere else, and just show them in a nice format like Jakub&apos;s site.&lt;/p&gt;
&lt;h2&gt;vsco&lt;/h2&gt;
&lt;p&gt;I already have a &lt;a href=&quot;https://vsco.co/&quot;&gt;VSCO&lt;/a&gt; &lt;a href=&quot;https://vsco.co/angeloverlain&quot;&gt;account&lt;/a&gt;, and I&apos;ve been using it as a
more sane alternative to my Instagram, which is private. Basically I put images
on VSCO that I want to be public, like landscapes or just random photos I take.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/river/my-vsco.png&quot; alt=&quot;my vsco&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So I thought it would be a nice idea to use VSCO as a source of images for my
photostream, instead of relying on a git-based workflow. This means that I can
simply upload an image to my VSCO using the mobile app, and it will be instantly
visible on my photostream.&lt;/p&gt;
&lt;p&gt;It&apos;s important to note that in this case, VSCO is just an implementation detail,
as I initially wanted to a less proprietary solution, like &lt;a href=&quot;https://pixelfed.org/&quot;&gt;pixelfed&lt;/a&gt;, but I
didn&apos;t have the time to explore the ecosystem and set it up, so I just went with
VSCO (at the moment).&lt;/p&gt;
&lt;p&gt;However, VSCO doesn&apos;t have an API, but since the website is a webapp and makes
requests client-side, I can just inspect the network requests and see how the
website gets the images.&lt;/p&gt;
&lt;h2&gt;reverse engineering&lt;/h2&gt;
&lt;p&gt;So now let&apos;s invoke the developer tools and look at how VSCO gets that crispy
data about the images.&lt;/p&gt;
&lt;p&gt;The website is a SSR webapp and that means it initally loads the pages (for
example, the &lt;a href=&quot;https://vsco.co/angeloverlain&quot;&gt;user profile&lt;/a&gt;) are initially generated by the server
and returned as HTML. This means getting data from VSCO would require scraping
the HTML and parsing it, which is not a very good idea.&lt;/p&gt;
&lt;p&gt;However, we are lucky. If you then navigate to another page (say you click on an
image), the website doesn&apos;t reload the page, but it makes a &lt;em&gt;data&lt;/em&gt; client side
request to an &lt;a href=&quot;https://vsco.co/api/&quot;&gt;internal API&lt;/a&gt; which returns the data in JSON format.
This means that we can get data for a user&apos;s profile by first making a request
to a user&apos;s photo page (which is returned as HTML), and then clicking on the
user&apos;s username to get the JSON data.&lt;/p&gt;
&lt;p&gt;Using this method, it was easy to know some of the endpoints that the VSCO API
provides.&lt;/p&gt;
&lt;h2&gt;vsco&apos;s API&lt;/h2&gt;
&lt;p&gt;By clicking around VSCO&apos;s website, you will see it making a view requests to
it&apos;s API, and that&apos;s how I discovered a few of the endpoints needed to get the
data I wanted.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/river/vsco-requests.png&quot; alt=&quot;a few VSCO API endpoints&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here are the details I found about VSCO&apos;s API:&lt;/p&gt;
&lt;h3&gt;base URL&lt;/h3&gt;
&lt;p&gt;The base URL for the API is &lt;code&gt;https://vsco.co/api&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;versions&lt;/h3&gt;
&lt;p&gt;When you inspect the requests, you will see that the API is versioned. There are
2 versions in use &lt;strong&gt;concurrently&lt;/strong&gt; at the time of writing this article: &lt;code&gt;v2&lt;/code&gt; and
&lt;code&gt;v3&lt;/code&gt;. The 2 VSCO APIs seem to coexist and are both utilised by VSCO, buy they
are incompatible. This means that some resources are only visible in &lt;code&gt;v2&lt;/code&gt; and
some are only visible in &lt;code&gt;v3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&apos;s very weird that VSCO uses 2 versions of the API at the same time, but it&apos;s
what they do, and we have to deal with it.&lt;/p&gt;
&lt;h3&gt;authentication&lt;/h3&gt;
&lt;p&gt;If you try to make a request to the API normally, the API will deny you access
and return a response with a &lt;code&gt;401&lt;/code&gt; status code. This means that we need to
authenticate (or identify) ourselves to the API.&lt;/p&gt;
&lt;p&gt;Luckily, VSCO uses a simple &lt;a href=&quot;https://swagger.io/docs/specification/authentication/bearer-authentication/&quot;&gt;token authentication&lt;/a&gt; scheme (also
known as a bearer authentication). All the (web)apps need to do to gain access
to the API is to include a special &quot;token&quot; that identifies them to the server.&lt;/p&gt;
&lt;p&gt;Normally, other APIs use something more sophisticated, like OAuth to make sure
each user has their own &quot;token&quot; or tokens, but VSCO doesn&apos;t do that and keeps
the granularity of the tokens to the app level.&lt;/p&gt;
&lt;p&gt;To provide the token, the apps provide a standard
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization&quot;&gt;&lt;code&gt;Authorization&lt;/code&gt; header&lt;/a&gt; in the request body. The header the
&lt;code&gt;auth-scheme&lt;/code&gt; of &lt;code&gt;Bearer&lt;/code&gt;. This means that the full header looks like the
following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;&amp;lt;token&amp;gt;&lt;/code&gt; is a placeholder for the actual token, and for the
purposes of this article, I will not be showing the actual token used, although
you can easily get it by inspecting the requests made by the
&lt;a href=&quot;https://vsco.co/&quot;&gt;VSCO website&lt;/a&gt; or the &lt;a href=&quot;https://studio.vsco.co/&quot;&gt;VSCO Studio&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some good news is that &lt;code&gt;v2&lt;/code&gt; and &lt;code&gt;v3&lt;/code&gt; both use the same token, so we don&apos;t have
manage multiple tokens.&lt;/p&gt;
&lt;h3&gt;endpoints&lt;/h3&gt;
&lt;p&gt;After identifying how to connect to the API, I started looking at the endpoints
and the kind of data they return.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;/2.0/sites?subdomain=&amp;lt;username&amp;gt;&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This endpoint returns the data about a user&apos;s profile. The &lt;code&gt;subdomain&lt;/code&gt; query is
used to identify the user. For example, if you want to get the data for the user
&lt;code&gt;vsco&lt;/code&gt;, you would make a request to &lt;code&gt;/2.0/sites?subdomain=vsco&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The data returned by this endpoint is a bit too verbose, but there is really one
field you need, which is the &lt;code&gt;id&lt;/code&gt; field. This field is used to identify the user
in other endpoints.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;sites&quot;: [
    {
      // other fields hidden for brevity
      &quot;description&quot;: &quot;&quot;,
      &quot;domain&quot;: &quot;vsco.co/vsco&quot;,
      &quot;externalLink&quot;: &quot;&quot;,
      &quot;name&quot;: &quot;VSCO&quot;,
      &quot;subdomain&quot;: &quot;vsco&quot;,
      &quot;id&quot;: 113950 // &amp;lt;-- this is the site id
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;/3.0/medias/profile?site_id=283527326&amp;amp;limit=14&amp;amp;cursor=&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This endpoint returns the data about a user&apos;s photos. This endpoint has a few
query parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;site_id&lt;/code&gt; is the &lt;strong&gt;required&lt;/strong&gt; user id of the user you want to get the photos
of. This is the &lt;code&gt;id&lt;/code&gt; field from the previous endpoint.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;limit&lt;/code&gt; is the &lt;strong&gt;optional&lt;/strong&gt; number of photos to return. The default is &lt;code&gt;10&lt;/code&gt;,
and the maximum is &lt;code&gt;30&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cursor&lt;/code&gt; is the &lt;strong&gt;optional&lt;/strong&gt; cursor to use to get the next page of photos.
This is used for pagination, and is not needed for the first page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The data returned by this endpoint is a bit too verbose, and I invite you to try
it for yourself to get a complete response but here is a sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;media&quot;: [
    {
      &quot;type&quot;: &quot;image&quot;,
      &quot;image&quot;: {
        // some fields are hidden for brevity
        &quot;_id&quot;: &quot;IDOFIMG&quot;,
        &quot;grid_name&quot;: &quot;vsco&quot;,
        &quot;adaptive_base&quot;: &quot;/i/IDOFIMG&quot;,
        &quot;site_id&quot;: 283527326,
        &quot;site_profile_image_url&quot;: &quot;i.vsco.co/IDOFIMG&quot;,
        &quot;description&quot;: &quot;&quot;,
        &quot;description_anchored&quot;: &quot;&quot;,
        &quot;capture_date&quot;: 1705541060652,
        &quot;capture_date_ms&quot;: 1705541060652,
        &quot;upload_date&quot;: 1705541060652,
        &quot;last_updated&quot;: 1705541060652,
        &quot;permalink&quot;: &quot;http://vsco.co/vsco/media/id&quot;,
        &quot;share_link&quot;: &quot;http://vsco.co/vsco/media/id?share=tracking&quot;,
        &quot;responsive_url&quot;: &quot;im.vsco.co/aws-us-west-2/blurb/etc/id/not-real.jpg&quot;,
        &quot;image_meta&quot;: {
          &quot;aperture&quot;: 0,
          &quot;copyright&quot;: &quot;Copyright 2024. All rights reserved.&quot;,
          &quot;flash_mode&quot;: &quot;Off, Did not fire&quot;,
          &quot;iso&quot;: 0,
          &quot;make&quot;: &quot;Sony&quot;,
          &quot;model&quot;: &quot;ZV E-1&quot;,
          &quot;shutter_speed&quot;: &quot;128/100000&quot;,
          &quot;white_balance&quot;: &quot;Auto&quot;,
          &quot;orientation&quot;: 1,
          &quot;capture_date&quot;: 1705541060652,
          &quot;flash_value&quot;: 10,
          &quot;file_size&quot;: 1010000,
          &quot;file_hash&quot;: &quot;hash&quot;
        },
        &quot;height&quot;: 1000,
        &quot;width&quot;: 100
      }
    },
    {
      // ... another image
    },
    {
      // ... yet another image
    }
  ],
  &quot;next_cursor&quot;: &quot;use-this-to-get-the-next-page-of-results&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I know that&apos;s a lot of data, but we only need a few fields from each &lt;code&gt;media&lt;/code&gt;
object:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;image._id&lt;/code&gt; is the id of the image. This is used to identify each image
uniquely.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image.responsive_url&lt;/code&gt; is the URL of the image. This is the URL we will use to
get and display the image.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image.description&lt;/code&gt; is the user-provided caption of the image. May be used to
show more context about the image.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image.capture_date&lt;/code&gt; is the timestamp of when the image was captured. This is
used to sort the images by date. However, this field is not always present, so
we will use the &lt;code&gt;image.upload_date&lt;/code&gt; field if that&apos;s the case.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image.image_meta&lt;/code&gt; is the metadata of the image. This is used to show more
context about the image.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&apos;s actually all the endpoints we need to get the data we want. With this
information in mind, we can now start building the webapp that shows the images.&lt;/p&gt;
&lt;p&gt;I then build a &lt;a href=&quot;https://github.com/vixalien/river/tree/main/api&quot;&gt;minimal VSCO API client&lt;/a&gt; that can be used to
get the data from VSCO.&lt;/p&gt;
&lt;h2&gt;webapp&lt;/h2&gt;
&lt;p&gt;The original &lt;a href=&quot;https://github.com/waschinski/photo-stream&quot;&gt;photo-stream&lt;/a&gt; is a jekyll app. And by looking at the source code,
&lt;a href=&quot;https://jekyllrb.com/&quot;&gt;jekyll&lt;/a&gt; looks like a very nice tool, but there are caveats that rendered the
original app an unsuitable choice for me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The git based workflow, as explained before, is not for me. You need to
basically fork the repo, add your images, and then push the repo to GitHub and
then GitHub pages will build the site for you. This means that you will need
to maintain a fork of the repo, and I&apos;d rather not.&lt;/li&gt;
&lt;li&gt;I&apos;m not even sure if GitHub pages will be able to build the site, and hosting
providers that provide virtual machines are not my thing. I&apos;d rather host my
website on a serverless platform using JAMStack preferrably.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was clear that I needed to build my own webapp, and in the ever-growing
landscape of JavaScript frameworks, it was not easy to pick a choice. However, I
ended up settling on &lt;a href=&quot;https://fresh.deno.dev/&quot;&gt;Fresh&lt;/a&gt;, a preact framework optimised for &lt;a href=&quot;https://deno.land/&quot;&gt;Deno&lt;/a&gt;. This
means that I can host the app on &lt;a href=&quot;https://deno.com/deploy&quot;&gt;Deno Deploy&lt;/a&gt; and it will just
work.&lt;/p&gt;
&lt;p&gt;The webapp itself just fetches data from the API, and presents the images in a
nice UI &lt;s&gt;stolen from&lt;/s&gt; inspired by &lt;a href=&quot;https://github.com/waschinski/photo-stream&quot;&gt;photo-stream&lt;/a&gt;. It&apos;s basically a
re-implementation of &lt;a href=&quot;https://github.com/waschinski/photo-stream&quot;&gt;photo-stream&lt;/a&gt; in TypeScript.&lt;/p&gt;
&lt;p&gt;Of course, I have made some minor modifications to the UI, but mostly they are
the same concept.&lt;/p&gt;
&lt;p&gt;You can find the website&apos;s &lt;a href=&quot;https://github.com/vixalien/river&quot;&gt;source code on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;flow&lt;/h2&gt;
&lt;p&gt;Now that the webapp is built, let&apos;s look at how exactly it gets the images.&lt;/p&gt;
&lt;p&gt;Now, we don&apos;t have to add images to a git repo, so you might be wondering how
exactly we get images for the photostream.&lt;/p&gt;
&lt;h3&gt;1. setup VSCO&lt;/h3&gt;
&lt;p&gt;You will need to create a VSCO account if you don&apos;t have one already. An account
is free, although you can pay for a subscription to get more features.&lt;/p&gt;
&lt;h3&gt;2. clone river&lt;/h3&gt;
&lt;p&gt;You now need to make a clone of &lt;a href=&quot;https://github.com/vixalien/river&quot;&gt;river&lt;/a&gt;, then clone the forked repo to your
local machine.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/vixalien/river
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. get your site id&lt;/h3&gt;
&lt;p&gt;Remember that we use the site id (in contrast to username) to identify the user
in the API. To get the site id, you need can run the following command in the
root of the cloned app to know your site id. This is so we don&apos;t have to always
fetch the site id from the API.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno task get-id &amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember to replace &lt;code&gt;&amp;lt;username&amp;gt;&lt;/code&gt; with your username.&lt;/p&gt;
&lt;h3&gt;3. configure river&lt;/h3&gt;
&lt;p&gt;Now let&apos;s configure the app. You need to create a &lt;code&gt;.env&lt;/code&gt; file in the root of the
cloned app, and modify the configuration to your liking.&lt;/p&gt;
&lt;p&gt;There is a &lt;a href=&quot;https://github.com/vixalien/river/blob/main/.env.defaults&quot;&gt;&lt;code&gt;.env.defaults&lt;/code&gt;&lt;/a&gt; file that contains all the possible
configuration values. You can refer to the file to add configuration files as
needed.&lt;/p&gt;
&lt;p&gt;For example, here is the configuration of my site at
https://memories.vixalien.com/:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# .env
TITLE=vixalienʼs memories
AUTHOR_NAME=vixalien
AUTHOR_WEBSITE=https://vixalien.com
DESCRIPTION=A few snapshots from a life that I probably lived.
URL=https://memories.vixalien.com
SHOW_RSS_FEED=0

ALLOW_ORDER_SORT_CHANGE=0

INSTAGRAM_USERNAME=username
CUSTOM_LINK_NAME=© 2024 vixalien
CUSTOM_LINK_URL=https://vixalien.com

# this is the only required configuration
USER_ID=281297126
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that you should probably change the settings to reflect your own site. And
the &lt;code&gt;.env&lt;/code&gt; file is ignored by git, so you don&apos;t have to worry about accidentally
committing it.&lt;/p&gt;
&lt;h3&gt;5. run&lt;/h3&gt;
&lt;p&gt;Now that you have configured the app, it&apos;s time to run it. Run the following
command in the root of the cloned app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno task start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now have a local server running at http://localhost:8000/ that shows
your photostream, similar to the following&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/river/river.png&quot; alt=&quot;a river photostream&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;4. deploy&lt;/h3&gt;
&lt;p&gt;Now that you have configured the app, you can deploy it to Deno Deploy or any
other provider of your choice. I&apos;ll focus on Deno Deploy as it&apos;s the one I used,
and is free for my use case.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an account at https://deno.com/deploy&lt;/li&gt;
&lt;li&gt;Clone &lt;a href=&quot;https://github.com/vixalien/river&quot;&gt;river&lt;/a&gt; to your GitHub account&lt;/li&gt;
&lt;li&gt;Create a new project on Deno Deploy at https://dash.deno.com/new&lt;/li&gt;
&lt;li&gt;Connect your forked repo to the project&lt;/li&gt;
&lt;li&gt;Add configuration variables by going to Project &amp;gt; Settings &amp;gt; Environment
Variables.&lt;/li&gt;
&lt;li&gt;Visit your project&apos;s URL!!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/river/deno-config.png&quot; alt=&quot;example river deno deploy configuration&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;conclusion&lt;/h2&gt;
&lt;p&gt;I hope you enjoyed this article. I know it&apos;s a bit long, but I wanted to explain
this (useless) little project of mine deeply. The next step is to fix some items
on &lt;a href=&quot;https://github.com/vixalien/river/blob/main/TODO.md&quot;&gt;the todo list&lt;/a&gt; add more features to the app.&lt;/p&gt;
&lt;p&gt;On the wishlist is supporting other sources of images, like &lt;a href=&quot;https://pixelfed.org/&quot;&gt;pixelfed&lt;/a&gt; and maybe
even Instagram. I also want to add support for videos, but I don&apos;t have a use
case for that yet.&lt;/p&gt;
&lt;p&gt;If you already forgot, the source code of river &lt;a href=&quot;https://github.com/vixalien/river&quot;&gt;is available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The source of the banner image is https://memories.vixalien.com&lt;/p&gt;
</content:encoded><category>code</category><category>projects</category><category>explained</category></item><item><title>2022</title><link>https://www.vixalien.com/blog/2022/</link><guid isPermaLink="true">https://www.vixalien.com/blog/2022/</guid><description>A year of change and adaption.</description><pubDate>Fri, 23 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello everyone! It&apos;s almost the end of the year 2022, and I hope you had a
wonderful year. As we look ahead to 2023, I wish you an even better year ahead!&lt;/p&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;The past year was one of change, with many important events taking place around
the world. Some of these events, such as the February Russian Invasion of
Ukraine and the continued impact of COVID-19, had significant and far-reaching
consequences. Other notable events included the global population reaching 8
billion people and the death of Queen Elizabeth II.&lt;/p&gt;
&lt;p&gt;I won&apos;t be trying to list every single event that happened this year, but
instead will focus on what happened in my own life and what I learned from it.
I&apos;ll also interpret some events that happened and provide my thoughts on what
may happen in the future. This blog post is divided into three sections:
Personal, News, and Development.&lt;/p&gt;
&lt;h2&gt;Personal&lt;/h2&gt;
&lt;p&gt;In this section, I&apos;ll share some of the important events that happened for me
this year, and my thoughts and feelings about them.&lt;/p&gt;
&lt;h3&gt;Graduating&lt;/h3&gt;
&lt;p&gt;I graduated from high school in August, and looking back, I realize that I&apos;m
going to miss school. It&apos;s not just about the fun and memories, but also about
the people. I&apos;ve made so many wonderful connections and experiences, and it&apos;s
hard to think about moving on and starting a new chapter in my life. I know that
I won&apos;t always have the same group of friends and colleagues, and that can be
sad.&lt;/p&gt;
&lt;h3&gt;Work&lt;/h3&gt;
&lt;p&gt;After graduating, I started working at &lt;a href=&quot;https://rwarri.com&quot;&gt;RWARRI&lt;/a&gt;, an NGO that helps people in
poverty and refugees, as a web developer and IT specialist. It&apos;s been a
challenging and busy year, with less time for personal pursuits, friends, and
hobbies. But on the positive side, I&apos;ve met some great people and gained some
valuable experience in a work environment and with socializing.&lt;/p&gt;
&lt;h3&gt;Work Travel/Missions&lt;/h3&gt;
&lt;p&gt;As part of my job at &lt;a href=&quot;https://rwarri.com&quot;&gt;RWARRI&lt;/a&gt;, I&apos;ve had the opportunity to travel and meet
people in different areas who are facing difficult circumstances. These
experiences have really helped me appreciate how lucky we are, even when we have
very little. It&apos;s easy to take our basic needs and comforts for granted, but
seeing firsthand how others struggle to meet their basic needs has made me more
grateful for what I have.&lt;/p&gt;
&lt;p&gt;In addition to helping me appreciate what I have, these experiences have also
reminded me of the importance of giving more than I receive. This can be as
simple as volunteering my time or resources to help others, or simply being
there to listen and offer support. As Jesus said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is more blessed to give than to receive. (Acts 20:35)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;ve learned that these values are important not just in our personal lives, but
also in our work and relationships with others. At RWARRI, we strive to serve
others with compassion and generosity, and I feel fortunate to be a part of that
mission. If you&apos;re interested in learning more about RWARRI and how you can get
involved, you can visit their website &lt;a href=&quot;https://rwarri.com&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Money&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;All is vanity. (Ecclesiastes 1:1)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This verse reminds us that everything is fleeting and temporary, and that we
shouldn&apos;t place too much value on material possessions. This is a lesson that
I&apos;ve learned more deeply this year as I&apos;ve started working and earning a salary.&lt;/p&gt;
&lt;p&gt;While I&apos;ve never been one to care excessively about money, my work has given me
a greater appreciation for the role that it plays in our lives. It&apos;s not just
about being able to afford the things we want, but also about having the
resources to meet our basic needs. Seeing others who struggle to put food on the
table has made me more grateful for the financial security that I have.&lt;/p&gt;
&lt;p&gt;At the same time, I&apos;ve learned to be mindful about how I spend my money. I try
to focus on buying the things that I really need, and save the rest for future
needs. I think the key is to prioritize spending on important things and not get
caught up in materialism.&lt;/p&gt;
&lt;h3&gt;University/College&lt;/h3&gt;
&lt;p&gt;I just received my results for my final exams a few days ago, and I&apos;m happy to
report that I passed. Now, I&apos;m looking forward to the next stage of my education
and considering my options for university or college. I&apos;m hoping to secure a
scholarship and, even more hopefully, study abroad. I know that university will
be a different experience, with more challenges and demands, but I&apos;m excited for
the opportunity to learn and grow.&lt;/p&gt;
&lt;p&gt;I&apos;m still in the process of researching and deciding on a major, so if you have
any useful tips or advice, please don&apos;t hesitate to reach out. I&apos;m open to
hearing from others who have gone through this process and can share their
insights.&lt;/p&gt;
&lt;h3&gt;Digital Interactions&lt;/h3&gt;
&lt;p&gt;In today&apos;s world, it&apos;s almost impossible to avoid having digital interactions
with others, whether through social media, group chats, or online communities
like Discord or Matrix. While these platforms can be a great way to connect with
others and share ideas, I&apos;ve also learned that people can be more rude and
vicious online than they would be in real life. This has led me to be more
selective about who I engage with online, and to prioritize building trust and
relationships in person.&lt;/p&gt;
&lt;p&gt;That being said, I&apos;m not suggesting that you should abandon your online friends
or delete your social media accounts. But I do think it can be beneficial to
limit your digital interactions and focus on more meaningful activities. It&apos;s
easy to get sucked into the endless scroll of social media and waste valuable
time that could be spent on things that are more meaningful and fulfilling. Next
year, consider setting limits on your digital interactions and prioritizing
face-to-face connections.&lt;/p&gt;
&lt;h3&gt;Depression&lt;/h3&gt;
&lt;p&gt;This year, I struggled with depression as a result of work-related stress and
other events in my life. At times, I felt overwhelmed, lost all motivation and
will to live, and struggled with impostor syndrome. These feelings were
compounded by external events that made things worse. As a web developer, my
productivity was severely impacted, and I found it difficult to complete tasks
and contribute to my team.&lt;/p&gt;
&lt;p&gt;Depression is a serious issue that can last for years or even a lifetime, and
it&apos;s not something that can simply be brushed off or dismissed. Unfortunately,
there are still many people who don&apos;t understand or take depression seriously,
and this can make it harder for those who are struggling to get the support and
help they need.&lt;/p&gt;
&lt;p&gt;If you&apos;re struggling with depression, I encourage you to speak to someone about
it. This could be a friend, family member, therapist, or healthcare
professional. It&apos;s important to have someone to talk to and share your feelings
with. For me, speaking out about my depression really helped, and it made a big
difference to know that I wasn&apos;t alone. If you ever need someone to talk to,
feel free to reach out to me.&lt;/p&gt;
&lt;p&gt;There are also many other resources available to help with depression, including
counseling, therapy, medication, and self-care techniques. It&apos;s important to
find the right combination of treatments that work for you and to be proactive
in seeking help. Remember, you don&apos;t have to suffer in silence – there is help
and hope available.&lt;/p&gt;
&lt;h3&gt;Music&lt;/h3&gt;
&lt;p&gt;Music has always been an important part of my life, and this year was no
different. I found myself constantly plugged into my earphones or headphones,
listening to music to help me focus and stay motivated while programming or
completing routine tasks. Music has a way of filling in the silence and
providing a sense of calm and therapy, at least for me.&lt;/p&gt;
&lt;p&gt;I&apos;ve also been experimenting with using white noise to help me sleep better. A
friend recommended rain sounds, and I have to say, they work wonders for me. If
you have trouble sleeping, you might want to try incorporating some white noise
into your bedtime routine.&lt;/p&gt;
&lt;p&gt;In terms of music genres, I really enjoyed listening to amaPiano this year. I
usually use Spotify, but I&apos;m slowly switching over to YouTube Music. Here&apos;s a
playlist with 10 tracks I can recommend easily.&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe
title=&quot;Embedded Spotify playlist for 2022&quot;
style=&quot;border-radius:12px&quot;
src=&quot;https://open.spotify.com/embed/playlist/2dRHDlx5a2o8oXMq049HOi?utm_source=generator&quot;
width=&quot;100%&quot;
height=&quot;380&quot;
frameBorder=&quot;0&quot;
allowfullscreen=&quot;&quot;
allow=&quot;autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture&quot;&amp;gt;
&amp;lt;/iframe&amp;gt;
&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Hobbies&lt;/h3&gt;
&lt;p&gt;This year, I discovered a new hobby: cycling. It&apos;s been a great activity for me,
not only because it&apos;s fun and enjoyable, but also because it helps me stay fit
and explore new places. If you&apos;re looking for a new hobby, I highly recommend
giving cycling a try.&lt;/p&gt;
&lt;p&gt;In addition to cycling, I&apos;ve also been listening to a lot of informative
podcasts and reading more this year. While I&apos;ve enjoyed these activities, I
haven&apos;t been able to practice my hobbies as much as I&apos;d like due to work and
other commitments. I&apos;m hoping to have more time for my hobbies next year, and to
find ways to balance work and leisure more effectively.&lt;/p&gt;
&lt;h2&gt;News/Opinions&lt;/h2&gt;
&lt;p&gt;In this section, I want to discuss some of the major events that happened in
2022 and share my (unprofessional) thoughts and opinions on them. Please keep in
mind that some of these topics are sensitive, and I will try to be as neutral as
possible.&lt;/p&gt;
&lt;h3&gt;Covid-19&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/COVID-19&quot;&gt;COVID-19&lt;/a&gt; pandemic has had a significant impact on the world this
year, and we are finally starting to see some progress in terms of vaccination
efforts and returning to pre-COVID situations. While it&apos;s sad to think about all
the time and opportunities that were lost during the pandemic, I&apos;m hopeful that
we are on the path to recovery and normalcy.&lt;/p&gt;
&lt;h3&gt;Russian Invasion of Ukraine&lt;/h3&gt;
&lt;p&gt;In February, &lt;a href=&quot;https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine&quot;&gt;Russia invaded Ukraine&lt;/a&gt; and sparked a conflict
with other Western countries. While Ukraine has been able to resist Russia&apos;s
efforts, thanks in part to support from NATO countries, the situation remains
tense and unresolved. It&apos;s important to respect the sovereignty of all countries
and to strive for peaceful relations with our neighbors.&lt;/p&gt;
&lt;p&gt;One aspect of this conflict that has particularly concerned me is the treatment
of Russian citizens. The war is largely being orchestrated by Putin and his
allies, rather than the average Russian person. It&apos;s wrong to view all Russians
as enemies and to discriminate against them, especially in fields like software
development where Russian developers have faced job loss and account
suspensions.&lt;/p&gt;
&lt;p&gt;In my opinion, unity is always preferable to conflict, and war should be avoided
at all costs. It&apos;s important to remember that ordinary people on both sides are
affected by these conflicts, and we should strive for peaceful resolutions
whenever possible.&lt;/p&gt;
&lt;h3&gt;Elon Musk&apos;s acquisition of Twitter&lt;/h3&gt;
&lt;p&gt;In 2022, Elon Musk made headlines when [he acquired Twitter for $44
billion][twitter-aquisiton]. This acquisition has had a significant impact on
the social media platform and on the world at large. Musk has claimed that there
were shady deals happening with the leadership of Twitter and the US government,
and he has fired a significant number of employees (from around 7,500 to around
2,900).&lt;/p&gt;
&lt;p&gt;The mass layoffs at Twitter have had a domino effect, with other companies
following suit and also reducing their workforce. This has led to a decrease in
demand for software development jobs and has caused many people to question if
it&apos;s still a viable career path.&lt;/p&gt;
&lt;p&gt;It&apos;s important to remember that these changes at Twitter and other companies
have far-reaching consequences, and they can have a significant impact on the
employment prospects of individuals in the tech industry. It&apos;s always a good
idea to stay informed about developments in your field and to be proactive in
building a strong career.&lt;/p&gt;
&lt;h3&gt;AI&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2022/midjourney.webp&quot; alt=&quot;Mechanical dove created with Midjourney V4&quot; title=&quot;By Deviationsz1 - Own work, Public Domain --- https://commons.wikimedia.org/w/index.php?curid=125105397&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This year, OpenAI released &lt;a href=&quot;https://chat.openai.com&quot;&gt;ChatGPT&lt;/a&gt;, an AI system that can engage in real-time
conversation and provide almost-perfect answers to any question you ask. While
this technology is impressive and has raised questions about the future of
humanity and the potential dominance of AI, it&apos;s important to remember that it&apos;s
still in its early stages and has limitations. For now, ChatGPT is available for
free, but it&apos;s unclear how long that will remain the case.&lt;/p&gt;
&lt;p&gt;OpenAI also released &lt;a href=&quot;https://en.wikipedia.org/wiki/DALL-E&quot;&gt;DALL-E 2&lt;/a&gt;, a deep learning model that can generate
digital images based on natural language descriptions or &quot;prompts.&quot; This model
and others like it, such as &lt;a href=&quot;https://stability.ai/&quot;&gt;Stable Diffusion&lt;/a&gt; and
&lt;a href=&quot;https://www.midjourney.com/&quot;&gt;Midjourney&lt;/a&gt;, demonstrate the increasing power and capabilities of AI. However,
the use of AI for image generation has also sparked a movement among artists who
feel that their work has been used without their consent. This has led to a
global conversation about the ethical implications of AI art and the need for
consent and proper attribution.&lt;/p&gt;
&lt;h2&gt;Development&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2022/contributions.webp&quot; alt=&quot;Github graph showing my contributions&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/2022/contributions-type.webp&quot; alt=&quot;Github graph showing the type of my contributions&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In this section, I want to share some of my thoughts and experiences as a web
developer. This year, I&apos;ve been interested in Linux systems, particularly
&lt;a href=&quot;https://www.gnome.org/&quot;&gt;GNOME&lt;/a&gt; and &lt;a href=&quot;https://www.gtk.org/&quot;&gt;GTK&lt;/a&gt;, and have been exploring alternative technologies and
approaches.&lt;/p&gt;
&lt;h3&gt;Contributions&lt;/h3&gt;
&lt;p&gt;This year, I discovered many open source libraries that I really enjoyed,
especially those that I have used in my own projects. The scope of these
libraries is quite broad, but some of the ones that stand out to me include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rabbet.me/&quot;&gt;Rabbet&lt;/a&gt;, a small link aggregator that I developed (similar to Linktr.ee)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deno.land/&quot;&gt;Deno&lt;/a&gt;, an alternative runtime to Node.JS&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://libvips.github.io/libvips/&quot;&gt;Libvips&lt;/a&gt;, an image processing library&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s worth noting that, as a developer, it&apos;s often common to contribute to these
types of libraries, especially if you rely on them in your daily work.&lt;/p&gt;
&lt;h3&gt;New Technology/Skills&lt;/h3&gt;
&lt;p&gt;I am in the process of rewriting a small link aggregator I made, &lt;a href=&quot;https://rabbet.me/&quot;&gt;Rabbet&lt;/a&gt; using
Deno and hope it&apos;ll be ready early next year.&lt;/p&gt;
&lt;p&gt;I also started using new software/alternatives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.com/&quot;&gt;Gitlab&lt;/a&gt; instead of Github (mainly for GNOME-related contributions)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt; for note-taking, finance management, and more&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://supabase.io/&quot;&gt;Supabase&lt;/a&gt;, an open source alternative to Firebase&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/&quot;&gt;Linux&lt;/a&gt;, specifically &lt;a href=&quot;https://www.archlinux.org/&quot;&gt;Arch Linux&lt;/a&gt; as an alternative to Windows&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joinmastodon.org/&quot;&gt;Mastodon&lt;/a&gt;, an open source alternative to Twitter&lt;/li&gt;
&lt;li&gt;and more I probably forgot...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Deno&lt;/h3&gt;
&lt;p&gt;One technology that I&apos;ve been particularly excited about is &lt;a href=&quot;https://deno.land/&quot;&gt;Deno&lt;/a&gt;, a runtime
for JavaScript and TypeScript that is an alternative to &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.JS&lt;/a&gt;. I&apos;ve
had a chance to work with Deno this year and have really enjoyed it. In fact, I
even used Deno to &lt;a href=&quot;/blog/deno-blog&quot;&gt;rewrite my blog&lt;/a&gt;. Deno has matured
significantly in recent years, and I think it has a bright future ahead of it.
I&apos;ve also attempted to create Deno bindings for
&lt;a href=&quot;https://docs.gtk.org/gobject/&quot;&gt;GObject&lt;/a&gt;, a library for creating and using
GObjects, which are used to create object-oriented code in C, with the goal of
allowing GTK programs to be written with Deno. This project is called
&lt;a href=&quot;#geno&quot;&gt;Geno&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;GNOME Mobile&lt;/h3&gt;
&lt;p&gt;In the third quarter of this year, I lost my phone and had to purchase a new
one. I was struck by the limited options available: it&apos;s either Android or
iPhone. I believe that
&lt;a href=&quot;https://blogs.gnome.org/shell-dev/2022/09/09/gnome-shell-on-mobile-an-update/&quot;&gt;GNOME Mobile&lt;/a&gt;,
a mobile operating system based on Linux, could bring significant improvements
and potentially challenge the dominant players in the market. There are other
Linux-based mobile OSes such as &lt;a href=&quot;https://www.plasma-mobile.org/&quot;&gt;Plasma Mobile&lt;/a&gt;,
&lt;a href=&quot;https://sxmo.org/&quot;&gt;sxmo&lt;/a&gt;, and others, but GNOME Mobile particularly caught my
attention. I hope that it becomes a viable option in the future.&lt;/p&gt;
&lt;h3&gt;Developing with GTK&lt;/h3&gt;
&lt;p&gt;In addition to working with Deno and GObjects, I&apos;ve also started learning to
make GTK apps (using &lt;a href=&quot;https://vala.dev&quot;&gt;Vala&lt;/a&gt;, with the goal of learning
&lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; in the future). It&apos;s been an interesting
journey, but one that has had its challenges, particularly the lack of good
documentation. However, efforts are underway to improve the documentation, and I
hope that it will become less of an issue in the near future.&lt;/p&gt;
&lt;h3&gt;Geno&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vixalien/geno&quot;&gt;Geno&lt;/a&gt; is a project I started to create GObject bindings for Deno. It is still
in the early stages, but I hope to complete it soon and potentially create a GTK
app with it.&lt;/p&gt;
&lt;h3&gt;Linux and the Community&lt;/h3&gt;
&lt;p&gt;I have been using &lt;a href=&quot;https://www.gnu.org/gnu/the-gnu-project.en.html&quot;&gt;GNU&lt;/a&gt;/&lt;a href=&quot;https://www.kernel.org/&quot;&gt;Linux&lt;/a&gt; for some time now and have grown to really
appreciate it. One of the best things about it is the community of people who
share a passion for GNU/Linux. Many of the people who work on projects such as
&lt;a href=&quot;https://www.gnu.org/gnu/the-gnu-project.en.html&quot;&gt;GNU&lt;/a&gt;, &lt;a href=&quot;https://www.kernel.org/&quot;&gt;Linux&lt;/a&gt;, &lt;a href=&quot;https://www.kde.org/&quot;&gt;KDE&lt;/a&gt;, &lt;a href=&quot;https://getfedora.org/&quot;&gt;Fedora&lt;/a&gt;, &lt;a href=&quot;https://www.archlinux.org/&quot;&gt;Arch&lt;/a&gt;, and others related to GNU/Linux are not
paid for their efforts. They do it because they are passionate about it and I
think that is truly amazing.&lt;/p&gt;
&lt;p&gt;In addition to the strong sense of community in the GNU/Linux world, it is also
important to address the issue of mental health. If you are struggling with
depression, it can be helpful to seek support from friends, family, or a mental
health professional. There are also online resources and support groups
available that can provide a sense of community and assistance during difficult
times. It is important to remember that you are not alone and that help is
available.&lt;/p&gt;
&lt;h2&gt;Closing&lt;/h2&gt;
&lt;p&gt;In conclusion, it is clear that the year 2022 has been a year of significant
events and challenges, both globally and personally. From the impact of COVID-19
and the Russian invasion of Ukraine, to the release of powerful AI systems and
the emergence of new technologies, it has been a year of change and adaptation.
As we move into 2023, it will be important to continue learning and growing, and
to prioritize the things that truly matter in our lives.&lt;/p&gt;
&lt;p&gt;I finally wish you all a Merry Christmas and Happy new year!&lt;/p&gt;
&lt;p&gt;&amp;lt;br/&amp;gt;&lt;/p&gt;
&lt;p&gt;Banner Photo by
&lt;a href=&quot;https://unsplash.com/es/@kellysikkema?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Kelly Sikkema&lt;/a&gt;
on
&lt;a href=&quot;https://unsplash.com/photos/-64OzuZ8ThE?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>life</category></item><item><title>Setting up React Cosmos in Remix</title><link>https://www.vixalien.com/blog/remix-in-the-cosmos/</link><guid isPermaLink="true">https://www.vixalien.com/blog/remix-in-the-cosmos/</guid><description>Build a sandbox for developing and testing UI components in Remix.</description><pubDate>Mon, 22 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello friends!&lt;/p&gt;
&lt;p&gt;As many of you might already know &lt;a href=&quot;https://remix.run&quot;&gt;Remix&lt;/a&gt; is a framework to build websites. Much
like Next.js, it&apos;s production-grade, but unlike Next, it is extremely robust,
easy and considerate. If you didn&apos;t check it out already, now might be the time.&lt;/p&gt;
&lt;h2&gt;Remix 💔 Storybook&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/remix-in-the-cosmos/storybook.webp&quot; alt=&quot;Storybook&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I was building an app using Remix, then as usual, I started writing some
components like Buttons, Select, Forms etc. I wanted to prototype those
components and view them in the browser as a way to speed up the development
process using Storybook. But sadly,
&lt;a href=&quot;https://github.com/remix-run/remix/issues/214&quot;&gt;Storybook does not work on Remix&lt;/a&gt;. It&apos;s fine as long as you don&apos;t use
some of Remix&apos;s provided components. This is because of the way Remix works, and
that if you use Remix&apos;s features such as &lt;code&gt;Link&lt;/code&gt;, &lt;code&gt;NavLink&lt;/code&gt; or &lt;code&gt;useTransition&lt;/code&gt;,
you need to wrap your app in a certain &lt;code&gt;Remix&lt;/code&gt; component. That &lt;code&gt;Remix&lt;/code&gt; component
doesn&apos;t in fact exist, and is just a combination of a React Context, Router
(provided by &lt;code&gt;react-router&lt;/code&gt; and config.) I&apos;m sure if we try hard, we may be able
to make it work on Storybook, but right now, I don&apos;t know of anyone who has
figured it out yet.&lt;/p&gt;
&lt;h2&gt;Remix 💝 Cosmos&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/remix-in-the-cosmos/cosmos.webp&quot; alt=&quot;Cosmos&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://reactcosmos.org/&quot;&gt;React Cosmos&lt;/a&gt; is yet another tool that aims to achieve exactly the same as
Storybook. Given, it doesn&apos;t have the docs, community or polish of Storybook,
but I had used it before and it worked exceptionally well. So I thought I might
give it another try. I found &lt;a href=&quot;https://dev.to/rzmz/react-cosmos-with-remix-7go&quot;&gt;Rasmus&lt;/a&gt; who already made Remix work with Cosmos.
you can read his blog post as it explains what he did. I extended on his work to
make it work better in my use case, and I hope it can be helpful for you too.&lt;/p&gt;
&lt;h2&gt;My approach&lt;/h2&gt;
&lt;p&gt;I based my work on Rasmus&apos; and made some other modifications. Here is the full
list of file changes.&lt;/p&gt;
&lt;p&gt;Structure of changes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.
├── .gitignore
├── cosmos.config.json
├── package.json
├── prod.cosmos.config.json
├── app
│   ├── components
│   │   └── cosmos.decorator.tsx
│   └── routes
│       └── cosmos.tsx
└── scripts
    └── generate-cosmos-userdeps
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;cosmos.config.json&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;staticPath&quot;: &quot;public&quot;,
  &quot;watchDirs&quot;: [&quot;app&quot;],
  &quot;userDepsFilePath&quot;: &quot;app/cosmos.userdeps.js&quot;,
  &quot;experimentalRendererUrl&quot;: &quot;http://localhost:3000/cosmos&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file is pretty much self explanatory. It is Cosmos&apos;s config. I don&apos;t think
the &lt;code&gt;staticPath&lt;/code&gt; is necessary, because Cosmos doesn&apos;t inherently need it, but i
haven&apos;t tested it yet, so there it is. The &lt;code&gt;experimentalRendererUrl&lt;/code&gt; must match
the URL you use for Remix in development, just add the &lt;code&gt;/cosmos&lt;/code&gt; path, for which
we&apos;ll create a page route soon. The &lt;code&gt;userDepsFilePath&lt;/code&gt; is a file that is
generated by Cosmos, and it outlines all the files that it uses (decorators,
fixtures, config).&lt;/p&gt;
&lt;h3&gt;prod.cosmos.config.json&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;staticPath&quot;: &quot;public&quot;,
  &quot;watchDirs&quot;: [&quot;app&quot;],
  &quot;userDepsFilePath&quot;: &quot;app/cosmos.userdeps.js&quot;,
  &quot;experimentalRendererUrl&quot;: &quot;https://whereveryourappishosted:3000/cosmos&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is almost a perfect clone of the &lt;code&gt;cosmos.config.json&lt;/code&gt;, but notice the
changed &lt;code&gt;experimentalRendererUrl&lt;/code&gt; that points to wherever you app is hosted for
production, this is important.&lt;/p&gt;
&lt;h3&gt;app/routes/cosmos.json&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { useCallback, useState } from &quot;react&quot;;
import { useEffect } from &quot;react&quot;;
import type { HeadersFunction } from &quot;@remix-run/node&quot;;
import type { LinksFunction } from &quot;@remix-run/node&quot;;

/// @ts-ignore - is generated everytime by cosmos
import { decorators, fixtures, rendererConfig } from &quot;~/cosmos.userdeps.js&quot;;

// only load cosmos in the browser
const shouldLoadCosmos = typeof window !== &quot;undefined&quot;;

// CORS: allow sites hosted on other URLs to access this one.
export const headers: HeadersFunction = () =&amp;gt; {
  return { &quot;Access-Control-Allow-Origin&quot;: &quot;*&quot; };
};

// mount the DOM renderer, notice it hydrates into `body`
function Cosmos() {
  const [cosmosLoaded, setCosmosLoaded] = useState(false);
  const loadRenderer = useCallback(async () =&amp;gt; {
    /// @ts-ignore - works
    const { mountDomRenderer } = (await import(&quot;react-cosmos/dom&quot;)).default;
    mountDomRenderer({
      decorators,
      fixtures,
      rendererConfig: {
        ...rendererConfig,
        containerQuerySelector: &quot;body&quot;,
      },
    } as any);
  }, []);

  useEffect(() =&amp;gt; {
    if (shouldLoadCosmos &amp;amp;&amp;amp; !cosmosLoaded) {
      loadRenderer();
      setCosmosLoaded(true);
    }
  }, [loadRenderer, cosmosLoaded]);

  return &amp;lt;div className=&quot;cosmos-container&quot; /&amp;gt;;
}

export default Cosmos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is almost the same as Rasmus&apos;s version.&lt;/p&gt;
&lt;h3&gt;app/components/cosmos.decorator.tsx&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import { CustomApp } from &quot;~/root&quot;;
import { MemoryRouter } from &quot;react-router-dom&quot;;
import { useEffect, useState } from &quot;react&quot;;
import { createTransitionManager } from &quot;@remix-run/react/dist/transition&quot;;
import { LiveReload, Scripts, ScrollRestoration } from &quot;@remix-run/react&quot;;

const clientRoutes = [
  {
    id: &quot;idk&quot;,
    path: &quot;idk&quot;,
    hasLoader: true,
    element: &quot;&quot;,
    module: &quot;&quot;,
    action: () =&amp;gt; null,
  },
];

let context = {
  routeModules: { idk: { default: () =&amp;gt; null } },
  manifest: {
    routes: {
      idk: {
        hasLoader: true,
        hasAction: false,
        hasCatchBoundary: false,
        hasErrorBoundary: false,
        id: &quot;idk&quot;,
        module: &quot;idk&quot;,
      },
    },
    entry: { imports: [], module: &quot;&quot; },
    url: &quot;&quot;,
    version: &quot;&quot;,
  },
  matches: [],
  clientRoutes,
  routeData: {},
  appState: {} as any,
  transitionManager: createTransitionManager({
    routes: clientRoutes,
    location: {
      key: &quot;default&quot;,
      hash: &quot;#hello&quot;,
      pathname: &quot;/&quot;,
      search: &quot;?a=b&quot;,
      state: {},
    },
    loaderData: {},
    onRedirect(to, state?) {
      console.log(&quot;redirected&quot;);
    },
  }),
};

const Decorator = ({ children }: { children: any }) =&amp;gt; {
  const [result, setResult] = useState&amp;lt;any | null&amp;gt;(null);

  useEffect(() =&amp;gt; {
    /// @ts-expect-error i swear to God importing is allowed
    import(
      /// @ts-expect-error Node expects CommonJS, but we&apos;re giving him ESM
      &quot;@remix-run/react/dist/esm/components&quot;
    ).then(
      (
        { RemixEntryContext }:
          typeof import(&quot;@remix-run/react/dist/components&quot;),
      ) =&amp;gt; {
        setResult(
          &amp;lt;RemixEntryContext.Provider value={context}&amp;gt;
            &amp;lt;MemoryRouter&amp;gt;
              {children}
              &amp;lt;ScrollRestoration /&amp;gt;
              &amp;lt;Scripts /&amp;gt;
              &amp;lt;LiveReload /&amp;gt;
            &amp;lt;/MemoryRouter&amp;gt;
          &amp;lt;/RemixEntryContext.Provider&amp;gt;,
        );
      },
    );
  }, []);

  if (!result) return &amp;lt;&amp;gt;Loading...&amp;lt;/&amp;gt;;

  if (result) return result;
};
export default Decorator;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the file that took me most of the time to implement. One drawback is
that it only renders on the client, so Cosmos won&apos;t have SSR. It basically wraps
around all other components in a directory deeper than it is &lt;code&gt;app/components/**&lt;/code&gt;
in this case. It wraps the provided children around inside a &lt;code&gt;MemoryRouter&lt;/code&gt;
which allows &lt;code&gt;react-router&lt;/code&gt; specific things to work (such as &lt;code&gt;Link&lt;/code&gt; or
&lt;code&gt;NavLink&lt;/code&gt;) It then wraps (decorates) that around in a &lt;code&gt;RemixEntryContext&lt;/code&gt;
Provider together with a very fake context (it was partially stolen from Remix
test data).&lt;/p&gt;
&lt;h3&gt;scripts/generate-cosmos-userdeps.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const { generateUserDepsModule } = require(
  &quot;react-cosmos/dist/userDeps/generateUserDepsModule.js&quot;,
);
const { getCosmosConfigAtPath } = require(
  &quot;react-cosmos/dist/config/getCosmosConfigAtPath&quot;,
);
const { join, relative, dirname } = require(&quot;path&quot;);
const { writeFileSync } = require(&quot;fs&quot;);

const config = getCosmosConfigAtPath(
  join(process.cwd(), &quot;prod.cosmos.config.json&quot;),
);

const userdeps = generateUserDepsModule({
  cosmosConfig: config,
  rendererConfig: {},
  relativeToDir: relative(process.cwd(), dirname(config.userDepsFilePath)),
});

writeFileSync(config.userDepsFilePath, userdeps);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file generates the &lt;code&gt;userDeps&lt;/code&gt; that I talked about earlier. There is no
direct script to generate it on the CLI, but this works.&lt;/p&gt;
&lt;h3&gt;package.json&lt;/h3&gt;
&lt;p&gt;Here are some convinience scripts to build the mix.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;private&quot;: true,
  &quot;sideEffects&quot;: false,
  &quot;scripts&quot;: {
    &quot;build:remix&quot;: &quot;remix build&quot;,
    &quot;build:userdeps&quot;: &quot;node scripts/generate-cosmos-userdeps.js&quot;,
    &quot;build&quot;: &quot;yarn build:userdeps &amp;amp;&amp;amp; yarn build:remix&quot;,
    &quot;dev&quot;: &quot;remix dev&quot;,
    &quot;start&quot;: &quot;remix-serve build&quot;,
    &quot;cosmos&quot;: &quot;cosmos&quot;,
    &quot;build:cosmos&quot;: &quot;cosmos-export --config prod.cosmos.config.json&quot;,
    &quot;start:cosmos&quot;: &quot;serve cosmos-export&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;.gitignore&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/app/cosmos.userdeps.js
/cosmos-export
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks for being with me today. One love!&lt;/p&gt;
</content:encoded><category>code</category><category>tutorial</category></item><item><title>Hello Deno Blog!</title><link>https://www.vixalien.com/blog/deno-blog/</link><guid isPermaLink="true">https://www.vixalien.com/blog/deno-blog/</guid><description>Converting my blog to Deno. Spoiler Alert: it&apos;s easy and fast!</description><pubDate>Sat, 20 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hello! I&apos;m back to blogging!&lt;/p&gt;
&lt;p&gt;Today, I bring very exciting news!! I migrated my blog to &lt;a href=&quot;https://deno.land&quot;&gt;Deno&lt;/a&gt; fully. And now
it runs on the EDGE.&lt;/p&gt;
&lt;h2&gt;Preface&lt;/h2&gt;
&lt;p&gt;Deno is really a solid choice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deno is much faster than Node in practice (think time to build)&lt;/li&gt;
&lt;li&gt;Deno is no longer a project in it&apos;s infancy&lt;/li&gt;
&lt;li&gt;Amazing community, packages &amp;amp; docs (seriously, everything is documented)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://deno.com/deploy&quot;&gt;Deno Deploy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I recommend you watch &lt;a href=&quot;https://www.youtube.com/watch?v=3NR9Spj0DmQ&quot;&gt;Ryan Dahl&apos;s talk about his dream stack&lt;/a&gt;,
which inspired me to migrate my blog to Deno. Ryan is the creator of both Deno
and NodeJS. He created Deno to try and fix the mistakes and regrets that have
been made in NodeJS. Watch &lt;a href=&quot;https://www.youtube.com/watch?v=M3BM9TB-8yA&quot;&gt;10 Things I Regret About Node.js&lt;/a&gt; by Ryan
Dahl, which is his legendary talk about him explaining the mistakes he made with
NodeJS.&lt;/p&gt;
&lt;h2&gt;Plot&lt;/h2&gt;
&lt;p&gt;I decided to use the &lt;a href=&quot;https://github.com/denoland/deno_blog&quot;&gt;&lt;code&gt;deno_blog&lt;/code&gt;&lt;/a&gt; package as it seems too easy to
work with. It&apos;s also made by the Deno core team. For example, here is the entire
configuration of Ryan Dahl&apos;s blog. It&apos;s just a single Javascript file and the
other files are markdown and a few other static files such as images that all
live in &lt;code&gt;/posts&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import blog, { ga, redirects } from &quot;https://deno.land/x/blog@0.3.3/blog.tsx&quot;;

blog({
  title: &quot;Ryan Dahl&quot;,
  author: &quot;Ryan Dahl&quot;,
  avatar: &quot;./ry.jpg&quot;,
  avatarClass: &quot;full&quot;,
  links: [
    { title: &quot;Email&quot;, url: &quot;mailto:ry@tinyclouds.org&quot; },
    { title: &quot;GitHub&quot;, url: &quot;https://github.com/ry&quot; },
  ],
  background: &quot;#fff&quot;,
  middlewares: [
    ga(&quot;UA-91675022-1&quot;),
    redirects({
      &quot;iocp-links.html&quot;: &quot;iocp_links&quot;,
      &quot;rant.html&quot;: &quot;rant&quot;,
    }),
  ],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the beginning, I built the very same app using a similar configuration but I
soon realised I&apos;m too stubborn and need something more custom. Mainly because I
had integrated too much with &lt;code&gt;marked&lt;/code&gt; (which is a library that converts markdown
to HTML) and Deno Blog uses &lt;code&gt;gfm&lt;/code&gt; (Github Flavored Markdown). I know it&apos;s bad
(fragmentation instead of contributing new features) but I forked &lt;code&gt;deno_blog&lt;/code&gt;
and made downstream changes because I thought this might be overkill comparing
to the KISS nature of everything in Deno. I hence created &lt;a href=&quot;https:/github.com/vixalien/vixalien_deno_blog/&quot;&gt;my fork&lt;/a&gt; so
that it can render my site exactly as it was before all while being open to
being extended. Success!&lt;/p&gt;
&lt;p&gt;Here is my current script (Notice the different import URL):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** @jsx h */

import blog, {
  h,
  highlight,
  imageContainer,
} from &quot;https://deno.land/x/vixalien_deno_blog@0.4.9/blog.tsx&quot;;

blog({
  author: &quot;Angelo Verlain&quot;,
  title: &quot;vixalien&apos;s blog&quot;,
  avatar: &quot;favicon/maskable.webp&quot;,
  avatarClass: &quot;border-none rounded-full&quot;,
  port: 3001,
  dateStyle: &quot;medium&quot;,
  links: [
    { title: &quot;Email&quot;, url: &quot;mailto:hey@vixalien.com&quot; },
    { title: &quot;GitHub&quot;, url: &quot;https://github.com/vixalien&quot; },
    { title: &quot;Twitter&quot;, url: &quot;https://twitter.com/vixalientweets&quot; },
    { title: &quot;Resume.pdf&quot;, url: &quot;/Resume.pdf&quot;, icon: &amp;lt;IconFile /&amp;gt; },
  ],
  canonicalUrl: Deno.env.get(&quot;URL&quot;),
  description:
    `Hello! I&apos;m Angelo Verlain, but you can call me vixalien. I am a web \
    developer. This is my website, a collection of projects and writings.`,
  middlewares: [await highlight(), await imageContainer()],
  headLinks: [
    {
      rel: &quot;apple-touch-icon&quot;,
      sizes: &quot;192x192&quot;,
      href: &quot;/favicon/android-chrome-192x192.webp&quot;,
    },
    {
      rel: &quot;icon&quot;,
      type: &quot;image/webp&quot;,
      sizes: &quot;192x192&quot;,
      href: &quot;/favicon/android-chrome-192x192.webp&quot;,
    },
    {
      rel: &quot;icon&quot;,
      type: &quot;image/webp&quot;,
      sizes: &quot;512x512&quot;,
      href: &quot;/favicon/android-chrome-512x512.webp&quot;,
    },
    { rel: &quot;manifest&quot;, href: &quot;/manifest.json&quot; },
    { rel: &quot;shortcut icon&quot;, href: &quot;/favicon.ico&quot; },
    { href: &quot;/css/app.css&quot;, rel: &quot;stylesheet&quot; },
  ],
  lang: &quot;en-US&quot;,
});

function IconFile() {
  return (
    &amp;lt;svg
      width=&quot;1em&quot;
      height=&quot;1em&quot;
      stroke-width={2}
      stroke=&quot;currentColor&quot;
      fill=&quot;none&quot;
      stroke-linecap=&quot;round&quot;
      stroke-linejoin=&quot;round&quot;
      viewBox=&quot;0 0 24 24&quot;
    &amp;gt;
      &amp;lt;path d=&quot;M0 0h24v24H0z&quot; stroke=&quot;none&quot; /&amp;gt;
      &amp;lt;path d=&quot;M14 3v4a1 1 0 001 1h4&quot; /&amp;gt;
      &amp;lt;path d=&quot;M17 21H7a2 2 0 01-2-2V5a2 2 0 012-2h7l5 5v11a2 2 0 01-2 2zM9 9h1M9 13h6M9 17h6&quot; /&amp;gt;
    &amp;lt;/svg&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And all I need to do is run a single commad, then the app is live (INSTANT: NO
COMPILATION).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno run -A mod.tx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then to push it to production, I just push to Github, at which point
&lt;a href=&quot;https://deno.com/deploy&quot;&gt;Deno Deploy&lt;/a&gt; pushes my script to no less than 30 locations worldwide
(the real edge, 👀 Vercel) for free.&lt;/p&gt;
&lt;p&gt;The source of my &lt;a href=&quot;https://github.com/vixalien/dotio2/tree/deno&quot;&gt;new website is hosted on Github&lt;/a&gt; on the deno branch.&lt;/p&gt;
&lt;h2&gt;Aftermath&lt;/h2&gt;
&lt;p&gt;Pictures are work 100 words right?&lt;/p&gt;
&lt;h3&gt;Layout&lt;/h3&gt;
&lt;h4&gt;Before&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/layout-before.webp&quot; alt=&quot;Old layout on Node&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;After&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/layout-after.webp&quot; alt=&quot;New layout on Deno&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Comment: No visible changes, but under the hood, everything changed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Total Files&lt;/h3&gt;
&lt;h4&gt;Before&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/files-before.webp&quot; alt=&quot;Total files in filesystem before&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;After&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/files-after.webp&quot; alt=&quot;Total files in filesystem using Deno&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Command: &lt;code&gt;tree .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Comment: Deno uses significantly less files per project as dependencies are
stored in central cache (&lt;code&gt;~/.cache/deno&lt;/code&gt;). Node&apos;s &lt;code&gt;node_modules&lt;/code&gt; caused it&apos;s
demise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Disk Usage&lt;/h3&gt;
&lt;h4&gt;Before&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/du-before.webp&quot; alt=&quot;Disk usage on Node (~11 dependencies)&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;After&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/du-after.webp&quot; alt=&quot;Disk usage on Deno (source code, =1 dependency)&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/du-after-cache.webp&quot; alt=&quot;Disk usage on Deno (in Deno cache)&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Command: &lt;code&gt;du -h .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Comment: Deno caches every single URL (compressed). If you run an app once,
it&apos;s dependencies will be cached for the next time. Cache is stored in a
central location so that multiple projects can reuse the cache which is not
the case for Node&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Performance&lt;/h3&gt;
&lt;h4&gt;Before&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/perf-before.webp&quot; alt=&quot;Performance on Node (Deployed to Vercel)&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;After&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/deno-blog/perf-after.webp&quot; alt=&quot;New layout on Deno (Deployed to Deno Deploy)&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Measured using &lt;a href=&quot;https://web.dev/measure&quot;&gt;Lighthouse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Comment: Deno is almost always 2 times faster because there is good management
of code on Deno(on my part) which causes only 2 requests to be served, but
nevertheless it&apos;s quite amazing to see Deno win because the app deployed to
Vercel is a static app whereas on Deno some actual JS code runs everytime a
user hits a route. This is because Deno Deploy pushes to more than 30
locations for free so when you actually request a resource, it&apos;s fetched from
the nearest data center unlike Vercel which routes all requests to Washington
DC (in the free plan atleast).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thanks for reading! Have a nice day!&lt;/p&gt;
</content:encoded><category>code</category></item><item><title>Setup Git</title><link>https://www.vixalien.com/blog/setup-git/</link><guid isPermaLink="true">https://www.vixalien.com/blog/setup-git/</guid><description>How I set up Git on my systems with using opinionated defaults.</description><pubDate>Fri, 01 Apr 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After you&apos;ve &lt;a href=&quot;/blog/git&quot;&gt;learnt the basics of Git&lt;/a&gt;, it&apos;s time to make Git your own by creating a workflow that suits you.&lt;/p&gt;
&lt;h2&gt;How to setup Git&lt;/h2&gt;
&lt;p&gt;This is my default setup&lt;/p&gt;
&lt;h3&gt;1. Install Git&lt;/h3&gt;
&lt;p&gt;Git has a high chance of being already installed on some systems. First run &lt;code&gt;git version&lt;/code&gt; to see if it is installed and up-to-date.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install git # on macos

yay -S git # arch (or use pacman)
sudo apt install git # ubuntu
sudo dnf install git # fedora

# this guide is not really meant for windows but everything should work, go to https://git-scm.com/download/win to download git for windows
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Update Git settings&lt;/h3&gt;
&lt;p&gt;Create a file called &lt;code&gt;.gitconfig&lt;/code&gt; in your home directory. On Windows, it is &lt;code&gt;C:\Users\username\.gitconfig&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ~/.gitconfig
# Tell Git who you are
[user]
    email = email@domain.tld
    name = First Last Name
[push]
    default = current
	autoSetupRemote = true
[credential]
    helper = store
[alias]
    silly = commit --amend -a --no-edit
    tree = log --graph --pretty=format:&apos;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&amp;lt;%an&amp;gt;%Creset%n&apos; --abbrev-commit --date=relative --branches
    # logs
    lg1 = log --graph --abbrev-commit --decorate --format=format:&apos;%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)&apos; --all
    lg2 = log --graph --abbrev-commit --decorate --format=format:&apos;%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n&apos;&apos;          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)&apos; --all
    lg = !&quot;git lg1&quot;
    adog = log --all --decorate --oneline --graph
[filter &quot;lfs&quot;]
    required = true
    clean = git-lfs clean -- %f
    smudge = git-lfs smudge -- %f
    process = git-lfs filter-process
# Set the default branch to main
[init]
    defaultBranch = main
[commit]
    verbose = true
[rebase]
    autoStash = true
    stat = true
[tag]
    forceSignAnnotated = true
[fetch]
    prune = true
[color]
    ui = true
[core]
      editor = flatpak run com.visualstudio.code -w

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Github/GitLab/...&lt;/h2&gt;
&lt;p&gt;Git is decentralised, however, you usually need to host your files on a server somewhere to make them available to other people. &lt;strong&gt;Git Forges&lt;/strong&gt; make it easy, and there are many options available, such as GitHub, GitLab, Forgejo, Bitbucket, and more. Here&apos;s how you can set up your local Git to work with your favorite forge correctly.&lt;/p&gt;
&lt;h3&gt;1. Using SSH&lt;/h3&gt;
&lt;p&gt;Your hosted git server will need to authenticate you. We use &lt;a href=&quot;https://en.wikipedia.org/wiki/Secure_Shell&quot;&gt;SSH&lt;/a&gt;, or Secure SHell to generate a pair of keys that will help establish the link to the server. One is a public key which means we can share it with anyone, while the other is a private key which means it needs to stay on the coumputer&lt;/p&gt;
&lt;h4&gt;1. Generate SSH keys&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen -t ed25519 -f ~/.ssh/[filename] -C &quot;[comment]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;[filename]&lt;/code&gt; with the name of your forge, and add a comment to help you remember what this key is for. For example, here is how I&apos;d generate a key for github:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh-keygen -t ed25519 -f ~/.ssh/github -C &quot;email@domain.tld&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Copy the contents of the &lt;code&gt;[filename].pub&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;For a reminder, it&apos;s located at &lt;code&gt;~/.ssh/[filename].pub&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;3. Add your SSH to your forge&lt;/h4&gt;
&lt;p&gt;For convenience, here&apos;s how to add an SSH key to Github.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to your Github&apos;s &lt;a href=&quot;https://github.com/settings/profile&quot;&gt;Account Settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the sidebar, Click on &quot;&lt;a href=&quot;https://github.com/settings/keys&quot;&gt;SSH and GPG keys&lt;/a&gt;&quot;.&lt;/li&gt;
&lt;li&gt;Scroll and click on &quot;New SSH Key&quot;.&lt;/li&gt;
&lt;li&gt;Add a descriptive label and paste the text you had copied from the previous command and save.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;4. Create an SSH config&lt;/h4&gt;
&lt;p&gt;Now, we need to create a config file that tells SSH which key to use for which domain at &lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Host github.com
  HostName github.com
  IdentityFile ~/.ssh/github
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feel free to add even more domains for each forge.&lt;/p&gt;
&lt;h4&gt;5. Test SSH&lt;/h4&gt;
&lt;p&gt;To test SSH, run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -T git@github.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see something like the following, it worked:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hi username! You&apos;ve successfully authenticated, but Github does
not provide shell access.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, don&apos;t mind it if you see a message that says the authenticity of &apos;github.com&apos; can&apos;t be established. It&apos;s normal, and once you trust the domain, SSH will remember that.&lt;/p&gt;
&lt;p&gt;Now clone a repo using SSH by running your clone commands in the form:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone git@github.com:user/repo.git
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Adding a GPG Key to your account.&lt;/h3&gt;
&lt;p&gt;You can show a &lt;code&gt;Verified&lt;/code&gt; Badge or a blue tick next to your commits by setting up a &lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_Privacy_Guard&quot;&gt;GPG&lt;/a&gt; Key and using it to sign your commits. Commits made through the Github web interface are automatically signed.&lt;/p&gt;
&lt;h4&gt;1. Install gpg&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;brew install gpg # on macos
# it&apos;s usually preinstalled on linux
# on windows go to https://www.gnupg.org/download/
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Generate a new GPG Key&lt;/h4&gt;
&lt;p&gt;To create a GPG key quickly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --quick-generate-key [email@domain.tld]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;pre class=&quot;not-code&quot;&amp;gt;
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key &amp;lt;i&amp;gt;&amp;lt;b&amp;gt;&amp;lt;code&amp;gt;YOUR KEY WILL BE HERE&amp;lt;/code&amp;gt;&amp;lt;/b&amp;gt;&amp;lt;/i&amp;gt; marked as ultimately trusted
dgpg: revocation certificate stored as &apos;/home/&amp;lt;i&amp;gt;USERNAME&amp;lt;/i&amp;gt;&amp;lt;wbr&amp;gt;/.gnupg/openpgp-revocs.d/RANDOM40CHARACTERSIDKWHYUREADINGTHISLMAO.rev&apos;
public and secret key created and signed.&lt;/p&gt;
&lt;p&gt;pub   rsa4096 2022-03-31 [SC] [expires: 2022-06-29]
RANDOM40CHARACTERSIDKWHYUREADINGTHISLMAO
uid                      &amp;lt;i&amp;gt;&quot;Beautiful name (The comment you provided) &amp;lt;good@email.dne&amp;gt;&amp;lt;/i&amp;gt;
sub   rsa4096 2022-03-31 [E] [expires: 2022-06-29]
&amp;lt;/pre&amp;gt;&lt;/p&gt;
&lt;p&gt;At this point you will be demanded to enter a password twice. Please remember it or save it somewhere (in a password manager, does paper still exist in the future?)&lt;/p&gt;
&lt;h4&gt;3. Export the key and add it to Github&lt;/h4&gt;
&lt;p&gt;Run the following command using the &lt;code&gt;[KEY_ID]&lt;/code&gt; from the previous command to export your newly-created GPG key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --armor --export [KEY_ID]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will generate a large block of text. In the following format.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-----BEGIN PGP PUBLIC KEY BLOCK-----
[SCRAMBLE]
-----END PGP PUBLIC KEY BLOCK-----
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the whole text including the comments.&lt;/p&gt;
&lt;p&gt;As an example, here&apos;s how to add the key to Github.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;a href=&quot;https://github.com&quot;&gt;Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Go your &lt;a href=&quot;https://github.com/settings/profile&quot;&gt;settings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;On the sidebar, Click on &quot;&lt;a href=&quot;https://github.com/settings/keys&quot;&gt;SSH and GPG keys&lt;/a&gt;&quot;.&lt;/li&gt;
&lt;li&gt;Scroll and click on &quot;New GPG Key&quot; in the &quot;GPG&quot; keys section. (Below the SSH section).&lt;/li&gt;
&lt;li&gt;Paste the text you had copied from the previous command and save.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;4. Configure Git to always sign commits&lt;/h4&gt;
&lt;p&gt;Now that you have GPG set up with Git, run the following commands to tell git to always sign your commits with your GPG key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global user.signingkey [KEY_ID]
git config --global commit.gpgsign true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you do not enable &lt;code&gt;commit.gpgsign&lt;/code&gt; you can always sign each commit individually by running &lt;code&gt;git commit -S&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now try and commit to one of your projects. And it should show a verified commit.&lt;/p&gt;
&lt;h4&gt;Troubleshooting&lt;/h4&gt;
&lt;p&gt;If you run into issues while on the last part and the response says the commit can&apos;t be verified, try running the following command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;test&quot; | gpg --clearsign
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it fails, set the GPG_TTY variable.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export GPG_TTY=$(tty)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then try re-running the command and it should be successful.&lt;/p&gt;
&lt;p&gt;It is also a good idea to kill the GPG client so that it asks for the password the first time.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpgconf --kill all
gpg-agent --daemon
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Sources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://daily-dev-tips.com/posts/how-to-verify-your-commits-on-github/&quot;&gt;Verifying GPG keys on Daily Dev Tips&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kbroman.org/github_tutorial/pages/first_time.html&quot;&gt;Your First time on Git by Karl Broman&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>code</category><category>tutorial</category></item><item><title>Building a blog with Explosiv</title><link>https://www.vixalien.com/blog/explosiv-blog/</link><guid isPermaLink="true">https://www.vixalien.com/blog/explosiv-blog/</guid><description>Building a static lightweight and fast blog with Explosiv.</description><pubDate>Sun, 19 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this year, I created &lt;a href=&quot;https://github.com/vixalien/explosiv&quot;&gt;Explosiv ↗&lt;/a&gt;, a lightweight &amp;amp; fast static site generator that allows pages to be built with JSX. This is a tutorial on how to build a functional blog with Explosiv.&lt;/p&gt;
&lt;h2&gt;Terminology&lt;/h2&gt;
&lt;p&gt;We&apos;ll first talk about how the blog will be built. You can directly &lt;a href=&quot;#code&quot;&gt;jump to the programming part&lt;/a&gt; or directly view &lt;a href=&quot;https://github.com/vixalien/explosiv-blog&quot;&gt;the source code of the final blog on Github&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What&apos;s in &lt;em&gt;this blog&lt;/em&gt;?&lt;/h2&gt;
&lt;p&gt;The blog will be a simple one with room for improvement (I invite you to be creative.) It will simply render a homepage, an about page and a group of posts. That&apos;s it. We&apos;ll not be using any heavy styling or custom components library. Of course we&apos;ll use &lt;strong&gt;Explosiv&lt;/strong&gt; to build the blog and we&apos;ll write the blog posts themselves in &lt;strong&gt;Markdown&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Explosiv?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vixalien/explosiv&quot;&gt;Explosiv ↗&lt;/a&gt; is a NodeJS framework that uses JSX to render pages. It transforms &lt;code&gt;.jsx&lt;/code&gt; files into &lt;code&gt;.html&lt;/code&gt; files. That is: you write code that uses components, run js etc and Explosiv convert them into native HTML ready to be displayed to your favorite web browser.&lt;/p&gt;
&lt;h2&gt;JSX?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://reactjs.org/docs/introducting-jsx.html&quot;&gt;JSX ↗&lt;/a&gt; stands for &lt;strong&gt;XHTML in JSX&lt;/strong&gt; and it allows you to write HTML inside JS files simplifying data binding. JSX was created by the React team and is famously used within React so if you come from React, Explosiv will be easy for you to understand because it uses that same loved JSX syntax. Here is an example of JSX syntax.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// JSX syntax is coool!
let Site = (data) =&amp;gt; {
	return &amp;lt;div&amp;gt;Hello {data.name}!&amp;lt;/div&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;details&amp;gt;&amp;lt;summary&amp;gt;Why not use React instead? or NextJS? or Gatsby? - Rant&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Why not use React instead? or NextJS? or Gatsby?&lt;/h2&gt;
&lt;p&gt;React is only a library. React is in the core of NextJS or Gatsby and they all use it to create their own opinionated way of rendering React pages into HTML pages.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://nextjs.org&quot;&gt;NextJS ↗&lt;/a&gt; is a framework created by Vercel and it provides many features to build very complex web apps: API Routes, Internationalisation, Analytics, Typescript, Image Optimization. It&apos;s many features means you can use it to create any type of website, from TikTok to Twitch to &lt;a href=&quot;https://nextjs.org/showcase&quot;&gt;others ↗&lt;/a&gt;. However this means that it&apos;s also pretty bloated for simple websites like blogs where you end up not using much of the features. And the site ends up containing many and many JS files you&apos;ll not use and takes some time to load.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/explosiv-blog/nextjs-waterfall.webp&quot; alt=&quot;NextJS waterfall&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As you can see in the above screenshot from https://www.joshwcomeau.com/blog/how-i-built-my-blog. NextJS served more than 120 requests weighing 6 MBs in 13 seconds. Hmm??&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby ↗&lt;/a&gt; touts itself as a fast static site generator that also uses React. &lt;a href=&quot;https://news.ycombinator.com/item?id=24670252&quot;&gt;It is NOT fast ↗&lt;/a&gt;. It takes about 30 seconds to make a production build. Imagine what would happen if you customize your site. Plus there are also some features that I think are overkill like GraphQL integrations. I mean I get it, but I would like to install GraphQL as a plugin, not baked into my static site that won&apos;t use it&lt;/p&gt;
&lt;p&gt;&amp;lt;hr&amp;gt;&amp;lt;/details&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;h2&gt;Markdown?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.markdownguide.org/getting-started/&quot;&gt;Markdown&lt;/a&gt; is a lightweight language that will convert plain text to formatted text. It&apos;s the language we&apos;ll use to write our own blog posts. It is used by bloggers, software developers and documentation writers. All those &lt;code&gt;README.md&lt;/code&gt; files on GitHub are Markdown!. You can view the simple syntax of Markdown here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This is a heading

This is a paragraph wil _emphasized_ and **strongly emphasized** text. And this is [a link to Google](https://google.com)

1. This is an ordered list
2. Another list item
3. - A nested unordered list
   - Another list item.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This blog post you are reading is written in markdown too! You can &lt;a href=&quot;https://github.com/vixalien/dotio2/blob/main/blog/explosiv-blog.md&quot;&gt;view the source code here ↗&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Code&lt;/h2&gt;
&lt;p&gt;Explosiv is a NodeJS framework. That means you&apos;ll need to have NodeJS installed first. NodeJS comes with a package manager called &lt;code&gt;npm&lt;/code&gt; and we&apos;ll use it to install Explosiv.&lt;/p&gt;
&lt;h2&gt;1. Install Explosiv&lt;/h2&gt;
&lt;p&gt;The first step is creating a folder for your blog. I used &lt;code&gt;explosiv-blog&lt;/code&gt; for mine. Open the folder in your favorite shell (or command prompt or command line interface). You&apos;ll first need to initialize the folder as a NodeJS project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm init -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NPM will generate a &lt;code&gt;package.json&lt;/code&gt; that will be used to identify your app and manage your dependencies. The next step is to install Explosiv.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install explosiv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;re now ready to start building with Explosiv.&lt;/p&gt;
&lt;h2&gt;2. Create homepage and about page.&lt;/h2&gt;
&lt;p&gt;Now go ahead and create a folder called &lt;code&gt;pages&lt;/code&gt; at the root of your project. This folder will hold all Explosiv pages.&lt;/p&gt;
&lt;h3&gt;Homepage&lt;/h3&gt;
&lt;p&gt;Create a file called &lt;code&gt;index.js&lt;/code&gt; for our homepage. &lt;code&gt;index&lt;/code&gt; is a special name as it denotes that this file will be the first one that the user sees when they visit our site for the first time. Add some simple JSX to our index page to show a warm welcome message to visitors of the blog.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// index.js
let Homepage = () =&amp;gt; {
	return &amp;lt;&amp;gt;
		&amp;lt;Head&amp;gt;
			&amp;lt;title&amp;gt;my blog&amp;lt;/title&amp;gt;
			&amp;lt;meta name=&quot;description&quot; content=&quot;This is my own blog&quot;/&amp;gt;
		&amp;lt;/Head&amp;gt;
		&amp;lt;main&amp;gt;
    		&amp;lt;h1&amp;gt;Welcome to my blog&amp;lt;/h1&amp;gt;
    		&amp;lt;p&amp;gt;This is my cool new blog built with Explosiv. &amp;lt;a href=&quot;/about&quot;&amp;gt;About Me&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
    	&amp;lt;/main&amp;gt;
	&amp;lt;/&amp;gt;
};

export default Homepage;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now see how our site will look in the browser. Switch to your shell and run the following command.&lt;/p&gt;
&lt;h3&gt;Explosiv Development mode&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npx explosiv dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start Explosiv in Development Mode, build the app then serve it locally at http://localhost:3000. Visit the URL to view the homepage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/explosiv-blog/blog-homepage.webp&quot; alt=&quot;Blog homepage&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;About Page&lt;/h3&gt;
&lt;p&gt;Create a file called &lt;code&gt;about.js&lt;/code&gt;. This will be our about page and it will be accessible at &lt;code&gt;/about&lt;/code&gt; on our website.  Add some JSX for the about page as well.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// about.js
let About = () =&amp;gt; {
	return &amp;lt;&amp;gt;
		&amp;lt;Head&amp;gt;
			&amp;lt;title&amp;gt;about my blog&amp;lt;/title&amp;gt;
			&amp;lt;meta name=&quot;description&quot; content=&quot;About my blog&quot;/&amp;gt;
		&amp;lt;/Head&amp;gt;
		&amp;lt;main&amp;gt;
    		&amp;lt;h1&amp;gt;About my blog&amp;lt;/h1&amp;gt;
    		&amp;lt;p&amp;gt;Hey there! Welcome to my new blog built with Explosiv. Here you can find all my blog posts. &amp;lt;a href=&quot;/&quot;&amp;gt;Go back to homepage&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
    	&amp;lt;/main&amp;gt;
	&amp;lt;/&amp;gt;
};

export default About;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now go to http://localhost:3000/about to view the about page. Note that Explosiv automatically rebuilt the app because we started explosiv in development mode.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ProTip:&lt;/strong&gt; Creating a page at &lt;code&gt;pages/about.js&lt;/code&gt; is equivalent to creating one at &lt;code&gt;pages/about/index.js&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. Styling&lt;/h2&gt;
&lt;p&gt;Now the page looks a little bit ugly doesn&apos;t it? We can add CSS styles to make our site look nicer. We&apos;ll create a folder called &lt;code&gt;public/&lt;/code&gt; and create a stylesheet at &lt;code&gt;public/app.css&lt;/code&gt;. Files in the &lt;code&gt;public/&lt;/code&gt; folder will be publicly accessible so you can visit http://localhost:3000/app.css to view the stylesheet.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* public/app.css */
body {
  max-width: 600px;
  padding: 0 20px;
  margin: 0 auto;
  font-family: system-ui, -apple-system, &quot;Segoe UI&quot;, &quot;Roboto&quot;, &quot;Ubuntu&quot;, &quot;Cantarell&quot;, &quot;Noto Sans&quot;, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now to allow Explosiv to include the above CSS, create a document file at &lt;code&gt;pages/_document.js&lt;/code&gt; to customize the overall behavior of the blog.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// pages/_document.js
let Document = () =&amp;gt; {
	return (&amp;lt;html lang=&quot;en&quot;&amp;gt;
		&amp;lt;head&amp;gt;
			&amp;lt;meta charset=&quot;utf-8&quot;/&amp;gt;
			&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot;/&amp;gt;
			&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/app.css&quot;/&amp;gt;
		&amp;lt;/head&amp;gt;
		&amp;lt;body&amp;gt;
			&amp;lt;div class=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
		&amp;lt;/body&amp;gt;
	&amp;lt;/html&amp;gt;)
}

export default Document;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;_document.js&lt;/code&gt; file is a special one because it provides a wrapper to the whole site, hence it can be used to customize the site. Let&apos;s explain the components of this &lt;code&gt;_document.js&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;html lang=&quot;en&quot;&amp;gt;&lt;/code&gt; specify the language of our site.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;meta charset=&quot;utf-8&quot;/&amp;gt;&lt;/code&gt; to specify the character set of our site to prevent incorrect renderings of our site&apos;s text.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;meta name=&quot;viewport&quot;&amp;gt;&lt;/code&gt; to correctly scale the site for mobile users.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot;&amp;gt;&lt;/code&gt; to allow web browsers to fetch our stylesheet.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;div class=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; where the main page&apos;s content will be rendered.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can now refresh http://localhost:3000 in your browser to see the updated page.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/explosiv-blog/styled-blog-homepage.webp&quot; alt=&quot;Looks better already&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now you know how to add custom styles, the limit is the sky. You can start to style your app this way. You can even use PostCSS or Stylus to build stylesheets faster.&lt;/p&gt;
&lt;h2&gt;4. Blog Posts&lt;/h2&gt;
&lt;h3&gt;Writing the first blog post&lt;/h3&gt;
&lt;p&gt;Off to writing the real posts now. Create a blog post at &lt;code&gt;blog/first-post.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: My First Blog Post
description: &quot;The first blog post to be created on this site.&quot;
created: 1639915508100
---

Hello people, a warm welcome to you. This is the first blog post on this site.

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Notice the block at the beginning of the file that starts and ends with &lt;code&gt;---&lt;/code&gt;. This is called Front Matter and is used to describe the blog posts like who wrote it, the title, when was it written etc. Also, be sure to update the &lt;code&gt;created&lt;/code&gt; field to show the real time you created it. &lt;a href=&quot;https://vixalien.com/date&quot;&gt;View time in terms of milliseconds after the UNIX epoch&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Showing the blog posts on the homepage&lt;/h3&gt;
&lt;p&gt;Now comes the part that requires us to be a little bit ingenious. We are going to show all the blog posts on the homepage and provide links to them.&lt;/p&gt;
&lt;p&gt;First of all, we&apos;ll be installing 2 other dependencies to allow us deal with Markdown files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install front-matter marked
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;front- matter&lt;/code&gt;: Allows use to parse page&apos;s front matter.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;marked&lt;/code&gt;: Allows use to parse Markdown files into HTML.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are going to write a script at &lt;code&gt;src/posts.js&lt;/code&gt; that loads all blog posts then give us info about them.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src/posts.js

// Import dependencies
let path = require(&quot;path&quot;);
let fs = require(&quot;fs&quot;);
let fm = require(&quot;front-matter&quot;);

// This function resolves where files or folders are relative to the `cwd` or current working directory.
let resolve = (...link) =&amp;gt; path.resolve(process.cwd(), ...link);

// Where all our blog posts are stored
let blogsPath = resolve(&quot;blog&quot;);

let blogs = fs
    // Get all blog posts in the `blogsPath` folder.
	.readdirSync(blogsPath)
	.map((blog) =&amp;gt; {
     	// Get the slug. i.e `first-post` from `first-post.md`
		let slug = blog.replace(/\.md$/, &quot;&quot;);
		// And return an array of posts and their front matter
		// Example: [ &quot;first-post&quot;, { title: &quot;My First Blog Post&quot;, created: 1639915508100, description: &quot;...&quot; } ]
		return [
			slug,
			{ slug, ...fm(fs.readFileSync(resolve(blogsPath, blog), &quot;utf8&quot;)).attributes },
		]
	})
	// Sort the blog posts by date created
	.sort(([_, a], [$, b]) =&amp;gt; b.created - a.created);

// Export the posts as an object
module.exports = Object.fromEntries(blogs);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are then going to display all blog posts on the homepage. To do this, we&apos;ll create a component at &lt;code&gt;components/posts.js&lt;/code&gt; that uses the post data to display a list of info about posts.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// components/posts.js
// Load the posts as an object.
import postsJSON from &quot;../src/posts&quot;;

let PostsCard = ({ ...props }) =&amp;gt; {
    // Convert the posts object into an array.
	let posts = Object.entries(postsJSON)

	return (
		&amp;lt;p&amp;gt;
			&amp;lt;h2&amp;gt;Posts&amp;lt;/h2&amp;gt;
			&amp;lt;div className=&quot;posts&quot;&amp;gt;
     			{/* Display the posts one by one */}
     			{/* Display each post&apos;s title, date of creation and description with a link to read the post */}
				{posts.map(([slug, { title, description, created }]) =&amp;gt; (
					&amp;lt;p&amp;gt;
						&amp;lt;a href={&quot;/post/&quot; + slug}&amp;gt;{title} &amp;amp;rarr;&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;
						&amp;lt;small&amp;gt;{new Date(created).toDateString()}&amp;lt;/small&amp;gt;&amp;lt;br/&amp;gt;
						&amp;lt;span&amp;gt;{description}&amp;lt;/span&amp;gt;
					&amp;lt;/p&amp;gt;
				))}
			&amp;lt;/div&amp;gt;
		&amp;lt;/p&amp;gt;
	);
};

export default PostsCard;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll then modify &lt;code&gt;pages/index.js&lt;/code&gt; to show blog posts using the newly created component on the homepage.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// index.js
import PostsCard from &quot;../components/posts.js&quot;;

let Homepage = () =&amp;gt; {
	return &amp;lt;&amp;gt;
		&amp;lt;Head&amp;gt;
			&amp;lt;title&amp;gt;my blog&amp;lt;/title&amp;gt;
			&amp;lt;meta name=&quot;description&quot; content=&quot;This is my own blog&quot;/&amp;gt;
		&amp;lt;/Head&amp;gt;
		&amp;lt;main&amp;gt;
    		&amp;lt;h1&amp;gt;Welcome to my blog&amp;lt;/h1&amp;gt;
    		&amp;lt;p&amp;gt;This is my cool new blog built with Explosiv. &amp;lt;a href=&quot;/about&quot;&amp;gt;About Me&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
    		&amp;lt;PostsCard/&amp;gt;
    	&amp;lt;/main&amp;gt;
	&amp;lt;/&amp;gt;
};

export default Homepage;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point you can visit http://localhost:3000 to view the site in a web browser. Notice the list of posts&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/explosiv-blog/blog-homepage-with-posts.webp&quot; alt=&quot;Blog Homepage with Posts&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Showing the blog posts on their URLs&lt;/h3&gt;
&lt;p&gt;Yay!! Our blog can now show posts. But if you click on the link to read the blog post, you&apos;ll reach a 404 page. We are going to create a page that will render each blog post to allow readers to read it.&lt;/p&gt;
&lt;h4&gt;Meet dynamic pages&lt;/h4&gt;
&lt;p&gt;We would need to write each blog&apos;s page like &lt;code&gt;/pages/post/first-blog.js&lt;/code&gt; and &lt;code&gt;/pages/post/second-blog.js&lt;/code&gt; etc. However, there is a feature called &lt;strong&gt;Dynamic Pages&lt;/strong&gt; that simplify the development of related pages. We will be creating one single dynamic page at &lt;code&gt;/pages/post/[slug].js&lt;/code&gt; that will render each post according to the &lt;code&gt;[slug]&lt;/code&gt; provided. For example, visiting &lt;code&gt;/post/first-blog&lt;/code&gt; will render &lt;code&gt;/pages/post/[slug].js&lt;/code&gt; with a &lt;code&gt;slug&lt;/code&gt; that is equal to &lt;code&gt;first-blog&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; You can change &lt;code&gt;[slug]&lt;/code&gt; to any other name you like. For example &lt;code&gt;[id]&lt;/code&gt; or &lt;code&gt;[post]&lt;/code&gt;. However, it is important to enclose the slug in brackets (&lt;code&gt;[]&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// pages/post/[slug].js

// Import dependencies, will be used later
import { promises as fs } from &apos;fs&apos;
import path from &apos;path&apos;
import matter from &apos;front-matter&apos;
import { marked } from &apos;marked&apos;

// The Post component will be used to render each post
const Post = ({ post }) =&amp;gt; (
	&amp;lt;&amp;gt;
		{/* Add a HEAD that shows the title of the page and expose the description of the post */}
		&amp;lt;Head&amp;gt;
			&amp;lt;title&amp;gt;{post.attributes.title} - vixalien&amp;lt;/title&amp;gt;
			&amp;lt;meta name=&quot;description&quot; content={post.attributes.description} /&amp;gt;
		&amp;lt;/Head&amp;gt;
		&amp;lt;main&amp;gt;
			{/* Show a link to the homepage */}
			&amp;lt;div style={{marginTop:&quot;20px&quot;}}&amp;gt;&amp;lt;a href=&quot;/&quot;&amp;gt;Homepage&amp;lt;/a&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;/div&amp;gt;
			&amp;lt;small&amp;gt;{new Date(post.attributes.created).toDateString()}&amp;lt;/small&amp;gt;
			&amp;lt;h1&amp;gt;{post.attributes.title}&amp;lt;/h1&amp;gt;
			&amp;lt;p&amp;gt;{post.attributes.description}&amp;lt;/p&amp;gt;
			&amp;lt;div&amp;gt;===&amp;lt;/div&amp;gt;
			&amp;lt;br/&amp;gt;
			{/* Render the post&apos;s content as HTML in an `article` tag */}
			&amp;lt;article html={post.content}/&amp;gt;
		&amp;lt;/main&amp;gt;
	&amp;lt;/&amp;gt;
)

export default Post;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;getPaths&lt;/code&gt; and &lt;code&gt;getProps&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;However, the above content is not enough for a dynamic page to work. For it to work correctly, we need to export 2 other functions beside the default export which is the main page JSX.&lt;/p&gt;
&lt;p&gt;The first needed export is &lt;code&gt;getPaths&lt;/code&gt; and it is used to determine the number of all acceptable paths (or slugs). For example, it can be used to allow &lt;code&gt;/post/first-blog&lt;/code&gt; to be rendered and &lt;code&gt;/post/unknown-post&lt;/code&gt; to return a 404 page (Not Found). In our case, it&apos;s pretty straightforward to know the range of acceptable slugs. We just read the &lt;code&gt;blog&lt;/code&gt; folder and see which blog posts are there:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Append to the end of `pages/post/[slug].js`
export const getPaths = async () =&amp;gt; {
	// Read all files in the `blog` folder.
	const files = await fs.readdir(path.resolve(&apos;blog&apos;))
	// Remove the training extensions like `.md` (remove the 3 last characters of filename)
	return files.map((filename) =&amp;gt; filename.slice(0, filename.length - 3))
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we know what posts are there, we&apos;ll use &lt;code&gt;getProps&lt;/code&gt; to read info about the post themselves given the slug. The &lt;code&gt;getProps&lt;/code&gt; function is provided with a &lt;code&gt;slug&lt;/code&gt; and use it to get information that will be passed to default export of the function (as props.)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Append to the end of `pages/post/[slug].js`
export const getProps = async (slug) =&amp;gt; {
	// Read the file named `slug`+.md in the `blog` directory with the utf-8 format.
	let post = await fs.readFile(path.join(&apos;blog&apos;, `${slug}.md`), &apos;utf-8&apos;)
	// uses the `front-matter` package to get the post&apos;s attributes.
	post = matter(post)

	// parse the post&apos;s body to get the raw HTML content.
	post.content = marked(post.body)
	// Return an object that will be passed onto the default page export.
	return { post }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now visit http://localhost:3000/post/first-blog to read &lt;code&gt;first-blog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/posts/explosiv-blog/blog-post.webp&quot; alt=&quot;First Blog&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Final Steps&lt;/h2&gt;
&lt;p&gt;Now that you are done, here are a list of things you should do next.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visit &lt;a href=&quot;https://github.com/vixalien/explosiv&quot;&gt;Explosiv&lt;/a&gt; on Github for docs, stars etc.&lt;/li&gt;
&lt;li&gt;Host your site on &lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Provide feedback in Github Issues&lt;/li&gt;
&lt;li&gt;View the &lt;a href=&quot;https://github.com/vixalien/dotio2&quot;&gt;source of this site&lt;/a&gt;, which is written with Explosiv as well.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>code</category><category>tutorial</category></item><item><title>How Rabbet Works</title><link>https://www.vixalien.com/blog/rabbet/</link><guid isPermaLink="true">https://www.vixalien.com/blog/rabbet/</guid><description>A lightweight site that allow you to build pages from  links.</description><pubDate>Fri, 17 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Rabbet&lt;/strong&gt; is a very small and minimal app that allows users to create pages on the Internet with a set of URLs.&lt;/p&gt;
&lt;p&gt;I will try my best to share how Rabbet works in this short write-up. You can try to browse the source to follow along the explanations.&lt;/p&gt;
&lt;p&gt;Useful links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dash.rabbet.me/account&quot;&gt;Rabbet dashboard ↗&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vixalien/rabbet3&quot;&gt;Github ↗&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vixalien.rabbet.me/test&quot;&gt;Test Page ↗&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Rabbet is a monorepo&lt;/h2&gt;
&lt;p&gt;Rabbet is built as a monorepo. That is, it is one repository (folder) but it contain other packages inside of it. The monorepo doesn&apos;t use any repo manager like Lerna or Yarn workspaces as the packages are not tightly coupled. The different packages are for different uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;render:&lt;/strong&gt; a simple library that consume pages as JSON and return rendered HTML ready to be used in &lt;code&gt;pages&lt;/code&gt; or &lt;code&gt;dash&lt;/code&gt; to preview pages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;db:&lt;/strong&gt; a library that allows other packages to access the database where the users and pages are stored.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pages:&lt;/strong&gt; the express app that serves the rabbet pages at [username].rabbet.me/[page-slug].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dash:&lt;/strong&gt; a NextJS app that renders the &lt;a href=&quot;https://dash.rabbet.me/account&quot;&gt;Rabbet dashboard ↗&lt;/a&gt; that serves as a front end that allows users to sign in and create pages.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Render&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;render&lt;/code&gt; was one of the hardest packages to create. The hardest part is that it has different templates so that a user can choose a template they choose.&lt;/p&gt;
&lt;h3&gt;Templates&lt;/h3&gt;
&lt;p&gt;TODO: Allow user to select template in Dashboard UI&lt;/p&gt;
&lt;p&gt;Templates reside in the &lt;code&gt;/templates&lt;/code&gt; directory. The default template is called &lt;strong&gt;Lnks.&lt;/strong&gt; Templates favorably use &lt;strong&gt;React&lt;/strong&gt; and &lt;strong&gt;Stylus&lt;/strong&gt; to render HTML and CSS respectively. Because the &lt;code&gt;render&lt;/code&gt; package is meant to be run in the browser, Templates can&apos;t access the filesystem. So the template is split into 2 files:&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;metadata.js&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This file is run at build time and is used to get all files necessary using &lt;code&gt;require&lt;/code&gt; statements. For example, &lt;strong&gt;Lnks&lt;/strong&gt; use &lt;code&gt;metadata.js&lt;/code&gt; to load it&apos;s JSX file (the one that will be used to render the page into HTML.) And the CSS file that will be used to style the rendered page.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const jsFn = require(&quot;./page.jsx&quot;).default;
const cssString = require(&apos;./style.css&apos;);

module.exports = async () =&amp;gt; {
	return { cssString, jsFn };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;page.js&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This file is used to provide info about the template itself. It exports a JSON object with the following properties and a render function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
	&quot;label&quot;: &quot;lnks&quot;,
	&quot;settings&quot;: [
		{
			&quot;key&quot;: &quot;show_rabbet&quot;,
			&quot;label&quot;: &quot;Show Powered by Rabbet&quot;,
			&quot;description&quot;: &quot;Show the text &apos;Powered by Rabbet&apos; at the end of the page.&quot;,
			&quot;type&quot;: &quot;boolean&quot;, // can be boolean, number, string
			&quot;default&quot;: true,
		}
	],
	render,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The settings property affect Settings related to the template itself.&lt;/p&gt;
&lt;p&gt;The render function is a function that is fed the Page JSON and a meta attribute (which is the data returned from &lt;code&gt;metadata.json&lt;/code&gt;.) The &lt;code&gt;render&lt;/code&gt; function returns an object with properties that will be converted into HTML.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let render = (page, meta) =&amp;gt; {
  return {
    title: &quot;Sample page&quot;,
    about: &quot;A sample page&quot;,
    scripts: [
      { src: &quot;https://uri&quot;, type: &quot;module&quot; },
      { html: &quot;console.log(\&quot;Hello\&quot;)&quot; },
      &quot;https://uri&quot;,
      &quot;console.log(\&quot;Hello\&quot;)&quot;
    ],
    links: [
      { href: &quot;https://uri&quot;, rel: &quot;stylesheet&quot; },
      &quot;https://uri&quot;
    ],
    styles: [
      { html: &apos;body { color: &quot;red&quot; }&apos; },
      &apos;body { color: &quot;red&quot; } &apos;
    ],
    html: &quot;HTML&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;html&lt;/code&gt; attribute is what will be in the body of the rendered page. The reason for &lt;code&gt;styles&lt;/code&gt;, &lt;code&gt;links&lt;/code&gt; and &lt;code&gt;scripts&lt;/code&gt; is that the template may or may not need some scripts. For example, &lt;strong&gt;Lnks&lt;/strong&gt; only use Lite Youtube scripts and external CSSs when the page&apos;s hero is a YouTube Embed.&lt;/p&gt;
&lt;h2&gt;DB&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; All DB functions are expected to be promises.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the most trivial package of all. It provides an &lt;code&gt;/init&lt;/code&gt; script that is called before any database operation (may be called multiple times.)  It&apos;s main export is a file that provide a set of trivial operations such as:&lt;/p&gt;
&lt;h3&gt;DB operations.&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;get(COLLECTION, id):&lt;/strong&gt; Get an item with given &lt;code&gt;id&lt;/code&gt; from &lt;code&gt;collection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;set(COLLECTION, id, data, merge = false):&lt;/strong&gt; Update &lt;code&gt;data&lt;/code&gt; for an item with given &lt;code&gt;id&lt;/code&gt; from &lt;code&gt;collection&lt;/code&gt; and whether to &lt;code&gt;merge&lt;/code&gt; the new data with already exisiting data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;add(COLLECTION, data):&lt;/strong&gt; Add an item with given &lt;code&gt;data&lt;/code&gt; to &lt;code&gt;collection&lt;/code&gt;. The &lt;code&gt;id&lt;/code&gt; is inferred automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;query(COLLECTION, ...queries):&lt;/strong&gt; Perform a set of &lt;code&gt;queries&lt;/code&gt; on given &lt;code&gt;collection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deleteAll(COLLECTION, ...queries):&lt;/strong&gt; Delete all records that match a set of &lt;code&gt;queries&lt;/code&gt; on given &lt;code&gt;collection&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A query is built with the exported &lt;code&gt;where&lt;/code&gt; property. For example, to get all users who have the given username use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import db from &quot;@rabbet/db&quot;;

db.get(&quot;users&quot;, db.where(&quot;username&quot;, &quot;==&quot;, &quot;exampleusername&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Account operations&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;In the future, all account operations will be moved to &lt;code&gt;/account&lt;/code&gt; instead of the current default export.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;getCurrentUser():&lt;/strong&gt; Get the logged in user or null.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;getRealUser({ uid }):&lt;/strong&gt; Get the real user&apos;s info (from the database) based on the &lt;code&gt;uid&lt;/code&gt; returned from login.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;onCurrentUserChange(callback):&lt;/strong&gt; Calls the given &lt;code&gt;callback&lt;/code&gt; when the current user changes (logged out, logged in, loaded, logged out remotely)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Firebase take a while to initialize so when yu call &lt;code&gt;getCurrentUser&lt;/code&gt; for the first time it will always return null. Rabbet doesn&apos;t use &lt;code&gt;getCurrentUser&lt;/code&gt; but instead listen to the &lt;code&gt;onCurrentUserChange&lt;/code&gt; to get a real result when the user has changed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import db from &quot;@rabbet/db&quot;;

let user = await dbgetCurrentUser();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Optimized Account operations&lt;/h3&gt;
&lt;p&gt;Optimized account operations are located at &lt;code&gt;/account&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;login {}:&lt;/strong&gt; Currently an object with &lt;code&gt;{ withGoogle }&lt;/code&gt; that launchs a login dialog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;logout():&lt;/strong&gt; Logout from the current device.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import account from &quot;@rabbet/db/account&quot;;

loginWithGoogleButton.addEventListener(&quot;click&quot;, account.login.withGoogle);
logoutButton.addEventListener(&quot;click&quot;, logout);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Pages&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pages&lt;/code&gt; is a small Express app that resides at &lt;code&gt;https://rabbet.me&lt;/code&gt;. It renders a page from the given url. You can always visit a &lt;a href=&quot;https://vixalien.rabbet.me/test&quot;&gt;test rabbet page ↗&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Assuming the root URL is &lt;code&gt;rabbet.me&lt;/code&gt;, the pages app routes using the following rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[username].rabbet.me/[slug]:&lt;/strong&gt; Return the page with given &lt;code&gt;slug&lt;/code&gt; created by user with given &lt;code&gt;username&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rabbet.me/:&lt;/strong&gt; Redirects to Rabbet dashboard.&lt;/li&gt;
&lt;li&gt;A 404 page with a link to the Rabbet dashboard when a resource isn&apos;t found.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Pages doesn&apos;t currently use the &lt;code&gt;db&lt;/code&gt; package but use a custom implementation that access the firebase API using URLs instead of a native driver (library) because it was thought to be faster. TODO: fix this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Dash&lt;/h2&gt;
&lt;p&gt;This is the dashboard that appears to all visitors of the service. It allows users to log in and create accounts, create, modify and delete pages. It is a NextJS app.&lt;/p&gt;
&lt;p&gt;The package has the following directory structure:&lt;/p&gt;
&lt;p&gt;&amp;lt;pre style=&quot;white-space: break-spaces; font-family: monospace;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;dash&lt;/strong&gt;
├─ &lt;strong&gt;components&lt;/strong&gt;: Components used throughout the app.
├─ &lt;strong&gt;lib&lt;/strong&gt;: Useful libraries and tools.
│  ├─ &lt;strong&gt;constants&lt;/strong&gt;: Holds app constants.
├─ &lt;strong&gt;pages&lt;/strong&gt;: Contains JSX files for the project.
│  ├─ &lt;strong&gt;account&lt;/strong&gt;: Pages related to user accounts&apos;
│  ├─ &lt;strong&gt;pages&lt;/strong&gt;: Pages related to linkpages created by users
│  ├─ &lt;strong&gt;home&lt;/strong&gt;: Homepage: the site which the users see when not logged in.
├─ &lt;strong&gt;public&lt;/strong&gt;: Static files
├─ &lt;strong&gt;partials&lt;/strong&gt;: Reused high-level components
├─ &lt;strong&gt;schemas&lt;/strong&gt;: Validation schemas for different objects
├─ &lt;strong&gt;stores&lt;/strong&gt;: Zustand stores for different objects like user, pages etc.
├─ &lt;strong&gt;stylus&lt;/strong&gt;: Stylus for the site that will be transcribed into CSS&lt;/p&gt;
&lt;p&gt;&amp;lt;/pre&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&amp;lt;br/&amp;gt;&lt;/p&gt;
&lt;p&gt;If you&apos;ve read this much, you might as well create a PR for a template. Inspirations: &lt;a href=&quot;https://skepta.orcd.co/all-in&quot;&gt;Orcd ↗&lt;/a&gt; &lt;a href=&quot;https://ada.lnk.to/Intro&quot;&gt;Linkfire ↗&lt;/a&gt; &lt;a href=&quot;https://dev.page/vixalien&quot;&gt;Dev.page ↗&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>code</category><category>projects</category><category>explained</category></item><item><title>Learn Git!</title><link>https://www.vixalien.com/blog/git/</link><guid isPermaLink="true">https://www.vixalien.com/blog/git/</guid><description>Learn to use Git, a popular Distributed Control System to effectively collaborate and manage your software projects.</description><pubDate>Sun, 01 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;So now you understand a certain programming language and coffee is your new best friend. But everywhere there is code, you see &lt;strong&gt;Git&lt;/strong&gt;. They say a true developer must know Git and here you are, knowing nothing about Git, but longing to getting started.&lt;/p&gt;
&lt;p&gt;I have to admit, Git is as complex as it is popular. However, you don&apos;t need to know all the 160+ commands of Git to do daily operations. Here is a short but comprehensive tutorial that will teach you the most used features of this world-famous version control system.&lt;/p&gt;
&lt;h2&gt;What is a Version Control System?&lt;/h2&gt;
&lt;p&gt;A Version Control System (VCS) is a system that track and manages changes to files. A project with a VCS is usually called a &lt;strong&gt;repository&lt;/strong&gt; or &lt;strong&gt;repo&lt;/strong&gt; for short.&lt;/p&gt;
&lt;p&gt;A VCS allows you to have multiple versions of a project. To visualize the need for a VCS, imagine you are working on a new feature in a project, and a bug that needs to be fixed immediately is reported. With a VCS, you can easily create a new version of your repository, fix the bug, then return to the previous version when you are done fixing the bug. You can also see the different changes you&apos;ve made in your repository over time and can easily rollback when a change is undesired or introduced a bug. If you also modify your files or lose them, you can restore your repository to the last saved snapshot.&lt;/p&gt;
&lt;h2&gt;What is Git?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Git&lt;/strong&gt; is a version control system that manages a collection of files in a certain directory. Git is a &lt;em&gt;Distributed Version Control System.&lt;/em&gt; This means Git does not rely on a central server to store all the versions of a project&apos;s files. Instead, every person &quot;clones&quot; a copy of a repository and get all the history and branches (more on that later) of the project. Although the original source code of a repository may be stored on a third-party hosting service like Github, any person can have their own copy of the project.&lt;/p&gt;
&lt;p&gt;Git was created by Linus Torvalds in 2005 for development of the Linux kernel, with other kernel developers contributing to its initial development.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note: You&apos;ll need to be able to work with the command line fluently before learning Git.&lt;/strong&gt; Even though there are Git GUIs, Git itself is a command line application. If you don&apos;t understand the arts of the command line yet, you can check &lt;a href=&quot;https://www.taniarascia.com/how-to-use-the-command-line-for-apple-macos-and-linux/&quot;&gt;Tania&apos;s command line tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can download Git for macOS, Windows, Linux or build it from source from the &lt;a href=&quot;https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&quot;&gt;Official Git website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When you are finished installing, you can check if git is installed by running the following code in your Terminal (or Command Prompt for windows).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see a result like the following, you are good to go.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git version 2.30.2.windows.1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configuring Git&lt;/h2&gt;
&lt;p&gt;After you install Git, you will have to set some configurations. These include your name and email and are used to mark changes that you introduce in a repository. That way, people can see changes you made and contact you easi;y.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --global user.name &quot;Firstname Lastname&quot;
git config --global user.email username@email.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above commands tell Git your names and email. Remember to change &lt;code&gt;Firstname Lastname&lt;/code&gt; and &lt;code&gt;username@email.com&lt;/code&gt; to your own names and email respectively.&lt;/p&gt;
&lt;p&gt;You can type the following command to check the saved config:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git config --list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you may get results similar to this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;color.ui=auto
core.editor=&apos;C:\Program Files\Sublime Text 3\subl.exe&apos; -w
core.symlinks=true
core.eol=lf
user.email=username@email.com
user.name=Firstname Lastname
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Git workflow&lt;/h2&gt;
&lt;p&gt;Any git repository consists of 3 &quot;trees&quot; maintained by Git:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Working Directory:&lt;/strong&gt; This is your folder with the actual files, the one you can see in File Explorer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Index:&lt;/strong&gt; This is a staging area where Git put files that you are going to commit soon. (i.e. changes that are going to be marked as a new version.) This is because there are certain files that you may want to mark as finished and won&apos;t change, while there are others you are still working on and don&apos;t want to be released in this version.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HEAD:&lt;/strong&gt; This is a reference that points to the last commit you&apos;ve made.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So you &lt;em&gt;add&lt;/em&gt; files from the Working directory to the Index. As you work further, you can add more files to the Index or even remove (&lt;em&gt;restore&lt;/em&gt;) files from the Index. When you are ready, you &lt;strong&gt;commit&lt;/strong&gt; your changes. This will next generate a commit and a new HEAD that points to your last commit.&lt;/p&gt;
&lt;h2&gt;Working with repositories&lt;/h2&gt;
&lt;p&gt;In this tutorial, we will need a new blank directory to learn Git and follow along. You can create a new folder anywhere to start experimenting with Git. I created mine at &lt;code&gt;D:\project&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Initializing a Git repository&lt;/h3&gt;
&lt;p&gt;By Initializing a Git repository, you convert an unversioned project to Git or as in our case, create a new empty repository. (Yep, you can now call your project a repository!) You will need to run the rest of the commands in the root of the project. You can type &lt;code&gt;cd&lt;/code&gt; to see where you are now and check if it is indeed where you are planning to create a new repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After initializing your git repository, you should see a message like: &quot;Initialized empty Git repository in C:/project/.git/&quot; to confirm that a new repo has been created successfully. Note that Git create a hidden folder called &lt;code&gt;.git&lt;/code&gt; to store version and history data.&lt;/p&gt;
&lt;h3&gt;Tracking files&lt;/h3&gt;
&lt;p&gt;You will now need to create two files at the root of your project folder: &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;style.css&lt;/code&gt;. You can use your favorite text editor (I ❤ &lt;a href=&quot;https://www.sublimetext.com/&quot;&gt;Sublime Text&lt;/a&gt;) to save them to the root of your repository.&lt;/p&gt;
&lt;h4&gt;Checking the status of a repository&lt;/h4&gt;
&lt;p&gt;You can check the status of your local repository by using the &lt;code&gt;git status&lt;/code&gt; command. You will use this command a lot while working with Git repositories.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;On branch main

No commits yet

Untracked files:
  (use &quot;git add &amp;lt;file&amp;gt;...&quot; to include in what will be committed)
        index.html
        style.css

nothing added to commit but untracked files present (use &quot;git add&quot; to track)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; On some older versions of Git, the &lt;code&gt;main&lt;/code&gt; branch may be called &lt;code&gt;master&lt;/code&gt; by default, this is normal. To change the name of the master branch to main, run: &lt;code&gt;git checkout master&lt;/code&gt; then &lt;code&gt;git branch -M main&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Staging files&lt;/h4&gt;
&lt;p&gt;The output above tells us that Git knows there are new files in the Working Directory but they are not tracked (They are not part of our Git repo; Git is not tracking changes to them, yet). We have to &lt;strong&gt;stage&lt;/strong&gt; the files using the git &lt;code&gt;add&lt;/code&gt; command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adding/Staging the files put them to Git&apos;s Index.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; (or &lt;code&gt;*&lt;/code&gt;) tells git to add EVERYTHING to the repo.&lt;/p&gt;
&lt;p&gt;You can also add a single file to the index at a time by using the &lt;code&gt;git add &amp;lt;filename&amp;gt;&lt;/code&gt; syntax. &lt;code&gt;git add index.html&lt;/code&gt; would add index.html only.&lt;/p&gt;
&lt;p&gt;You could also add a range of files using the &lt;code&gt;*&lt;/code&gt; (wildcard character). &lt;code&gt;git add hello/*&lt;/code&gt; would add all files in the hello  folder.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Committing changes&lt;/h4&gt;
&lt;p&gt;Let&apos;s check the status again with &lt;code&gt;git status&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;On branch main

No commits yet

Changes to be committed:
  (use &quot;git rm --cached &amp;lt;file&amp;gt;...&quot; to unstage)
        new file:   index.html
        new file:   style.css

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are now ready to commit the files (i.e. mark the changes we made a version).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &quot;Initial Commit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[main (root-commit) 154dcd7] Initial Commit
 2 files changed, 2 insertions(+)
 create mode 100644 index.html
 create mode 100644 style.css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While committing, the option &lt;code&gt;-m&lt;/code&gt; can be used to provide a commit message, in this case &quot;Initial Commit&quot;. You are encouraged to always provide a descriptive commit message that show a gist of the changes you&apos;ve made.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you don&apos;t provide a commit message, Git will use the default editor, set in the installation process on Windows or otherwise Vim, which could be weird for users who don&apos;t know to use Vim because it shows a strange screen where you can no longer enter any commands. To quit Vim, press &amp;lt;kbd&amp;gt;ESC&amp;lt;/kbd&amp;gt; and type &lt;code&gt;:q!&lt;/code&gt; followed by &amp;lt;kbd&amp;gt;ENTER&amp;lt;/kbd&amp;gt;. You can &lt;a href=&quot;https://docs.github.com/en/get-started/getting-started-with-git/associating-text-editors-with-git&quot;&gt;learn how to configure Git to use your favorite text editor&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Branches&lt;/h3&gt;
&lt;p&gt;Branches are used to develop features isolated from each other. The &lt;code&gt;main&lt;/code&gt; branch (or &lt;code&gt;master&lt;/code&gt;, depending on the version of Git) is the &quot;default&quot; branch when you create a repository. Use other branches for development and merge them back to the main branch upon completion.&lt;/p&gt;
&lt;h4&gt;Creating a new branch&lt;/h4&gt;
&lt;p&gt;In this project, we will be creating a new branch to add javascript.
You use the &lt;code&gt;git chekout -b &amp;lt;branch-name&amp;gt;&lt;/code&gt; syntax to add a new branch.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout -b javascript
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Switched to a new branch &apos;javascript&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Listing all branches&lt;/h4&gt;
&lt;p&gt;You can use the &lt;code&gt;git branch&lt;/code&gt; command to list all branches in the current repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git branch
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;* javascript
  main

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The current branch is highlighted in green and an &lt;code&gt;asterisk&lt;/code&gt; is shown before it&apos;s name.&lt;/p&gt;
&lt;p&gt;We can starting working on code in our new branch. Create a file called &lt;code&gt;script.js&lt;/code&gt;. We can the use &lt;code&gt;git status&lt;/code&gt; to view the state of our repo.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git status
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;On branch javascript
Untracked files:
  (use &quot;git add &amp;lt;file&amp;gt;...&quot; to include in what will be committed)
        script.js

nothing added to commit but untracked files present (use &quot;git add&quot; to track)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the output shows that we&apos;re on branch &lt;code&gt;javascript&lt;/code&gt; and that &lt;code&gt;script.js&lt;/code&gt; is untracked. We will need to add it to the
Index.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git add .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then commit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git commit -m &quot;Add script&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[javascript 355fad9] Add script
 1 file changed, 1 insertion(+)
 create mode 100644 script.js

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Reviewing and Merging&lt;/h3&gt;
&lt;p&gt;When you are done implementing a feature in a branch, the only thing left is to &lt;strong&gt;merge&lt;/strong&gt; it to the main branch. Merging is Git&apos;s way of taking a forked history (an independent line of that divergd from the current branch) and incorporating it the current branch. Merging allows you to &quot;combine&quot; different versions of code that diverged from a shared branch. We are going to merge code from the &lt;code&gt;javascript&lt;/code&gt; branch back into the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;We&apos;ll need to go back to the main branch first.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout main
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;git diff&lt;/h4&gt;
&lt;p&gt;It is good practise to first review changes before merging or committing. You can use the &lt;code&gt;git diff&lt;/code&gt; command (short for difference) to show changes between different revisions or paths. (You can use it to compare branches, commits and whatnots).&lt;/p&gt;
&lt;p&gt;If you provide only one argument, Git shows the changes in your working tree relative to the named reference. You can use HEAD to compare it to the latest commit, or a branch name to compare it to the tip of a different branch, which is what we&apos;ll do here.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git diff javascript
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/script.js b/script.js
new file mode 100644
index 0000000..d7ac302
--- /dev/null
+++ b/script.js
@@ -0,0 +1 @@

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;git merge&lt;/h4&gt;
&lt;p&gt;After now reviewing the changes we are about to merge, it&apos;s time we merge the actual changes back into the main branch.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git merge javascript
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Updating 154dcd7..355fad9
Fast-forward
 script.js | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 script.js

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the branch is merged successfully, we can now delete the &lt;code&gt;javascript&lt;/code&gt; branch because it is no longer needed.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git branch -d javascript
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Git tries to auto-merge changes. Unfortunately, this is not always possible and results in conflicts. You are responsible to merge those conflicts manually by editing the files shown by git. After changing, you need to mark them as merged with &lt;code&gt;git add &amp;lt;filename&amp;gt;&lt;/code&gt; before merging changes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Logging&lt;/h3&gt;
&lt;p&gt;You can type &lt;code&gt;git log&lt;/code&gt; see the repository&apos;s history and confirm that the branch has been merged.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;commit 355fad97c2d442bb4a307385bfd6bac2198e825d (HEAD -&amp;gt; main)
Author: Firstname Lastname &amp;lt;username@email.com&amp;gt;
Date:   Sun Aug 1 15:35:31 2021 +0200

    Add script

commit 154dcd7ce38589eb903346f6996e810401fa4910
Author: Firstname Lastname &amp;lt;username@email.com&amp;gt;
Date:   Sun Aug 1 15:07:37 2021 +0200

    Initial Commit

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because git feeds git log results to a pager, you may need to press &lt;code&gt;q&lt;/code&gt; when the log is too long.&lt;/p&gt;
&lt;p&gt;You can add a lot of parameters to make the log look like what you want. Here are a few examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To see only the commits of a certain author:
&lt;code&gt;git log --author=alice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To see a very compressed log where each commit is one line:
&lt;code&gt;git log --pretty=oneline&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Or maybe you want to see an ASCII art tree of all the branches, decorated with the names of tags and branches:
&lt;code&gt;git log --graph --oneline --decorate --all&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;See only which files have changed:
&lt;code&gt;git log --name-status&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are just a few of the possible parameters you can use. For more, see &lt;code&gt;git log --help&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Tags&lt;/h3&gt;
&lt;p&gt;When you are done and ready to mark a version of your code, you can add a git tag.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git tag v1.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The End&lt;/h3&gt;
&lt;p&gt;You are now mostly ready to contribute to projects while using Git to track your projects. Although you must note that this is only a basic tutorial of Git and you still have more to learn namely working with remotes. Git is a very complex software and has more than 160 commands but it&apos;s amazing how you don&apos;t have to know even a half to contribute to open source projects and track your project&apos;s progress. You&apos;ll know more git commands naturally as you become more experienced with Git and when the need for them comes.&lt;/p&gt;
&lt;h3&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;Official Git website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/downloads&quot;&gt;Git Downloads from the official site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rogerdudler.github.io/git-guide/img/trees.png&quot;&gt;No nonsense git cheatsheet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.atlassian.com/git&quot;&gt;Extensive Git guide by Atlassian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>tutorial</category><category>code</category></item><item><title>Let it snow!</title><link>https://www.vixalien.com/blog/let-it-snow/</link><guid isPermaLink="true">https://www.vixalien.com/blog/let-it-snow/</guid><description>Building an optimized snowing weather with the Web Animations API and Promises.</description><pubDate>Sun, 31 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Easter egg: Run this page with &lt;code&gt;#snow&lt;/code&gt; at the end&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;🌨⛄ Do you like snow? Does it snow in your region? Are we in December yet?&lt;/p&gt;
&lt;p&gt;We are going to create virtual snow using the chilly &lt;em&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs.Web/API/Web_Animations_API&quot;&gt;Web Animations API&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;A snowflake!&lt;/h2&gt;
&lt;p&gt;First and foremost, let&apos;s create a snowflake! Our snowflake will be loaded as an &lt;code&gt;.svg&lt;/code&gt; file provided by the beautiful &lt;a href=&quot;https://ionicons.com&quot;&gt;Ionicons&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Loading the snowflake&lt;/h3&gt;
&lt;p&gt;You can store it as a local file then load it as SVG, or use it from Ionicon&apos;s library, but we will be storing it as a string.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let svg_str = `&amp;lt;!-- snowflake svg text here --&amp;gt;`;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Parsing the string into a DOM element&lt;/h3&gt;
&lt;p&gt;Then we&apos;ll use &lt;code&gt;DOMParser&lt;/code&gt; to parse the string into an actual DOM element.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let snow = new DOMParser().parseFromString(svg_str, &quot;text/xml&quot;).children[0];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Because &lt;code&gt;parseFromString&lt;/code&gt; returns a &lt;code&gt;#document&lt;/code&gt;, we used &lt;code&gt;.children[0]&lt;/code&gt; to get the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element instead. (&lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; is equivalent to &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Setting the snowflake to float&lt;/h3&gt;
&lt;p&gt;Our snowflake is fixed (it doesn&apos;t scroll like other elements) and initially, it is placed just above the screen.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;snow.style.position = &quot;fixed&quot;;
snow.style.top = &quot;-24px&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Creating a new snowflake&lt;/h2&gt;
&lt;p&gt;Because our page will have many snowflakes, we&apos;ll clone the snowflake we just created.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let newSnow = () =&amp;gt; {
	let clonedSnow = snow.cloneNode(true);
	// we pass true to clone the node deeply (that is, with all it&apos;s children).
};
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; from now on, our code will be in the &lt;code&gt;newSnow&lt;/code&gt; function.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next, we&apos;ll generate a random left position for that snowflake&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let left = Math.floor(document.body.offsetWidth * Math.random());
// we use Math.floor to ensure left is an integer
clonedSnow.style.left = left + &quot;px&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we&apos;ll just add it to the DOM&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;document.body.append(clonedSnow);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Animating the snowflake&lt;/h3&gt;
&lt;p&gt;Here we&apos;ll just use &lt;em&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs.Web/API/Web_Animations_API&quot;&gt;Web Animations API&lt;/a&gt;&lt;/em&gt; to animate an element. To use the API, we run &lt;code&gt;element.animate(keyframes, options)&lt;/code&gt;. You can read more in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs.Web/API/Web_Animations_API&quot;&gt;MDN Page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To make real snow effect, we will also generate a random speed (think the animation&apos;s duration)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let time = Math.max(10 * Math.random(), 5) * 1000;
// Math.max choose the largest argument it was given. By using it here, we restrict time to be larger than 5.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We will animate the snow to change it&apos;s &lt;code&gt;top&lt;/code&gt; CSS property gradually. At the end, the element will be placed just below the viewport, where you can&apos;t see it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let anim = clonedSnow.animate(
	{
		top: window.innerHeight + 24 + &quot;px&quot;,
	},
	{ duration: time, fill: &quot;forwards&quot; }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One last thing, we&apos;ll do Garbage Collection. When the animation ends, delete that snowflake as it is no longer useful.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// garbage collection
anim.onfinish = el =&amp;gt; el.target.effect.target.remove()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now go ahead, in your console, run &lt;code&gt;newSnow()&lt;/code&gt;. You&apos;ll see a snowflake falling slowly.&lt;/p&gt;
&lt;h2&gt;Snowing!!!&lt;/h2&gt;
&lt;p&gt;So far, we can only create snowflakes on demand by running &lt;code&gt;newSnow()&lt;/code&gt; everytime we need it. What about we create a loop that create as many snowflakes as possible?&lt;/p&gt;
&lt;h3&gt;The problem with native JS loops&lt;/h3&gt;
&lt;p&gt;If you use &lt;code&gt;for&lt;/code&gt; loops or &lt;code&gt;while&lt;/code&gt; or whatever, it won&apos;t work. Why? It will create many snowflakes at a time. Your browser will be filled with snowflakes and unless you are on a supercomputer, your browser will crash, badly. This creates a need for a custom loop!&lt;/p&gt;
&lt;h3&gt;Looping asynchronously&lt;/h3&gt;
&lt;h4&gt;Async Iterate&lt;/h4&gt;
&lt;p&gt;Here&apos;s an implementation of an async loop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let asyncIterate = async (start, iterations, fn) =&amp;gt; {
	// initialize the iterator
	let i = start;
	let call = res =&amp;gt; fn(res)
		// waits for the function to resolves before calling the next iteration
		.then(async result =&amp;gt; {
			if (i &amp;gt;= iterations) return result;
			i++
			return await call(i)
		});
	return await call(i);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It accepts 3 parameters. &lt;code&gt;start&lt;/code&gt; is what the iterator is initialized as. &lt;code&gt;iterations&lt;/code&gt; is pretty self-explanatory. it is the number of times the function will run. then &lt;code&gt;fn&lt;/code&gt; is the function to execute.&lt;/p&gt;
&lt;p&gt;It is important to remember that this is an async loop. That means, it will run the function, &lt;em&gt;then waits that it resolves&lt;/em&gt;. then execute the next iteration.&lt;/p&gt;
&lt;h4&gt;wait&lt;/h4&gt;
&lt;p&gt;Next is the &lt;code&gt;wait&lt;/code&gt; function. This is a wrapper around &lt;code&gt;setTimeout&lt;/code&gt;. It waits some time (in milliseconds), then execute a function. (It is available on the npm registry as &lt;a href=&quot;https://npmjs.com/package/async-wait-then&quot;&gt;async-wait-then&lt;/a&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wait = time =&amp;gt; new Promise(res =&amp;gt; setTimeout(res, time))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is a simple example using &lt;code&gt;wait&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wait(1000)
	.then(() =&amp;gt; console.log(&apos;This will be logged after one second!&apos;));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Using &lt;code&gt;wait&lt;/code&gt; and &lt;code&gt;asyncIterate&lt;/code&gt; to snow&lt;/h4&gt;
&lt;p&gt;By combining &lt;code&gt;wait&lt;/code&gt; and &lt;code&gt;asyncIterate&lt;/code&gt;, we get a powerful function set that uses the Promises API.&lt;/p&gt;
&lt;p&gt;So, to create realistic snow (and prevent browser crashes) we&apos;ll have to wait before we create a snow element&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;asyncIterate(0, 10, async () =&amp;gt; {
	await wait(1000)
	newSnow()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will make it rain 10 snowflakes, but with an interval of 1 seconds between each snowflake&lt;/p&gt;
&lt;p&gt;To make it look more realistic (and add some suspense), we will wait for a random amount of time instead of the static 1 second.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;asyncIterate(0, 10, async () =&amp;gt; {
	await wait(Math.max(3 * Math.random(), 1) * 300)
	newSnow()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But then, this will only create 10 snowflakes. Let&apos;s make it rain forever.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;asyncIterate(0, Infinity, async () =&amp;gt; {
	await wait(Math.max(3 * Math.random(), 1) * 300)
	newSnow()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The full code, complete with some optimizations is posted as &lt;a href=&quot;https://gist.github.com/vixalien/4a9fb790036d01399186e7c3050c2560&quot;&gt;Github Gist&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>projects</category><category>code</category></item><item><title>How Explosiv Works</title><link>https://www.vixalien.com/blog/explosiv/</link><guid isPermaLink="true">https://www.vixalien.com/blog/explosiv/</guid><description>The most lightweight, yet fully featured static-site generator you&apos;ll see.</description><pubDate>Thu, 28 Jan 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Explosiv &lt;a href=&quot;https://npmjs.com/package/explosiv&quot;&gt;npm ↗&lt;/a&gt; &lt;a href=&quot;https://github.com/vixalien/explosiv&quot;&gt;Github ↗&lt;/a&gt; is a static site generator for JSX content.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This article is about how Explosiv works, if you want to want to learn how to use Explosiv go to &lt;a href=&quot;https://github.com/vixalien/explosiv&quot;&gt;Explosiv&apos;s Github Page&lt;/a&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Why Explosiv was made.&lt;/h2&gt;
&lt;p&gt;While I was creating this blog, I thought.&lt;/p&gt;
&lt;p&gt;About all the front-end options I had. Because, I was not going to write static HTML for a fully featured site! While I had already met &lt;a href=&quot;https://google.com?q=stylus+css&quot;&gt;stylus&lt;/a&gt; for all my CSS needs, I was still looking for an option to write my markup seamlessly.&lt;/p&gt;
&lt;h2&gt;React&lt;/h2&gt;
&lt;p&gt;TBH, I love &lt;a href=&quot;https://reactjs.org&quot;&gt;React&lt;/a&gt;. It&apos;s syntax, it&apos;s community, it&apos;s everything really. Yet, React also put so much overhead on your site, like Build times, Babel, Webpack, hydrating or rendering etc. After some digging, I found out the foundation behind React was &lt;a href=&quot;https://reactjs.org/docs/introducting-jsx.html&quot;&gt;JSX&lt;/a&gt;, dubbed as XHTML within Javascript.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// JSX syntax is coool!
let Site = (data) =&amp;gt; {
	return &amp;lt;div&amp;gt;Hello {data.name}!&amp;lt;/div&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well because JSX is tightly coupled with React, I kinda thought it would not work on it&apos;s own, but yet &lt;a href=&quot;https://www.github.com/kartiknair&quot;&gt;kartiknair&lt;/a&gt; made &lt;a href=&quot;https://www.github.com/kartiknair&quot;&gt;Dhow&lt;/a&gt;, which proved me otherwise.&lt;/p&gt;
&lt;h2&gt;Dhow&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.github.com/kartiknair&quot;&gt;Dhow&lt;/a&gt;, is a static site generator, that uses JSX to render static HTML at build time, ready to be served as is. It is quick, &lt;em&gt;very fast&lt;/em&gt; and still uses JSX, so migrating my app from React was &lt;em&gt;a breeeeze&lt;/em&gt;.  Until I encountered the severe limitations of Dhow.&lt;/p&gt;
&lt;p&gt;Dhow was very young. That means It just implemented JSX, nothing else. Many features were lacking. While creating this site, I cloned Dhow. I found myself adding many features as I wanted. I saw it was incredible. I decided to push it to My Github as &lt;a href=&quot;https://www.github.com/vixalien/explosiv&quot;&gt;Explosiv&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Explosiv&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.github.com/vixalien/explosiv&quot;&gt;Explosiv&lt;/a&gt;, being a clone of &lt;a href=&quot;https://www.github.com/kartiknair&quot;&gt;Dhow&lt;/a&gt;, inherits all it&apos;s current features. Here is a simple example.&lt;/p&gt;
&lt;p&gt;First, add &lt;code&gt;explosiv&lt;/code&gt; to your site&apos;s dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i explosiv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And install explosiv globally, so that you can use the CLI wherever you are...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i explosiv -g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, to keep up with modern standards, although I personally like the first syntax more, use &lt;code&gt;npx&lt;/code&gt; to always use the latest version of explosiv&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx explosiv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make an Explosiv site, just create a folder and generate a &lt;code&gt;pages/&lt;/code&gt; directory. Add a simple &lt;code&gt;index.js&lt;/code&gt; file to get started.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// pages/index.js
import Explosiv from &apos;explosiv&apos;

export default () =&amp;gt; (
	&amp;lt;main&amp;gt;
		&amp;lt;h1&amp;gt;Hello there!&amp;lt;/h1&amp;gt;
		&amp;lt;p&amp;gt;
			This is a super simple example of generating static files using Explosiv.
			You can learn more at{&apos; &apos;}
			&amp;lt;a href=&quot;https://github.com/vixalien/explosiv&quot;&amp;gt;here&amp;lt;/a&amp;gt;
		&amp;lt;/p&amp;gt;
	&amp;lt;/main&amp;gt;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To build, and serve, the site use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;explosiv build
explosiv serve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Et voìla! A static site was generated in your &lt;code&gt;/out&lt;/code&gt; directory. Magic right!&lt;/p&gt;
&lt;h3&gt;How it works&lt;/h3&gt;
&lt;p&gt;You can learn how JSX works by &lt;a href=&quot;https://reactjs.org/docs/introducting-jsx.html&quot;&gt;this article&lt;/a&gt; from the React team.&lt;/p&gt;
&lt;p&gt;You can read a very nice article by kartiknair, the creator of Dhow &lt;a href=&quot;https://kartikn.me/writing/jsx-without-react&quot;&gt;about converting JSX into HTML&lt;/a&gt; &lt;strong&gt;without React&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;TL;DR: We use a &lt;em&gt;pragma&lt;/em&gt; function that generate real DOM elements using a minimal DOM implementation, &lt;a href=&quot;https://npmjs.com/package/min-document&quot;&gt;min-document&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// A general overview of how it works.
// !! Not real code
const document = require(&apos;min-document&apos;);

const createElement = (tag, props, ...children) =&amp;gt; {
	const element = document.createElement(tag)

	children.forEach((child) =&amp;gt; {
		element.appendChild(child)
	})

	return element
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We transpile Javascript using &lt;a href=&quot;https://esbuild.github.io&quot;&gt;ESBuild&lt;/a&gt;, a verrry fast, yet fully featured transpiler. We transpile the code in the pages directory  from JSX into pure, native Javascript, while replacing all instances of JSX with our &lt;em&gt;pragma&lt;/em&gt; function.&lt;/p&gt;
&lt;p&gt;The transpiled file will look like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// transpiled/index.js
let { createElement } = require(&apos;explosiv&apos;)

export default () =&amp;gt; (
	createElement(&apos;main&apos;, null,
		createElement(&apos;h1&apos;, null, &apos;Hello there!&apos;),
		createElement(&apos;p&apos;, null,
			&apos;This is a super simple example of generating static files using Explosiv.&apos;,
			&apos;You can learn more&apos;, &apos; &apos;,
			createElement(&apos;a&apos;, {
				href: &quot;https://github.com/vixalien/explosiv&quot;
			}, &apos;here&apos;
		),
	)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the end we render our DOM into static HTML by using &lt;code&gt;document.toString()&lt;/code&gt; and piping the output into the relevant output directory.&lt;/p&gt;
&lt;h3&gt;Impovements over Dhow&lt;/h3&gt;
&lt;p&gt;Explosiv, is a personal project. It is not a competitor, or even an alternative to Dhow, yet all current improvements are listed for anyone interested. Many of these can also be implemented in Dhow if worth it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provide an &lt;code&gt;explosiv serve&lt;/code&gt; command that serve a static directory on a specified port (defaults to  3000).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Head&lt;/code&gt; elements are added on top of &lt;code&gt;document.head&lt;/code&gt; instead of the bottom (allowing overriding existing tags)&lt;/li&gt;
&lt;li&gt;Rewritten for &lt;code&gt;build&lt;/code&gt; code to be independent and ease debugging&lt;/li&gt;
&lt;li&gt;Does not use &lt;code&gt;polka&lt;/code&gt; but the more minimal &lt;code&gt;connect&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use middleware deemed as useful like &lt;code&gt;morgan&lt;/code&gt; which log all requests and &lt;code&gt;compression&lt;/code&gt; which compress resources on HTTP.&lt;/li&gt;
&lt;li&gt;Fixed bugs on edge cases like rendering &lt;code&gt;&amp;lt;&amp;gt;&lt;/code&gt; (aka Fragment tags) as root elements and rendering empty children.&lt;/li&gt;
&lt;li&gt;Added support for &lt;code&gt;className&lt;/code&gt; HTML attribute.&lt;/li&gt;
&lt;li&gt;Fixed bug where self-closing (like &lt;code&gt;&amp;lt;img src=&quot;/path/to.img&quot;&amp;gt;&lt;/code&gt;) elements doesn&apos;t render correctly.&lt;/li&gt;
&lt;li&gt;Use tabs instead of 4 spaces lol!&lt;/li&gt;
&lt;li&gt;And other many but subtle changes.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>code</category><category>projects</category><category>explained</category></item></channel></rss>