<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Márton Salomváry</title>
		<description>Full Stack Web Engineer - Freelancer in Europe</description>
		<language>en-us</language>
		<link>https://salomvary.com</link>
		<atom:link href="https://salomvary.com/feed.xml" rel="self" type="application/rss+xml" />		
		<pubDate>Thu, 12 Mar 2026 19:55:50 +0000</pubDate>
		<lastBuildDate>Thu, 12 Mar 2026 19:55:50 +0000</lastBuildDate>
		
			<item>
				<title>Hosting Plausible Analytics on Dokku (2024 Edition)</title>
				<description>&lt;p&gt;&lt;a href=&quot;https://plausible.io/&quot;&gt;Plausible&lt;/a&gt; is an application that provides web
analytics but actually respects your users privacy. Below are the instructions
on how to install Plausible on &lt;a href=&quot;https://dokku.com/&quot;&gt;Dokku&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a modified version of the work done by &lt;a href=&quot;https://bartmathijssen.com/hosting-plausible-analytics-on-dokku/&quot;&gt;Bart
Mathijssen&lt;/a&gt;
which is the modified version of the work done by &lt;a href=&quot;https://kevinlangleyjr.dev/blog/self-hosting-plausible-analytics-with-dokku&quot;&gt;Kevin Langley
Jr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I made some modifications to the article, changed deployment to using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku
git:from-image&lt;/code&gt; instead of a custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; and switched to pulling the
Docker image from the &lt;a href=&quot;ghcr.io/plausible/community-edition&quot;&gt;GitHub Container
Registry&lt;/a&gt; since images no longer seem to be
published to &lt;a href=&quot;https://hub.docker.com/r/plausible/analytics&quot;&gt;Docker Hub&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;First, you need to SSH into your Dokku Host and create the Plausible app:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku apps:create plausible
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, you need to install the PostgreSQL and Clickhouse plugins, if they aren’t
already installed.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
dokku plugin:install https://github.com/dokku/dokku-clickhouse.git clickhouse
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Create the instances of the PostgresSQL and Clickhouse databases.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku postgres:create plausible-db
dokku clickhouse:create plausible-events-db
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then link the databases to the application (ignore “App image
(dokku/plausible:latest) not found” message in the next steps).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set plausible &lt;span class=&quot;nv&quot;&gt;CLICKHOUSE_DATABASE_SCHEME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http
dokku postgres:link plausible-db plausible
dokku clickhouse:link plausible-events-db plausible
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, you’ll slightly adjust the URL that was provided from linking the
Clickhouse database and change the name of the environment variable for it as
well to match what is expected within the Plausible docker image.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set plausible &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;CLICKHOUSE_DATABASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;dokku config:get plausible CLICKHOUSE_URL&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;/plausible_events_db
dokku config:unset plausible CLICKHOUSE_URL
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you need to generate a secrete key using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set plausible &lt;span class=&quot;nv&quot;&gt;SECRET_KEY_BASE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;openssl rand &lt;span class=&quot;nt&quot;&gt;-base64&lt;/span&gt; 64 | &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then, we’ll set the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BASE_URL&lt;/code&gt; environment variable and set the domain for
the application — make sure to use your domain here.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set plausible &lt;span class=&quot;nv&quot;&gt;BASE_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;https://analytics.example.com
dokku domains:set plausible analytics.example.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Set the required SMTP environment variables to set up transactional emails.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set plausible &lt;span class=&quot;nv&quot;&gt;MAILER_EMAIL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;admin@example.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_HOST_ADDR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mail.example.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_HOST_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;465 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_USER_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;admin@example.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_USER_PWD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;password &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_HOST_SSL_ENABLED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you happen to be using Fastmail for outgoing mail, set up a new SMTP-only
app password in Settings / Privacy &amp;amp; Security / Connected apps &amp;amp; API tokens /
Manage app passwords (access: SMTP).&lt;/p&gt;

&lt;p&gt;Then configure Plausible with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set plausible &lt;span class=&quot;nv&quot;&gt;MAILER_EMAIL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sender@yourdomain.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_HOST_ADDR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;smtp.fastmail.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_HOST_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;587 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_USER_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;username@yourdomain.com &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_USER_PWD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;the app password&amp;gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
                           &lt;span class=&quot;nv&quot;&gt;SMTP_HOST_SSL_ENABLED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Deploy Plausible to Dokku directly from the official Docker image.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku git:from-image plausible ghcr.io/plausible/community-edition:v2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Override the default (wrong) port mapping:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku ports:add plausible http:80:8000
dokku ports:remove plausible http:8000:8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Initialize the databases:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku run plausible /entrypoint.sh db createdb
dokku run plausible /entrypoint.sh db migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, you can generate the Let’s Encrypt SSL certificates.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku letsencrypt:enable plausible
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now open https://analytics.example.com and set up the first account.&lt;/p&gt;

&lt;p&gt;Once the first account is set up, only invited users can set up new accounts.
Override this with the
&lt;a href=&quot;https://github.com/plausible/community-edition/wiki/configuration#disable_registration&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DISABLE_REGISTRATION&lt;/code&gt;&lt;/a&gt;
environment variable.&lt;/p&gt;

</description>
				<pubDate>Thu, 14 Nov 2024 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/plausible-on-dokku.html</link>
				<guid>https://salomvary.com/plausible-on-dokku.html</guid>
			</item>
		
			<item>
				<title>Django: The Good, the Bad and the Ugly</title>
				<description>&lt;p&gt;I’ve been working on the &lt;a href=&quot;/building-shepherd.html&quot;&gt;largest Django project of my career&lt;/a&gt; - which is
&lt;a href=&quot;https://shepherd.com/&quot;&gt;Shepherd.com&lt;/a&gt; - in the last couple of years and have
been planning to share my first-hand real-life experience in building
non-trivial Django applications for a while. I finally took the plunge to write
this up, below follows what I liked and did not like about Django.&lt;/p&gt;

&lt;p&gt;The Django web framework can be used for a wide range of products of various
complexity, and your mileage may vary on how good fit Django will be. My
experience is focused on building a &lt;em&gt;web&lt;/em&gt; application with a little bit of
JavaScript sprinkled on top.  That means not building &lt;a href=&quot;https://en.wikipedia.org/wiki/Single-page_application&quot;&gt;single-page application
(SPA)&lt;/a&gt;, neither a native
mobile app (I do build and optimize for &lt;em&gt;mobile web&lt;/em&gt; though), nor a REST API.
The applications I have built are monoliths (as opposed to microservices)
backed by an SQL database and most of the content is managed using Django’s
admin.&lt;/p&gt;

&lt;p&gt;If you are building something drastically different from what I described
above, my insights and advice here might have limited applicability. Some
aspects are heavily opinionated, feel free to disagree.&lt;/p&gt;

&lt;p&gt;To give the readers some hint on the scale of the codebase, which I consider to
be a medium-sized project (for a small team or a solo developer), here are some
code size stats:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Python: 58k lines, 2 MB code (including tests, excluding generated code)&lt;/li&gt;
  &lt;li&gt;HTML: 8k lines, 400 kB code&lt;/li&gt;
  &lt;li&gt;JavaScript: 4.5k lines, 90 kB code&lt;/li&gt;
  &lt;li&gt;(S)CSS: 1.7k lines, 35 kB code&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-good&quot;&gt;The Good&lt;/h2&gt;

&lt;p&gt;I have to put forward that I generally like working with Django a lot. What did
I particularly like about it? See below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python.&lt;/strong&gt; I absolutely love working with Python. I am proficient in a handful
of other programming languages like Java, Scala, Ruby, JavaScript and
TypeScript, but for me Python comes close to the top when it comes to speed
(both runtime and development cycle), developer friendliness and features
(language and standard library).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping&quot;&gt;ORM&lt;/a&gt;.&lt;/strong&gt;
Object-relational mapping, also known as
&lt;a href=&quot;https://docs.djangoproject.com/en/4.2/topics/db/models/&quot;&gt;Models&lt;/a&gt; in Django.
The ORM has a concise and very powerful way of expressing database structure
and has powerful data modification and querying methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrations.&lt;/strong&gt; Django comes with built-in database migrations, which is a key
feature for an ORM. The generated migration files are expressive and easy to
read, and do the job without any human intervention most of the time. It is
extremely rare that I need to write a migration manually, and when that happens,
it’s still an easy job.&lt;/p&gt;

&lt;p&gt;Running and managing migrations are also very easy and straightforward using the
built-in command line tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Management commands.&lt;/strong&gt; Not that writing command line utilities is rocket
science, but Django makes this even easier. I use these extensively for
maintenance tasks, debugging and reporting and scheduled jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stability.&lt;/strong&gt; Django is 18 years old, and that means &lt;em&gt;stable&lt;/em&gt;. It no longer
makes crazy changes in architectural direction (perhaps it never did), and it
is not being rewritten to a different language. I like that! The release cycle
is predictable, version upgrades are smooth. In the recent years, I’ve gone
through one major version upgrade and many minor ones, all with reasonable
effort.&lt;/p&gt;

&lt;p&gt;Upgrading Django itself (excluding third party packages) usually took an hour
or less even during a major version upgrade. Surprises were due mostly by not
reading release notes carefully enough, or due to some deep customizations to
the admin (most of the admin HTML, CSS and JavaScript structure seems to be
treated as “internal API” but it is sometimes unavoidable to rely on these).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contributing.&lt;/strong&gt; I only have a limited experience with contributing to Django,
but that is all very positive. The &lt;a href=&quot;https://code.djangoproject.com/query?status=assigned&amp;amp;status=closed&amp;amp;status=new&amp;amp;reporter=salomvary&amp;amp;col=id&amp;amp;col=summary&amp;amp;col=status&amp;amp;col=reporter&amp;amp;col=owner&amp;amp;col=type&amp;amp;order=priority&quot;&gt;few issues I ever
filed&lt;/a&gt;
were responded to quickly, and the &lt;a href=&quot;https://github.com/django/django/pull/16278&quot;&gt;one
changeset&lt;/a&gt; I contributed was merged
into the mainline within a reasonable timeline. All in all, it seems like a
friendly and active community.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The admin.&lt;/strong&gt; Django comes with an (optional) &lt;a href=&quot;https://docs.djangoproject.com/en/dev/ref/contrib/admin/&quot;&gt;admin user
interface&lt;/a&gt; that allows
quick &lt;a href=&quot;https://en.wikipedia.org/wiki/Create,_read,_update_and_delete&quot;&gt;CRUD&lt;/a&gt;
operations on database records with almost no code. The admin is highly
customizable, and I have successfully used it to create sophisticated content
management systems (CMS) at a reasonable implementation cost and complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code structure.&lt;/strong&gt; The framework promotes a simple, flat directory structure
called &lt;a href=&quot;https://docs.djangoproject.com/en/dev/ref/applications/&quot;&gt;apps&lt;/a&gt; or
applications within a single project (a “project” is a website, combined from
one or more “apps”). The approach may seem confusing at the beginning but it
allows “clean code” structure, a folder hierarchy that follows the structure of
the problem domain, not implementation details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/jazzband/django-debug-toolbar/&quot;&gt;Django Debug Toolbar&lt;/a&gt;.&lt;/strong&gt;
Not part of Django, it is a third party package. An essential tool in the form
of a web GUI that allows inspecting pretty much everything that happened during
the rendering of a page. I mainly use it for optimizing SQL queries.&lt;/p&gt;

&lt;h2 id=&quot;the-bad&quot;&gt;The Bad&lt;/h2&gt;

&lt;p&gt;There are a few issues that frequently and actively annoy me. I believe that
solving these would lead to a significantly better experience for all Django
developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python’s dependency management&lt;/strong&gt;. &lt;em&gt;The horror! The horror!&lt;/em&gt; It is not Django
to blame, but Python has a number of alternative dependency management tools or
disciplines, and I found all of them to be horrible in some way. I settled with
Poetry, which is somewhat heavyweight and breaks every once in a while, but
does the job most of the time, seems to be the least horrible of all choices.
The state of Python dependency management makes Maven or npm look amazing…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Django templates.&lt;/strong&gt; I have always used Django templates (DTL, the framework’s
own template language) for generating HTML, because the default should be fine
for everyone, right? It is not the most amazing template language, but does the
job just fine, &lt;a href=&quot;https://docs.djangoproject.com/en/dev/topics/templates/&quot;&gt;the official
documentation&lt;/a&gt;
recommends sticking with it. (“If you don’t have a pressing reason to choose
another backend, you should use the DTL.”)&lt;/p&gt;

&lt;p&gt;Unfortunately, the template engine is very slow. Rendering a non-trivial, but
also not insanely large or complex HTML page can take tens, sometimes hundreds
of milliseconds on Standard-1X Heroku dynos.&lt;/p&gt;

&lt;p&gt;Django’s performance optimization docs do actually mention that &lt;a href=&quot;https://docs.djangoproject.com/en/dev/topics/performance/#template-performance&quot;&gt;“heavily
fragmented templates” might be
slow&lt;/a&gt;.
Given that many if not most web applications will want to use reusable HTML
fragments, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{% include %}&lt;/code&gt;, which is documented to
be slow, Django templates are not a great choice, except for trivial stuff.&lt;/p&gt;

&lt;p&gt;Unfortunately, I realized this too late. Django does ship with an &lt;a href=&quot;https://docs.djangoproject.com/en/dev/topics/performance/#alternatives-to-django-s-template-language&quot;&gt;alternate
template
engine&lt;/a&gt;,
Jinja2, and migrating an existing code base to a different templating engine is
a risky and expensive business.&lt;/p&gt;

&lt;p&gt;Lastly, I do not often miss tools from the JavaScript world, but I do miss
&lt;a href=&quot;https://prettier.io/&quot;&gt;Prettier&lt;/a&gt;, the opinionated code formatter for formatting
templates. I use &lt;a href=&quot;https://github.com/psf/black&quot;&gt;Black&lt;/a&gt; for formatting Python and
&lt;a href=&quot;https://github.com/rtts/djhtml&quot;&gt;DjHTML&lt;/a&gt; does an OK job indenting templates, but
it does not come close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refactoring models, apps.&lt;/strong&gt; &lt;em&gt;Clean code&lt;/em&gt; means continuous refactoring as the
application grows, which sometimes involves splitting Django apps, or moving
functionality between them. Although the migration framework supports basic
refactoring like renaming fields or changing types, &lt;a href=&quot;https://realpython.com/move-django-model/&quot;&gt;moving models from one app
to another is a complicated task&lt;/a&gt;,
especially when model inheritance is involved.&lt;/p&gt;

&lt;p&gt;For example I seem to be forever stuck with the poorly named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&lt;/code&gt; model on
one project due to some weird dependency loops in the migration history or the
inheritance hierarchy. I never managed to figure out why.&lt;/p&gt;

&lt;h2 id=&quot;the-ugly&quot;&gt;The Ugly&lt;/h2&gt;

&lt;p&gt;I feel a bit “meh” about a few things in Django, that are not that &lt;em&gt;terrible&lt;/em&gt;,
not deal breakers for most people; quite likely many developers will not even
notice or identify them as problems. Still, my life, and maybe the life of a
few others would be slightly better if these symptoms did not exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lazy queries.&lt;/strong&gt; Model property access can and often does result in on-demand
database queries also known as the &lt;a href=&quot;https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping&quot;&gt;N+1 query
problem&lt;/a&gt;.
This is a Django feature only beneficial for absolute beginners and toy
projects.  Anything and anyone beyond that do not benefit from feature, and it
makes certain page rendering performance issues hard.&lt;/p&gt;

&lt;p&gt;As a solution the ORM has &lt;a href=&quot;https://docs.djangoproject.com/en/4.2/topics/db/optimization/#retrieve-everything-at-once-if-you-know-you-will-need-it&quot;&gt;tools for eager
loading&lt;/a&gt;
(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select_related&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prefetch_related&lt;/code&gt;) but there is no way to force the
usage of them and opt out from the default lazy behavior (although third party
solutions exist).&lt;/p&gt;

&lt;p&gt;One thing worth noting is that when using &lt;a href=&quot;https://docs.djangoproject.com/en/4.2/topics/async/#async-views&quot;&gt;async
views&lt;/a&gt;, lazy
fetch on property access fails, so that partially solves disabling lazy fetch,
but asynchronous Django has it’s own problems (see below).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous support.&lt;/strong&gt; Django has had &lt;a href=&quot;https://docs.djangoproject.com/en/4.2/topics/async/&quot;&gt;asynchronous
support&lt;/a&gt; since version
3.1. The catch is that it is still work-in-progress, therefore some parts of
the framework do have asynchronous support, some do not.&lt;/p&gt;

&lt;p&gt;That means one will end up sprinkling code with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sync_to_async&lt;/code&gt; helpers, which
quite quickly gets very ugly.&lt;/p&gt;

&lt;p&gt;There is however a bigger catch with async Django, and that is the ORM. Even
though the ORM has “asynchronous support” meaning it has async query methods
like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await Book.object.aget()&lt;/code&gt;, the underlying implementation is not truly
asynchronous and does not allow concurrent database operations at all. This
non-feature is not even documented clearly, in fact &lt;a href=&quot;https://forum.djangoproject.com/t/are-concurrent-database-queries-in-asgi-a-thing/24136&quot;&gt;I had to learn it the hard
way&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To add to the offense, when using an ASGI (asynchronous) server, &lt;a href=&quot;https://code.djangoproject.com/ticket/33497&quot;&gt;there is no
connection pooling&lt;/a&gt; available,
which on my projects turned out to be a performance killer and therefore a deal
breaker.&lt;/p&gt;

&lt;p&gt;The lack of truly asynchronous ORM means it is hard to optimize views that
require dozens or queries, no matter whether using sync or async, they will be
executed one after another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third party Django packages.&lt;/strong&gt; Django is a popular framework and there are
&lt;a href=&quot;https://djangopackages.org/&quot;&gt;third party packages for everything one can
imagine&lt;/a&gt;. But most of these are not of “industry
grade” quality. Which is OK, this is all free and open source, but one has to be
prepared for eventually forking most third party packages in-use, and maintain
the fork perpetually.&lt;/p&gt;

&lt;p&gt;And even the packages that get semi-regular maintenance often break on new
Django versions, sometimes taking weeks to catch up (even with community
contributions).&lt;/p&gt;

&lt;p&gt;This is of course not specific to Django, all open source communities suffer
from lack of resources or interest for maintenance. I do my share of making
things better by contributing fixes and filing issues, I wish everyone was
doing that.&lt;/p&gt;

&lt;p&gt;Notable exception are the packages maintained by the &lt;a href=&quot;https://jazzband.co/&quot;&gt;Jazzband
community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limited alternatives other than ORM or raw SQL.&lt;/strong&gt; Django’s ORM is very
capable and one is able to express rather sophisticated SQL queries using
Python syntax.  But there are limits to this, and no ORM will likely ever cover
all SQL features (standard or vendor specific), and that is fine…&lt;/p&gt;

&lt;p&gt;But a few times I hit a wall with a more complex ORM query, which by then is
rather complicated to abandon, and sometimes queries are reused elsewhere, and
switching to raw SQL means no easy reusing of SQL fragments (except for
manipulating strings that contain bits of SQL queries).&lt;/p&gt;

&lt;p&gt;Ideally, there should be some low level query builder interface that allows
expressing any SQL grammar using Python syntax, but also understand that it’s
not something trivial to add to the ORM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Almost everything is mutable.&lt;/strong&gt; For those with background in functional
programming and immutable data structures, Django might seem like a horrible
place. In Python, as well as in Django, almost everything is mutable, object
oriented programming and class based inheritance is widely used and relied on.&lt;/p&gt;

&lt;p&gt;Not a big deal for me, but I sometimes wish somethink like Python+Django
existed that is strictly immutable. I’ve probably spent too many years
programming Scala…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lack of service layer.&lt;/strong&gt; In most non-trivial web applications one eventually
want to contain “business logic” somewhere, ideally following some established
patterns.&lt;/p&gt;

&lt;p&gt;The missing piece in Django is something between models and views (n.b. what
Django calls “views” are called “controllers” elsewhere).&lt;/p&gt;

&lt;p&gt;Without further structural decisions, business logic in Django might end up in
views, models,
&lt;a href=&quot;https://docs.djangoproject.com/en/dev/topics/db/managers/&quot;&gt;managers&lt;/a&gt; and
&lt;a href=&quot;https://docs.djangoproject.com/en/4.2/ref/models/querysets/&quot;&gt;querysets&lt;/a&gt;. Most
often views end up being rather “fat”, containing a lot of business logic.&lt;/p&gt;

&lt;p&gt;The perfect architecture can be debated, and is inndeed debated a lot, but I
personally believe in “skinny” views and “skinny” models.  That means
implementing most of the business logic in a layer called
&lt;a href=&quot;https://www.martinfowler.com/eaaCatalog/serviceLayer.html&quot;&gt;“services”&lt;/a&gt;. These
services are usually plain Python modules named “something_service” containing
functions that execute &lt;a href=&quot;https://martinfowler.com/eaaCatalog/transactionScript.html&quot;&gt;“transaction
scripts”&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is especially useful, or even inevitable for transactions involving
multiple models and/or external services (e.g. REST API calls), shared between
multiple views.&lt;/p&gt;

&lt;p&gt;When the admin app is involved, the service approach becomes messy though.
There is no simple way to force the admin to always use whatever business layer
we dreamed up, for good reasons (admin is truly just a CRUD GUI on top of
database records).&lt;/p&gt;

&lt;p&gt;What I ended up doing is overriding the admin’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save_model&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save_related&lt;/code&gt;
methods and call service methods from there, where it was necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No zero-downtime migrations.&lt;/strong&gt; Not that many ORM-migration tools have that
included, but it is worth noting that changes to the database schema can block
production applications from accessing the database for the entire duration of
the change, which, for large tables, can cause a significant amount of
downtime.&lt;/p&gt;

&lt;p&gt;Outside of Django are ways around this, for example
&lt;a href=&quot;https://docs.percona.com/percona-toolkit/pt-online-schema-change.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pt-online-schema-change&lt;/code&gt;&lt;/a&gt;
for MySQL, but it would be nice to have something like this integrated into
Django. I think it would fit Django’s role of a “heavyweight ORM”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No European Django Foundation entity.&lt;/strong&gt; If you use Django for business, a
good way of supporting the project is with
&lt;a href=&quot;https://www.djangoproject.com/fundraising/&quot;&gt;donations&lt;/a&gt;. As the Django
Foundation is registered in the USA, making these donations tax deductible can
be hard or impossible in Europe (at least in Germany, where I am tax resident).&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;All in all, despite the gripes above, I am very happy with Django and will keep
using it on real-life projects. It is rock solid and truly lives up to it’s
slogan, which is “The web framework for perfectionists with deadlines”.&lt;/p&gt;

&lt;p&gt;I hope some day soon a truly asynchronous ORM becomes a reality. And my next
project will use Jinja templates.&lt;/p&gt;

</description>
				<pubDate>Mon, 04 Dec 2023 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/django-the-good-the-bad-and-the-ugly.html</link>
				<guid>https://salomvary.com/django-the-good-the-bad-and-the-ugly.html</guid>
			</item>
		
			<item>
				<title>Setting up GnuCash with Deutsche Bank Germany</title>
				<description>&lt;p&gt;I’ve been recently trying to get my personal finances under control, and part
of that was introducing &lt;a href=&quot;https://www.gnucash.org/&quot;&gt;GnuCash&lt;/a&gt; to my workflow.
GnuCash itself might we worth a separate blog post, but here I’m only
documenting how to connect GnuCash with Deutsche Bank in order to automatically
import transactions.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;GnuCash 4.13 or newer. Might work with other versions but I have not tried.&lt;/p&gt;

&lt;p&gt;You need to have a bank account at Deutsche Bank Germany. I do not know how
much of this applied to Deutsche Bank in other countries.&lt;/p&gt;

&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; you need to have “HCBI Plus” activated. Log in to online banking and
then go to Online SelfServices, Sonstiges, then “TelefonBanking PIN bestellen
und HBCI+ Freischaltung” and then make sure “HBCI Plus Status” is activated
(“Die HBCI Plus-Nutzung ist aktuell aktiviert.”). If not, you might need to
activate it. (For me it was already activated and I can’t recall activating it
myself, therefore that might be the default.)&lt;/p&gt;

&lt;h2 id=&quot;setting-up&quot;&gt;Setting up&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Open GnuCash&lt;/li&gt;
  &lt;li&gt;Open Tools / Online Banking Setup&lt;/li&gt;
  &lt;li&gt;Click Next, Start AqBanking Setup, click Create User&lt;/li&gt;
  &lt;li&gt;Select “HCBI backend using AqHBCI”&lt;/li&gt;
  &lt;li&gt;In the next step, select “Setup a PIN/TAN account” an click Run&lt;/li&gt;
  &lt;li&gt;In the “Please select the bank” step, enter the following:
    &lt;ul&gt;
      &lt;li&gt;Bank Code: your “Bankleitzahl”, e.g. 10070000&lt;/li&gt;
      &lt;li&gt;Bank Name: Deutsche Bank&lt;/li&gt;
      &lt;li&gt;Server URL: https://fints.deutsche-bank.de/&lt;/li&gt;
      &lt;li&gt;You can also auto-fill the last two using the Select button next to Bank Code.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;In the next step, enter your user details:
    &lt;ul&gt;
      &lt;li&gt;User name: this can be whatever you want, for example the account holder’s full name&lt;/li&gt;
      &lt;li&gt;User Id and Customer Id: this is your branch number (Filiale, 3 digits)
plus your account number (Konto, 7 digits) plus your sub-account number
(Unterkonto, 2 digits).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;On the certificate Received screen, accept the certificate if you get “The
certificate is valid” near the bottom.&lt;/li&gt;
  &lt;li&gt;When prompted to select a TAN method, select “901 - Mobile-TAN (Version 6)”&lt;/li&gt;
  &lt;li&gt;When prompted for a PIN, use the 5 digit code you use for signing in to online
banking.&lt;/li&gt;
  &lt;li&gt;At some point you will also be propmted for a TAN which you will receive via
SMS text message.&lt;/li&gt;
  &lt;li&gt;Once finished, close the setup wizard, and on the “Match Online accounts with
GnuCash accounts” screen, select which GnuCash account he Deutsche Bank
transactions should be synchronized to. If you don’t have one yet, create
something like “Assets/Current Assets/Deutsche Bank”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;testing-and-using&quot;&gt;Testing and using&lt;/h2&gt;

&lt;p&gt;Once the setup is done, let’s see if it actually works.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Open the GnuCash account for Deutsche Bank.&lt;/li&gt;
  &lt;li&gt;Go to Actions / Online Actions / Get Balance.&lt;/li&gt;
  &lt;li&gt;It should show the balance and ask if you want to reconcile.&lt;/li&gt;
  &lt;li&gt;Then go to Actions / Online Actions / Get Transactions.&lt;/li&gt;
  &lt;li&gt;Enter some time frame that’s less than the last 90 days.&lt;/li&gt;
  &lt;li&gt;If you get an error, saying “Error on executing job. Status: rejected (5).”,
you need an additional step below.&lt;/li&gt;
  &lt;li&gt;As the next step, you should see the “Generic import transaction matcher”
window, where you will see the to-be-imported transactions and can decide
what to do with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;fixing-error-on-executing-job&quot;&gt;Fixing ‘Error on executing job’&lt;/h2&gt;

&lt;p&gt;These instructions are specific to macOS but should mostly be valid with
appropriate changes on Linux and Windows:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Open the Terminal app&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Applications/Gnucash.app/Contents/MacOS/aqhbci-tool4 listaccounts -v&lt;/code&gt;,
which should print out something like this (find the right line matching the
Deutsche Bank account in question):
    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Account 0: Bank: 10070000 Account Number: xxxxxxxxx  SubAccountId: EUR  Account Type: bank LocalUniqueId: 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;p&gt;Note the number after LocalUniqueId.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Applications/Gnucash.app/Contents/MacOS/aqhbci-tool4 getaccsepa -a
LocalUniqueId&lt;/code&gt; but substitute LocalUniqueId with your actual id number.&lt;/li&gt;
  &lt;li&gt;When prompted (“Input:”), enter your online bank PIN.&lt;/li&gt;
  &lt;li&gt;Try Actions / Online Actions / Get Transactions again, as described above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;I found the fix to the transaction job in &lt;a href=&quot;https://www.aquamaniac.de/rdm/issues/148#note-20&quot;&gt;this AqBanking bug
report&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Very little &lt;a href=&quot;https://www.deutsche-bank.de/pk/digital-banking/online-banking/sicherheit/sicherheitsverfahren.html#parsys-accordion_copy-accordionParsys-accordionentry_40095&quot;&gt;documentation is available for HBCI by Deutsche Bank&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;GnuCash &lt;a href=&quot;https://bugs.gnucash.org/show_bug.cgi?id=797652&quot;&gt;does not support
photoTAN&lt;/a&gt; but the bug report
has a workaround which I have not tried yet.&lt;/li&gt;
  &lt;li&gt;I have not yet figured out how to synchronize Deutsche Bank credit card
transactions.&lt;/li&gt;
&lt;/ul&gt;
</description>
				<pubDate>Sun, 05 Mar 2023 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/gnucash-deutsche-bank.html</link>
				<guid>https://salomvary.com/gnucash-deutsche-bank.html</guid>
			</item>
		
			<item>
				<title>Replacing Heroku with Dokku (for Pet Projects)</title>
				<description>&lt;p&gt;August 25, 2022 was a sad day in cloud computing, when &lt;a href=&quot;https://blog.heroku.com/next-chapter&quot;&gt;Heroku
announced&lt;/a&gt; that they are discontinuing
all free plans. Reason being too much time spent on fraud, which I guess
translates to folks abusing Heroku for crypto mining.&lt;/p&gt;

&lt;p&gt;Anyway, as of writing this, there is already no more free Heroku. Now the
question is, should I pay Heroku or someone else from now on, to host my pet
projects and experiments? There is definitely more competition in the non-free
cloud offerings than in the free ones.&lt;/p&gt;

&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; use AWS but that’s the opposite of fun, and pet projects should still
be &lt;em&gt;fun&lt;/em&gt;. I like DigitalOcean since they are better on terms of developer
friendliness, but then that’s still not as simple as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push heroku main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I decided to have &lt;em&gt;my own “Heroku”&lt;/em&gt;, in the form of a self-hosted
&lt;a href=&quot;https://dokku.com/&quot;&gt;Dokku&lt;/a&gt; server. It’s a tiny PAAS that you can host on a
Linux machine and provides a Heroku-like command line interface to interact
with.&lt;/p&gt;

&lt;p&gt;But you need a &lt;em&gt;server&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;My first idea was to install it the NAS that is running 24/7 in my closet but
it’s neither powerful enough nor do I want experimental stuff running as root
right next to my precious private data.&lt;/p&gt;

&lt;p&gt;I decided to use a dirt cheap (4.5 EUR / month) cloud server at
&lt;a href=&quot;https://www.hetzner.com/cloud&quot;&gt;Hetzner&lt;/a&gt;, not only because it’s cheap but also
because pet projects are for &lt;em&gt;trying out new things&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Below is a step-by-step guide to setting up Dokku in a (mostly) automated way.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-the-server&quot;&gt;Setting up the server&lt;/h2&gt;

&lt;p&gt;I’m a Believer of Configuration as Code, therefore after signing up to Hetzner
and obtaining an API token, I did the rest of the setup using
&lt;a href=&quot;https://en.wikipedia.org/wiki/Ansible_(software)&quot;&gt;Ansible&lt;/a&gt;. The instructions
below assume that you have a basic understanding how Ansible works.&lt;/p&gt;

&lt;p&gt;I’ve put everything in an &lt;a href=&quot;https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html&quot;&gt;Ansible playbook&lt;/a&gt; named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku.yml&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up a server on Hetzner&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;127.0.0.1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;server&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;module_defaults&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hcloud_server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hetzner_api_token&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hcloud_ssh_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;api_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hetzner_api_token&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create an ssh_key&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hcloud_ssh_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my-laptop&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;public_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;lookup(&apos;file&apos;,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;~/.ssh/id_rsa.pub&apos;)&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;present&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create a server&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;hcloud_server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dokku&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;server_type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cx11&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-18.04&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fsn1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ssh_keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;my-laptop&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;present&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;hcloud_server_result&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Print server values&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hcloud_server_result&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Add server to inventory&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;ansible.builtin.add_host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dokku_hetzner&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hcloud_server_result.hcloud_server.ipv4_address&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&apos;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ansible_user&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Assuming you’ve set up an &lt;a href=&quot;https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html&quot;&gt;Ansible
variable&lt;/a&gt;
named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hetzner_api_token&lt;/code&gt; holding your API token (preferably using a vault,
running the following commands should get a server up and running:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install Ansible and dependencies&lt;/span&gt;
pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ansible
pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;hcloud
ansible-galaxy &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;dokku_bot.ansible_dokku
ansible-galaxy collection &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;hetzner.hcloud

&lt;span class=&quot;c&quot;&gt;# Set up the server&lt;/span&gt;
ansible-playbook dokku.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It is assumed that you want to use the publish ssh key at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/id_rsa.pub&lt;/code&gt;
for connecting to the server. If that’s not the case, tweak the playbook to
your liking.&lt;/p&gt;

&lt;p&gt;At this point &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh root@the-address-printed-under-server-values&lt;/code&gt; should get you
a shell on the server.&lt;/p&gt;

&lt;h2 id=&quot;set-up-a-domain&quot;&gt;Set up a domain&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://dokku.com/docs/getting-started/installation/#system-requirements&quot;&gt;It is recommended&lt;/a&gt; (but not mandatory) that you set up a wildcard domain for the
Dokku server. I’m using Cloudflare DNS, therefore this section depends on your
provider. You can also decide to set up the domain manually.&lt;/p&gt;

&lt;p&gt;Add the following to the playbook:&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Configure DNS entries for Dokku&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;127.0.0.1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;local&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dns&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create an A record for mydomain.com&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;cloudflare_dns&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mydomain.com&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;A&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cloud&apos;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hcloud_server_result.hcloud_server.ipv4_address&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&apos;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;account_email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cloudflare_account_email&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;account_api_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cloudflare_api_token&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;present&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Create an A record for *.mydomain.com&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;cloudflare_dns&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;zone&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mydomain.com&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;A&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;*.cloud&apos;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;hcloud_server_result.hcloud_server.ipv4_address&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&apos;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;account_email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cloudflare_account_email&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;account_api_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cloudflare_api_token&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;present&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You will have to set up Ansible variables for the DNS provider credentials.&lt;/p&gt;

&lt;p&gt;Running the playbook again should set up the DNS entries:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ansible-playbook dokku.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Verify by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dig mydomain.com&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh root@mydomain.com&lt;/code&gt;. It
might take a while until the DNS changes are propagated.&lt;/p&gt;

&lt;h2 id=&quot;install-dokku&quot;&gt;Install Dokku&lt;/h2&gt;

&lt;p&gt;Because the Dokku folks are amazing, they created an Ansible role for setting it up. Add the following to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set up Dokku on the server&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dokku_hetzner&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dokku&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dokku_bot.ansible_dokku&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dokku_hostname&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mydomain.com&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dokku_key_file&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/root/.ssh/authorized_keys&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;dokku_plugins&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;postgres&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://github.com/dokku/dokku-postgres.git&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;letsencrypt&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://github.com/dokku/dokku-letsencrypt.git&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can tweak &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku_plugins&lt;/code&gt; to your liking. Plugins can also be added later,
keep &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;letsencrypt&lt;/code&gt; if you want to use free SSL certificates.&lt;/p&gt;

&lt;p&gt;Installing Dokku with this command will take a while (~10 minutes):&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ansible-playbook dokku.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dokku&lt;/code&gt; command should be working on the server:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@dokku:~# dokku domains:report --global
=====&amp;gt; Global domains information
       Domains global enabled:        true                     
       Domains global vhosts:         mydomain.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;post-install&quot;&gt;Post-install&lt;/h2&gt;

&lt;p&gt;A few steps are necessary to enable using free &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s
Encrypt&lt;/a&gt; TLS certificates.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=me@mydomain.com
dokku letsencrypt:cron-job --add
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Use a valid email address :)&lt;/p&gt;

&lt;p&gt;&lt;small&gt;This should ideally be done with Ansible as well.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;It is not mandatory, but the rest assumes you &lt;a href=&quot;https://dokku.com/docs/deployment/remote-commands/#official-client&quot;&gt;have the official Dokku client installed&lt;/a&gt; on your development machine (wherever you deploy your apps &lt;em&gt;from&lt;/em&gt;).&lt;/p&gt;

&lt;h2 id=&quot;deploy-an-app&quot;&gt;Deploy an app&lt;/h2&gt;

&lt;p&gt;Now you are ready to deploy your Heroku-compatible apps to your own server
using a few simple commands.&lt;/p&gt;

&lt;p&gt;If you don’t have any ready, you can play with Heroku’s own example app, like this one:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/heroku/python-getting-started.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then in the app’s root folder (assuming a Django app using PostgreSQL):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku apps:create my-app
dokku postgres:create my-app-database
dokku postgres:link my-app-database my-app
dokku letsencrypt:enable
git push dokku master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Deploy a new version after making a git commit using:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git push dokku master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The app will be available at my-app.mydomain.com.&lt;/p&gt;

&lt;p&gt;Note that if you are using the Django example app above, a few more tweaks are
needed:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dokku config:set ALLOWED_HOSTS=my-app.mydomain.com SECRET_KEY=something-random
dokku run ./manage.py createsuperuser
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Happy pushing, feedback welcome! 👇&lt;/p&gt;
</description>
				<pubDate>Mon, 19 Dec 2022 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/setting-up-dokku.html</link>
				<guid>https://salomvary.com/setting-up-dokku.html</guid>
			</item>
		
			<item>
				<title>Building the Shepherd.com Web Engine</title>
				<description>&lt;p&gt;Some time in late 2020 I got approached by &lt;a href=&quot;https://twitter.com/bwb&quot;&gt;Ben
Fox&lt;/a&gt;, founder and entrepreneur, who needed
help building his new venture Shepherd.com - “A better way to discover
amazing books”. It is a website of curated book recommendations by
authors and famous people organized around topics.&lt;/p&gt;

&lt;p&gt;As a book lover, I quickly got excited and agreed to build the website
and a content management system (CMS) behind it. The website launched
publicly today (20 April 2021), and following is the story of building
the software that powers it. Ben runs a &lt;a href=&quot;http://build.shepherd.com/&quot;&gt;diary and newsletter from the
founder’s perspective&lt;/a&gt; and if you happen
to be a book author interested in promoting a book or project, &lt;a href=&quot;https://forauthors.shepherd.com/&quot;&gt;get in
touch here&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt=&quot;Screenshot of Shepherd.com Home&quot; src=&quot;images/shepherdcom-home.png&quot; /&gt;
  &lt;figcaption&gt;
    Landing page of Shepherd.com on the day of the official launch
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;how-it-started&quot;&gt;How it started?&lt;/h2&gt;

&lt;p&gt;When Ben reached out to me, he already had a clear picture on what he
wants and had elaborate design wireframes. The initial requirements were
written down in Google Docs which allowed us to quickly come up with a
roadmap with estimated costs.&lt;/p&gt;

&lt;p&gt;The first plan was quite simple:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Build a headless CMS so that creating website content can be
started as soon as possible.&lt;/li&gt;
  &lt;li&gt;Build a &lt;a href=&quot;https://en.wikipedia.org/wiki/Minimum_viable_product&quot;&gt;“minimum viable
product”&lt;/a&gt;
(MVP) website and launch as soon as possible.&lt;/li&gt;
  &lt;li&gt;Elaborate advanced website and CMS features after launch.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;research-on-book-metadata-and-cover-image-apis&quot;&gt;Research on book metadata and cover image APIs&lt;/h2&gt;

&lt;p&gt;At the beginning it seemed like we will want to have an external data
source on book metadata and high quality book covers. Even though
various options exist, this is a surprisingly hard problem to solve
for several reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The data quality wildly varies especially for non-English
publications or niche topics.&lt;/li&gt;
  &lt;li&gt;Book cover quality varies even more, it’s hard to cater for modern
designs with high resolution images.&lt;/li&gt;
  &lt;li&gt;Quality sources can get really expensive.&lt;/li&gt;
  &lt;li&gt;Licensing and usage rights are usually restrictive and complicated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on Ben’s original shortlist, here is a brief summary of the
options we looked at:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://affiliate-program.amazon.com/&quot;&gt;Amazon&lt;/a&gt;: you need to be an
Amazon Associate Program member to use their APIs, which was not an
option at this point.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developers.google.com/books/docs/overview&quot;&gt;Google Books&lt;/a&gt;:
easy to use REST API, great search (surprise!), low quality covers
only. Free but restricted.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.ingramcontent.com/retailers/data-integration&quot;&gt;Ingram&lt;/a&gt;:
awful SOAP API (documentation is 56 pages), high quality metadata
with high(ish) resolution covers. Expensive and restrictive.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://isbndb.com/apidocs/v2&quot;&gt;ISBNdb&lt;/a&gt;: simple REST API but the
data quality is not impressive. Cheap, restrictive.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openlibrary.org/developers/api&quot;&gt;OpenLibrary&lt;/a&gt;: simple REST
API. Free and anyone can contribute data, it’s the Wikipedia of book
databases (or the Wild West of it…)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://bms.bowker.com/help/&quot;&gt;Bowker&lt;/a&gt; a not too bad XML REST API,
some books have high resolution covers. Not free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end we decided not to use any of these APIs and rely on manual
book data entry until later when it becomes clearer what the business
demands.&lt;/p&gt;

&lt;h2 id=&quot;project-management-and-collaboration&quot;&gt;Project management and collaboration&lt;/h2&gt;

&lt;p&gt;Since it is a two-men team with Ben and me for most of the time, we
kept tooling lightweight. Ben writes down the initial requirements for
each milestone or major group of features in Google Docs, where we use
comments and collaborative editing until clarified and agreed on
everything.&lt;/p&gt;

&lt;p&gt;We initially used &lt;a href=&quot;https://www.howtogeek.com/451633/how-to-create-a-checklist-in-google-docs-or-slides/&quot;&gt;checklists within Google
Docs&lt;/a&gt;
for keeping track of tasks and progress but that quite soon turned out
to be not flexible enough due to lack of useable long conversation
threads. We now keep track of everything in GitHub Issues which
perfectly does the job. I briefly also considered using &lt;a href=&quot;https://docs.github.com/en/github/managing-your-work-on-github/about-project-boards&quot;&gt;GitHub
project
boards&lt;/a&gt;
but found it a little awkward to use and probably an overkill for this
team size. We do use &lt;a href=&quot;https://guides.github.com/features/issues/#filtering&quot;&gt;issue
milestones&lt;/a&gt;
though to organize and schedule stories, bugs and other tasks into
larger buckets.&lt;/p&gt;

&lt;p&gt;Our day-to-day communication is also very lightweight. We do
conversations on GitHub Issues when there is one and exchange emails
regularly on other topics. We have not seen the need for any sort of
real-time, synchronous collaboration so far. I also find the &lt;a href=&quot;https://github.com/notifications&quot;&gt;GitHub
notification inbox&lt;/a&gt; very useful for
keeping up with conversations happening on issues.&lt;/p&gt;

&lt;h2 id=&quot;software-stack&quot;&gt;Software stack&lt;/h2&gt;

&lt;p&gt;I used the following inputs to decide on what software stack to use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ben wanted to get to a working CMS as quickly as possible.&lt;/li&gt;
  &lt;li&gt;There were no particular user interface or visual design
requirements for the CMS part and compromises were acceptable if
they speed up delivery.&lt;/li&gt;
  &lt;li&gt;The CMS was to be used by Ben a very few selected administrators.&lt;/li&gt;
  &lt;li&gt;There was no &lt;a href=&quot;https://en.wikipedia.org/wiki/User-generated_content&quot;&gt;user generated
content&lt;/a&gt; for
the immediate roadmap.&lt;/li&gt;
  &lt;li&gt;The public website consists of mostly static pages with minimal to
no interactions other than following links.&lt;/li&gt;
  &lt;li&gt;High quality books covers were essential parts of the content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given these criteria I decided for the &lt;a href=&quot;https://www.djangoproject.com/&quot;&gt;Django web
framework&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is a “batteries included” framework that allows focusing on
building features at light speed.&lt;/li&gt;
  &lt;li&gt;It is mature. Having existed for 15 years, I know I won’t run into
major issues or lack of documentation and all my possible questions
have already been answered on StackOverflow.&lt;/li&gt;
  &lt;li&gt;Django comes with an automatically generated admin interface which
works very well for basic CMS purposes.&lt;/li&gt;
  &lt;li&gt;It assumes a relational database for which it also automatically
generates and manages schema migrations allowing to offload most of
the heavy-lifting part of working with data to the database server.&lt;/li&gt;
  &lt;li&gt;Given the mostly static nature of the public facing website, scaling
Django is not a major challenge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far no regrets for choosing Django and using the built-in admin for
managing the content. There are definitely a few places where I
stretched how far one can go customizing the admin and had to go
deeper in Django’s source code than I wished for but this has been
more an exception than something common so far.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt=&quot;Screenshot of Shepherd.com admin&quot; src=&quot;images/shepherdcom-admin.png&quot; /&gt;
  &lt;figcaption&gt;
    Django Admin – perfectly does the job of a simple CMS
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;infrastructure&quot;&gt;Infrastructure&lt;/h2&gt;

&lt;p&gt;Given that the system was to be built by me in a solo fashion
(collaborating with Ben on the requirements and content building side
and a designer responsible for the look of the public website)
spending time on anything that’s not closely related to the main goal
was out of question. One such area where time and money can saved is
setting up and managing infrastructure, also known as DevOps.&lt;/p&gt;

&lt;p&gt;For this reason I decided to deploy the application to
&lt;a href=&quot;https://heroku.com&quot;&gt;Heroku&lt;/a&gt;. It is not considered to be the “hottest
thing” nowadays but it does one thing well: it runs applications with
little to no setup or maintenance. The price can go up steeply if your
capacity needs grow but that’s a good problem to have. As Heroku did
not require me to write my application in any vendor-specific way
(except for following the &lt;a href=&quot;https://12factor.net/&quot;&gt;twelve factor
principles&lt;/a&gt; which is useful on most competing
platforms) it’s easy to move elsewhere once the extra effort is
financially justified.&lt;/p&gt;

&lt;p&gt;Heroku also offers managed &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt;
which allows me to not care about database security and backups. It
even comes with a 4 days window of point-in-time rollback which
drastically improves my quality of sleep.&lt;/p&gt;

&lt;p&gt;Uploading, storing, resizing and serving images for book covers and
author portraits is an important part of the software but not
something worth building ourselves. Heroku does not offer any sort of
static hosting itself but they have partnered with
&lt;a href=&quot;https://cloudinary.com/&quot;&gt;Cloudinary&lt;/a&gt; which is a CDN and a media
management service in one. Setting up Cloudinary with Django
integration took me about 30 minutes and we have working uploads and
images resized to all responsible design needs.&lt;/p&gt;

&lt;p&gt;We use &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt; as a CDN, caching and
security layer in front of the Django application. Because Heroku
offers no static file hosting and &lt;a href=&quot;https://docs.djangoproject.com/en/3.2/howto/static-files/deployment/&quot;&gt;Django also
recommends&lt;/a&gt;
solving the problem externally, Cloudflare CDN came handy for serving
stylesheets, fonts, images and other static assets. Another selling
point for Cloudflare was their
&lt;a href=&quot;https://www.cloudflare.com/learning/cdn/what-is-caching/&quot;&gt;cache&lt;/a&gt;
which we can use for saving a huge amount of Heroku resources by
serving a cached version of infrequently changing but still
dynamically generated pages.&lt;/p&gt;

&lt;p&gt;I am positive that this infrastructure will serve the project for long
enough after the public launch so that we can identify growth patterns
and make a plan to evolving it (or not) while also building the next
round of features.&lt;/p&gt;

&lt;h2 id=&quot;continuous-delivery&quot;&gt;Continuous delivery&lt;/h2&gt;

&lt;p&gt;I strongly believe in continuous delivery from day zero of any
project. We have a decent amount of automated tests mostly of
integration and unit kind, which are ridiculously fast in
Python/Django. Most test exercise the whole stack from the HTTP
middleware all the way down to an in-memory
&lt;a href=&quot;https://sqlite.org/&quot;&gt;SQLite&lt;/a&gt; database and the entire test suite still
completes within a few seconds.&lt;/p&gt;

&lt;p&gt;Code quality checks (tests and &lt;a href=&quot;https://pylint.org/&quot;&gt;Pylint&lt;/a&gt; rules)
alongside of verifying code formatting conformity with
&lt;a href=&quot;https://github.com/psf/black&quot;&gt;Black&lt;/a&gt; is done on each commit using
&lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt;. If the checks
succeed, the code is automatically deployed to Heroku.&lt;/p&gt;

&lt;p&gt;We do have a &lt;a href=&quot;https://en.wikipedia.org/wiki/Deployment_environment#Staging&quot;&gt;staging
environment&lt;/a&gt;
as a separate Heroku app with dedicated database. Just like the
production environment, it is also automatically deployed on each
commit.  We use staging for making sure database migrations apply
without issues, to play around with content without messing up the
public website and sometimes for previewing features.&lt;/p&gt;

&lt;h2 id=&quot;alternatives-considered&quot;&gt;Alternatives considered&lt;/h2&gt;

&lt;p&gt;There are a few alternatives we have or could have considered:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;WordPress: we could have taken WordPress off the shelf at one of the
many providers but keeping the data structure and workflow heavily
customized to our needs was a must from day one which is not
something WordPress easily allows.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.django-cms.org/en/&quot;&gt;Django CMS&lt;/a&gt;: might be a feasible
option for the future but at the beginning a heavyweight CMS did not
seem justified.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt;: a reasonable alternative
but I’ve been doing more Python work than Ruby recently.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://laravel.com/&quot;&gt;Laravel&lt;/a&gt;: if I had any experience with
Laravel, it could have also been an option, but I have none.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are probably plenty of others worth considering, but these were
the ones that briefly crossed my mind at the beginning.&lt;/p&gt;

&lt;h2 id=&quot;the-future&quot;&gt;The future&lt;/h2&gt;

&lt;p&gt;There is a long list of features we will keep shipping in the near
future. Time will tell how well the infrastructure holds under the
load but I don’t expect surprises here. Once we start adding more
dynamic or user generated content that does not play nice with heavy
caching we might need to reconsider some parts and even move to a
cheaper hosting provider from Heroku once we can justify the extra
DevOps cost.&lt;/p&gt;

&lt;p&gt;If everything goes well, I will write a follow-up post here. (Maybe
also when things go bad, but that “should not happen”;)&lt;/p&gt;

&lt;p&gt;Have comments or questions? Let’s &lt;a href=&quot;https://news.ycombinator.com/item?id=26874762&quot;&gt;discuss on Hacker News!&lt;/a&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 20 Apr 2021 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/building-shepherd.html</link>
				<guid>https://salomvary.com/building-shepherd.html</guid>
			</item>
		
			<item>
				<title>A Lightweight Project Management Workflow</title>
				<description>&lt;p&gt;Every time I start working on a new project where the means of
coordinating the software development work are not established yet, I
propose a lightweight
&lt;a href=&quot;https://en.wikipedia.org/wiki/Agile_management&quot;&gt;agile&lt;/a&gt; workflow
distilled from my past experience on a wide variety of projects. This
workflow described below is my personal preference and by no means
perfect or the only feasible approach. Also nothing here was invented
by me.&lt;/p&gt;

&lt;p&gt;The workflow is best applied at &lt;strong&gt;small teams of 1-10 people&lt;/strong&gt; working
on a single software product. Ideally, the team is composed of one or
more developers with roughly similar skillsets, optionally supported
by one or more visual/UX designers.&lt;/p&gt;

&lt;p&gt;It is also useful to have &lt;strong&gt;one person appointed&lt;/strong&gt; for “running the
show”, which means this person is responsible for making sure the
agreed upon process is followed and adjustments are made whenever
necessary. This role can be taken by the same person for indefinite
time but can be periodically rotated as well since it does not require
special skills other than knowing this workflow. (In more formal Scrum
projects this role is called &lt;a href=&quot;https://en.wikipedia.org/wiki/Scrum_%28software_development%29#Scrum_master&quot;&gt;Scrum
master&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;the-board&quot;&gt;The board&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;images/simple-agile-board.png&quot; alt=&quot;A simple agile board using macOS
Stickies&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The center of this project management approach is the agile board with
three &lt;strong&gt;swimlanes&lt;/strong&gt; or columns: &lt;strong&gt;ToDo, Doing and Done&lt;/strong&gt; representing
the state of the stories contained within the lanes. The swimlanes
contain stories represented by cards or sticky notes. Stories can
represent development or design work, new features, improvements, bugs
or maintenance tasks. This is also known as &lt;a href=&quot;https://en.wikipedia.org/wiki/Kanban_board&quot;&gt;Kanban
board&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are no hard constraints on how a story should be phrased and
what it should contain but there are a few practices I recommend:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keep the &lt;strong&gt;amount of work per story small&lt;/strong&gt;. Preferably not more
than one person-day per story.&lt;/li&gt;
  &lt;li&gt;It is OK to add larger stories to ToDo but &lt;strong&gt;break it down&lt;/strong&gt; to
smaller ones before the actual work starts.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Be clear when phrasing a story&lt;/strong&gt;. Use imperative style for new
features and improvements (use the words “add”, “create”, “make”,
“improve”, etc.). For larger features it might be useful to start
with &lt;a href=&quot;https://en.wikipedia.org/wiki/User_story#Examples&quot;&gt;“user story
style”&lt;/a&gt;
optionally followed by detailed requirements.&lt;/li&gt;
  &lt;li&gt;When adding &lt;strong&gt;bugs&lt;/strong&gt; to ToDo, make sure the &lt;strong&gt;distinction between
the intended and actual behavior&lt;/strong&gt; is clear. “Expected the icon to
be cornflower blue but got red” is better than “Button is red”.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-rules&quot;&gt;The rules&lt;/h2&gt;

&lt;p&gt;This is the team’s daily workflow. Tweak according to your needs.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Planned&lt;/strong&gt; and committed-to stories are in the &lt;strong&gt;ToDo&lt;/strong&gt; column.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;next story&lt;/strong&gt; to work on (often but not always the most
important one) is at the top of ToDo, the last one at the bottom.&lt;/li&gt;
  &lt;li&gt;ToDo stories &lt;strong&gt;should not be pre-assigned&lt;/strong&gt; to a person, anyone
capable working on the next ToDo should be able to take it.&lt;/li&gt;
  &lt;li&gt;When someone starts working on a story, it should be &lt;strong&gt;moved to
Doing&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Ideally nobody should be working on more than one story.&lt;/li&gt;
  &lt;li&gt;If something gets blocked, better move it back to ToDo.&lt;/li&gt;
  &lt;li&gt;Once a story is complete, it &lt;strong&gt;moved to Done&lt;/strong&gt;. For most stories
“done” means tested and deployed to production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the team does &lt;a href=&quot;https://en.wikipedia.org/wiki/Scrum_%28software_development%29#Daily_scrum&quot;&gt;daily
&lt;strong&gt;stand-ups&lt;/strong&gt;&lt;/a&gt;
to catch up, it is a great opportunity to ensure the agile board
reflects reality. Even if stand-ups are not practiced (very small team
or solo developer, cross-timezone collaboration) it is useful to
(only) refer to stories that exist on the board when discussing
progress.&lt;/p&gt;

&lt;p&gt;Besides the daily routine, some teams work in
&lt;a href=&quot;https://en.wikipedia.org/wiki/Scrum_%28software_development%29#Sprint&quot;&gt;sprints&lt;/a&gt;
or iterations. In this lightweight workflow it is not necessary to
stick to a rigid schedule, however I find &lt;strong&gt;reflecting to past
progress and planning ahead&lt;/strong&gt; every once in a while useful.&lt;/p&gt;

&lt;h2 id=&quot;the-tool&quot;&gt;The tool&lt;/h2&gt;

&lt;p&gt;The agile board has to manifest itself in some physical or virtual
form. Choose the simplest tool possible. If you are a small team all
sitting in the same room use &lt;strong&gt;sticky notes&lt;/strong&gt; on a reasonably sized
wall surface. If a digital approach is preferred, go for a tool that
has the &lt;strong&gt;smallest amount of features&lt;/strong&gt; necessary. Bloated
all-inclusive solutions like Jira only result in tears and
fist-shaking. I recommend &lt;a href=&quot;https://trello.com/&quot;&gt;Trello&lt;/a&gt;, &lt;a href=&quot;https://www.pivotaltracker.com&quot;&gt;Pivotal
Tracker&lt;/a&gt; or when using GitHub or
GitLab for source code control they both both offer decent Kanban-like
boards. See &lt;a href=&quot;https://github.com/features/project-management&quot;&gt;GitHub’s project
management&lt;/a&gt; offering
and the one of
&lt;a href=&quot;https://about.gitlab.com/solutions/project-management/&quot;&gt;GitLab’s&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Every project needs &lt;em&gt;some&lt;/em&gt; structure but keeping the process
lightweight for small projects can reduce overhead and friction. If
the team or the organizational complexity grows later, there is always
to option to “upgrade” the workflow to something more sophisticated.&lt;/p&gt;

&lt;p&gt;What is your favorite lean workflow? Can it go even more lightweight
than described here? Should it? &lt;a href=&quot;https://twitter.com/intent/tweet?screen_name=salomvary&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;Let
me know what you think!&lt;/a&gt;&lt;/p&gt;
</description>
				<pubDate>Tue, 13 Oct 2020 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/lightweight-project-management.html</link>
				<guid>https://salomvary.com/lightweight-project-management.html</guid>
			</item>
		
			<item>
				<title>The History of SoundCleod</title>
				<description>&lt;p&gt;This post is about the history of
&lt;a href=&quot;https://soundcleod.com&quot;&gt;SoundCleod&lt;/a&gt;, the open-source desktop app for
&lt;a href=&quot;https://soundcloud.com&quot;&gt;SoundCloud&lt;/a&gt; I started building almost 8 years
ago. After 2.0.0 was &lt;a href=&quot;https://twitter.com/salomvary/status/1262738354392625154&quot;&gt;released
recently&lt;/a&gt; I
thought it might be interesting to share the background story.&lt;/p&gt;

&lt;p&gt;The story starts in 2012 with me moving to Berlin and starting a job
as an engineer at SoundCloud. When I joined in August, the team was
preparing to launch “SoundCloud Next”, a modern &lt;a href=&quot;https://en.wikipedia.org/wiki/Single-page_application&quot;&gt;single page
application&lt;/a&gt;
that was being written from scratch to replace the old website.&lt;/p&gt;

&lt;p&gt;Even though SoundCloud had an official &lt;a href=&quot;https://blog.soundcloud.com/2011/01/06/desktop/&quot;&gt;Mac
app&lt;/a&gt; back then, with
the &lt;a href=&quot;https://blog.soundcloud.com/2012/12/04/next-becomes-soundcloud/&quot;&gt;launch of
Next&lt;/a&gt;
that had all the features the company wanted to ship combined
with a great user experience, the desktop app was already far behind
and &lt;a href=&quot;http://web.archive.org/web/20150513153237/https:/twitter.com/ceterum_censeo/status/292964333569310721&quot;&gt;got not much later
retired&lt;/a&gt;.
Some folks &lt;a href=&quot;https://gizmodo.com/can-we-bring-back-soundclouds-fantastic-desktop-app-1694762114&quot;&gt;regretted this
decision&lt;/a&gt;
but most of SoundCloud users were just fine using the website,
especially Windows users for whom a desktop app never existed.&lt;/p&gt;

&lt;p&gt;Meanwhile, I had the opportunity as a SoundCloud engineer to spend
&lt;a href=&quot;https://en.wikipedia.org/wiki/20%25_Project&quot;&gt;20% of my time&lt;/a&gt; (also
known as “hacker time”) on whatever I want as long as it is related to
the business the company is doing or a technology the team uses.&lt;/p&gt;

&lt;p&gt;And that’s when it clicked in: I want to build a thin desktop wrapper
around the SoundCloud web app so that I can use my Mac keyboard’s
media keys to control playback and get other desktop-integration
benefits like not needing to hunt down which browser tab is playing
music.&lt;/p&gt;

&lt;h2 id=&quot;the-inception&quot;&gt;The Inception&lt;/h2&gt;

&lt;p&gt;After installing &lt;a href=&quot;https://en.wikipedia.org/wiki/Xcode&quot;&gt;Xcode&lt;/a&gt; and
investing a few hours into learning a bit of
&lt;a href=&quot;https://en.wikipedia.org/wiki/Cocoa_(API)&quot;&gt;Cocoa&lt;/a&gt; and
&lt;a href=&quot;https://en.wikipedia.org/wiki/Objective-C&quot;&gt;Objective-C&lt;/a&gt;, a language I
never used before, I made the &lt;a href=&quot;https://github.com/salomvary/soundcleod/commit/63c11038cace325d07d25191b01e744ba09a5b19&quot;&gt;first commit on Dec 11
2012&lt;/a&gt;.
Few days later I had a working Mac application.&lt;/p&gt;

&lt;p&gt;Before releasing the it to an audience however, I needed a name.
Because the project was not meant to be an official app, I wanted a
name that’s kind of funny but still recognizable and related to
SoundCloud. As a reference to counterfeit brands like “abibas” I
&lt;a href=&quot;https://github.com/salomvary/soundcleod/commit/99a5dfe15e3bbf32f72dfecc2cfa7e7e6eb1f618&quot;&gt;renamed the app to
SoundCleod&lt;/a&gt;
and presented it at the next regular internal &lt;a href=&quot;https://manifesto.co.uk/scrum-practice-sprint-demo/&quot;&gt;demo
session&lt;/a&gt; at
SoundCloud.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/fake-adidas-logos.jpg&quot; alt=&quot;fake Adidas logos&quot; /&gt;
Image source: &lt;a href=&quot;https://www.reddit.com/r/crappyoffbrands/comments/70hks9/all_of_these_off_brand_adidas_logos/&quot;&gt;reddit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Colleagues #celebrated my little success, some started using it, some
others &lt;a href=&quot;https://github.com/salomvary/soundcleod/commit/eee431ef8dc5d407212a3538a0ad8522f2e5481d&quot;&gt;made small
contributions&lt;/a&gt;,
another one offered help on &lt;a href=&quot;https://soundcloud.com/senart/soundcleod&quot;&gt;how to pronounce
“SoundCleod”&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Version 0.1 was &lt;a href=&quot;https://twitter.com/salomvary/status/296660937207840769&quot;&gt;announced to the public in January
2013&lt;/a&gt; and the
source code was published to GitHub under &lt;a href=&quot;https://en.wikipedia.org/wiki/MIT_License&quot;&gt;MIT
license&lt;/a&gt; and with that the
journey had started.&lt;/p&gt;

&lt;h2 id=&quot;early-years&quot;&gt;Early Years&lt;/h2&gt;

&lt;p&gt;The public release created a moderate amount of buzz, I had a few
people reporting bugs, saying thanks and even had the first
contributors send pull requests in early 2013. I kept using my 20%
time to add features, fix bugs and keep the small app in good shape.
It was a surprising amount of work considering that the app had almost
no business logic and a small niche value added on top of
SoundCloud’s desktop web experience.&lt;/p&gt;

&lt;p&gt;Because any decent desktop software needs easy and frequent updates,
version 0.12 with automatic updates &lt;a href=&quot;https://twitter.com/salomvary/status/398837638393651200&quot;&gt;was released in November
2013&lt;/a&gt;. By
that time a continuous delivery pipeline &lt;a href=&quot;https://travis-ci.org/salomvary/soundcleod&quot;&gt;was already in
place&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Knowing that SoundCloud had no intention to maintain an official
desktop app I kept serving my little niche with a lot of enthusiasm
but my excitement about the native Mac app development ecosystem
started to fade.  I quite soon concluded that I will not want to go
in-depth with native Mac or iOS development, which back then mostly
meant writing Objective-C (Swift was not yet around).&lt;/p&gt;

&lt;h2 id=&quot;going-cross-platform-with-electron&quot;&gt;Going Cross-platform with Electron&lt;/h2&gt;

&lt;p&gt;Even though SoundCleod’s codebase was not much more than a
&lt;a href=&quot;https://developer.apple.com/documentation/webkit/webview&quot;&gt;WebView&lt;/a&gt;
(Apple’s embeddable WebKit browser component) with a few hundred lines
of glue code, I had a few pain points:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Using a weird language I was not familiar with – in a not
particularly developer friendly ecosystem.&lt;/li&gt;
  &lt;li&gt;Not being cross-platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coincidentally, GitHub &lt;a href=&quot;https://www.electronjs.org/blog/electron-1-0&quot;&gt;released Electron
1.0&lt;/a&gt; in 2016, a
framework for building cross-platform desktop applications using web
technologies like JavaScript, CSS and Node.js.&lt;/p&gt;

&lt;p&gt;Happy to ditch native Mac development I rewrote SoundCleod using
Electron from scratch and &lt;a href=&quot;https://twitter.com/salomvary/status/781799745421475840&quot;&gt;released
1.0&lt;/a&gt; in
September 2016 and soon after that a version that was available for
Windows as well.&lt;/p&gt;

&lt;p&gt;Switching to Electron also meant using its built-in &lt;a href=&quot;https://www.electronjs.org/docs/api/auto-updater&quot;&gt;auto
updater&lt;/a&gt; which
required not only setting up and maintaining my own update server but
also &lt;a href=&quot;https://en.wikipedia.org/wiki/Code_signing&quot;&gt;code signing&lt;/a&gt; the
application.&lt;/p&gt;

&lt;p&gt;Code-signing – as I learned pretty soon – is a complicated matter
especially for newcomers not familiar with native application
development. Not wanting to pay Apple $100 a year for giving me code
signing certificates I decided to use &lt;a href=&quot;https://en.wikipedia.org/wiki/Self-signed_certificate&quot;&gt;self-signed
certificates&lt;/a&gt;
which are indeed free but the cumulative hassle of dealing with them
cost me several times $100, which I only realized much later…&lt;/p&gt;

&lt;p&gt;During these years a handful of people said “thanks you” or praised
the app publicly, which felt good and encouraging. As many open-source
maintainers say it is a lot of work for very little reward and that’s
exactly my experience.&lt;/p&gt;

&lt;p&gt;The first and until today the only donation the project received in
2018 was a big moment: &lt;a href=&quot;https://twitter.com/salomvary/status/954064285084286976&quot;&gt;someone sent me 10
Euros&lt;/a&gt;!&lt;/p&gt;

&lt;h2 id=&quot;recent-times&quot;&gt;Recent Times&lt;/h2&gt;

&lt;p&gt;Fast forward to 2020 &lt;a href=&quot;/things-learned-at-soundcloud.html&quot;&gt;I no longer work for SoundCloud&lt;/a&gt; and have been freelancing
for a couple of years. Since then I no longer have the free SoundCloud
Go+ subscription meaning I rarely use SoundCloud at all. As a
freelancer I also no longer have the privilege of spending 20% of my
working hours on an open-source project. Every hour I spend on
SoundCleod has an &lt;a href=&quot;https://en.wikipedia.org/wiki/Opportunity_cost&quot;&gt;opportunity
cost&lt;/a&gt; – the cost of
not working for a client or developing my business in other ways.&lt;/p&gt;

&lt;p&gt;I still have bursts of interest in the project. Most recently, thanks
to the &lt;a href=&quot;https://en.wikipedia.org/wiki/Market_trend#Bear_market&quot;&gt;“bear
market”&lt;/a&gt;
caused by the global COVID-19 pandemic, some of my freelance clients
bailed or scaled down ongoing projects giving me plenty of free time.&lt;/p&gt;

&lt;p&gt;Besides doing some long overdue maintenance and small bugfixes, I
decided to enter Apple’s &lt;a href=&quot;https://en.wikipedia.org/wiki/Closed_platform&quot;&gt;walled
garden&lt;/a&gt; and try
releasing SoundCleod on the Mac App Store. I knew that Apple
notoriously rejects apps for various reasons therefore I did not have
high expectations of success.  Assuming the attempt is a small effort
on my side I decided to go for it as it could have increased my reach
and would have allowed to easily charge some small amount for the
convenience of the App Store downloads.&lt;/p&gt;

&lt;p&gt;The effort turned out to be bigger than I expected. Pushing an
Electron app to the App Store is a major hassle but I succeeded
submitting it for review, which in turn got rejected within a few
hours. Reasons? They did not like the name (no surprise!) and the
color of the icon, so I renamed the app to “Cleod for SoundCloud” and
changed the logo to green ;)&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;images/cleod-for-soundcloud-screenshot.jpg&quot; alt=&quot;Screenshot of Cleod for SoundCloud&quot; /&gt;
  &lt;figcaption&gt;The App Store compliant name and icon&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The branding changes got accepted, however what I did not manage to
convince the Review Board about was the functionality. The rejection
reason was something like “the app is not more than what you have on
your website (sic!)”. I even tried to reason with Slack’s native app
as a precedent, which is almost pixel-perfectly the same in Safari.
Anyway, appealed, got rejected again and then gave up. I might publish
the unedited conversation one day, it’s quite eye-opening…&lt;/p&gt;

&lt;h2 id=&quot;the-future-of-soundcleod&quot;&gt;The Future of SoundCleod&lt;/h2&gt;

&lt;p&gt;With the recent addition of dark mode I consider SoundCleod to be
feature complete. Small features might be added if someone contributes
code but I have no plans to add more or anything that might increase
the maintenance burden.&lt;/p&gt;

&lt;p&gt;Talking of maintenance, although I secretly hoped Electron will save
me from this, something still breaks with every major macOS release
(to be fair it’s often Apple to blame). To make is worse, things can
also break with major releases of &lt;em&gt;Electron&lt;/em&gt; making updates a risky
business.&lt;/p&gt;

&lt;p&gt;With all these in mind I am still planning to keep the project alive
for the foreseeable future and if at some point I become too busy to
be bothered, someone can fork the project and continue where I left.
That’s the beauty of open-source!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;images/soundcleod-screenshot-dark.jpg&quot; alt=&quot;Screenshot of SoundCleod in dark mode&quot; /&gt;&lt;/p&gt;
</description>
				<pubDate>Mon, 08 Jun 2020 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/history-of-soundcleod.html</link>
				<guid>https://salomvary.com/history-of-soundcleod.html</guid>
			</item>
		
			<item>
				<title>Moving a Website to Netlify from GitHub Pages</title>
				<description>&lt;h2 id=&quot;the-need&quot;&gt;The Need&lt;/h2&gt;

&lt;p&gt;I recently developed a new interest in how my blog and professional
portfolio (this very website you are looking at) is performing. The
goal was to see some basic numbers like total visitor numbers and a
breakdown by pages, maybe referrers as well. Because I removed Google
Analytics a while ago to &lt;a href=&quot;https://en.wikipedia.org/wiki/Google_Analytics#Privacy&quot;&gt;protect the privacy of my
visitors&lt;/a&gt; (and
to not feed the giant with even more tracking data) and also do not
think that any client-side tracking can be accurate for a
small-traffic website like mine (because of blockers) I looked into
server-side traffic analytics options.&lt;/p&gt;

&lt;p&gt;The website was hosted on &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt;
with a &lt;a href=&quot;https://www.cloudflare.com&quot;&gt;CloudFlare&lt;/a&gt; CDN in front of it.
GitHub does not offer any sort of analytics, and even if it did, it
would not be accurate as most of the requests are served by
CloudFlare. CloudFlare does have analytics but it &lt;a href=&quot;/images/cloudflare-analytics-screenshot.png&quot;&gt;lacks page-level
breakdown&lt;/a&gt;. I even talked
to a CloudFlare sales representative and they confirmed that they only
offer more traffic detail in the enterprise package, which costs
several thousand dollars a month. Not my budget.&lt;/p&gt;

&lt;h2 id=&quot;enter-netlify&quot;&gt;Enter Netlify&lt;/h2&gt;

&lt;p&gt;A &lt;a href=&quot;https://twitter.com/salomvary/status/1260502453608419328&quot;&gt;quick
ask-around&lt;/a&gt;
resulted in several recommendations in the direction of Netlify. The
service has been on my radar for a while but had no excuse to try it
out until today. &lt;a href=&quot;https://www.netlify.com/products/analytics/&quot;&gt;Netlify
Analytics&lt;/a&gt; for
$9/month/site looks like what I was searching for!&lt;/p&gt;

&lt;h3 id=&quot;step-one-add-site-to-netlify&quot;&gt;Step One: Add Site to Netlify&lt;/h3&gt;

&lt;p&gt;Because the source code for my website is in a GitHub repository and
uses &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; (the static website generator
behind GitHub pages) and this setup is supported by Netlify out of the
box, the first step was very easy. I signed up with my GitHub account
to Netlify and followed the wizard to deploy a website from a
repository.  After allowing Netlify to access my repos and selecting
the one named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;salomvary.github.com&lt;/code&gt; I had the website up and running
under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://some-random-name-6a18c6.netlify.app&lt;/code&gt; within a few
minutes. Looks like we are done!&lt;/p&gt;

&lt;p&gt;If you do not use your own custom domain, edit the site settings on
Netlify and change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some-random-name-6a18c6&lt;/code&gt; to something you like and
you can stop reading now.&lt;/p&gt;

&lt;h3 id=&quot;step-two-set-up-your-own-domain&quot;&gt;Step Two: Set up Your Own Domain&lt;/h3&gt;

&lt;p&gt;If you are using your own domain on GitHub pages like me, the domain
needs to be migrated too. The Domain Management area on Netlify nicely
explains the options. Unless you want to switch to their own DNS
(which is a feasible option) you will have to edit the records at your
DNS provider, for which Netlify also offers guidance. In my case the
DNS provider is CloudFlare. All I had to do was changing the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;salomvary.com&lt;/code&gt; &lt;strong&gt;CNAME&lt;/strong&gt; record to point to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;some-random-name-6a18c6.netlify.app&lt;/code&gt;. This is slightly different from
what Netlify recommended but &lt;a href=&quot;https://support.cloudflare.com/hc/en-us/articles/200169056-Understand-and-configure-CNAME-Flattening&quot;&gt;CloudFlare does CNAME
flattening&lt;/a&gt;
which is kind of equivalent of adding an &lt;strong&gt;A&lt;/strong&gt; record.&lt;/p&gt;

&lt;p&gt;Almost immediately after changing the DNS records the website was
already served over HTTP by Netlify, and the automatic setup of the
free &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; HTTPS certificates did
not take more than 10 minutes. One can verify the successful
switch-over by looking for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server: Netlify&lt;/code&gt; HTTP response header
using the browser’s developer tools or the output of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl -vL
http://mydomain.com&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;It is important to note that during these 10 minutes the website was
not available due to browsers showing certificate errors this is
expected and was acceptable in my case.&lt;/p&gt;

&lt;h3 id=&quot;step-three-fixing-github-project-pages&quot;&gt;Step Three: Fixing GitHub Project Pages&lt;/h3&gt;

&lt;p&gt;This part only applies if you are switching from a &lt;a href=&quot;https://help.github.com/en/github/working-with-github-pages/about-github-pages#types-of-github-pages-sites&quot;&gt;GitHub user or
organization
site&lt;/a&gt;.
You can recognize this from the repo being named
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myusername.github.io&lt;/code&gt; (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.com&lt;/code&gt; for old sites) or
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myorgname.github.io&lt;/code&gt;. In this case a less known magic is in action
which just got broken by moving to Netlify.&lt;/p&gt;

&lt;p&gt;The magic is the following. If you do not use your own domain on
GitHub Pages, your user/org site is at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://myusername.github.io&lt;/code&gt;
and your &lt;em&gt;repo&lt;/em&gt; pages are at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://myusername.github.io/myproject&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, if you do use your own domain with GitHub, all project pages
will be automagically served under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://mydomain.com/myproject&lt;/code&gt;
and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github.io&lt;/code&gt; variants will redirect to your domain.&lt;/p&gt;

&lt;p&gt;But since switching to Netlify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://mydomain.com/myproject&lt;/code&gt; no
longer serves the project page and neither does it redirect to
anywhere.  Netlify simply serves a “404 not found” error page. Even
worse, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://myusername.github.io/myproject&lt;/code&gt; still redirects to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://mydomain.com/myproject&lt;/code&gt; resulting in the same error. The only
exception are repos that are neither user nor organization page repos
&lt;strong&gt;and&lt;/strong&gt; are configured to use their own custom domain. These will keep
working as before.&lt;/p&gt;

&lt;p&gt;As long as you are fine with the project pages being served from
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://myusername.github.io/myproject&lt;/code&gt; with a redirect from
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://mydomain.com/myproject&lt;/code&gt; the fix is not very complicated:
Netlify allows &lt;a href=&quot;https://docs.netlify.com/routing/redirects/&quot;&gt;configuring redirects using a file named
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_redirects&lt;/code&gt;&lt;/a&gt; placed in
the root of the website repo. It should look like the example below,
where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myusername&lt;/code&gt; is &lt;em&gt;your&lt;/em&gt; GitHub username and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myproject&lt;/code&gt; is the
GitHub repo name you want the redirect for:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/myproject/* https://myusername.github.io/myproject/:splat
/otherproject/* https://myusername.github.io/otherproject/:splat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When using Jekyll, do not forget to add this to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;_redirects&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you only have a handful of repos to redirect to, the redirects can
be created by hand. If you have many repos, are lazy, or like &lt;a href=&quot;https://www.urbandictionary.com/define.php?term=yak%20shaving&quot;&gt;shaving
yaks&lt;/a&gt;,
you can automate creating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_redirects&lt;/code&gt;. All you need is
&lt;a href=&quot;https://stedolan.github.io/jq/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt;&lt;/a&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; installed, plus
&lt;a href=&quot;https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line&quot;&gt;obtaining a GitHub personal access
token&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run this command from a terminal window in the root of the website
project:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Authorization: token  &amp;lt;your GitHub token&amp;gt;&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;https://api.github.com/user/repos?affiliation=owner&amp;amp;per_page=100&amp;amp;page=1&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.[]
    | select(.has_pages)
    | &quot;/&quot; + .name + &quot;/* https://yourusername.github.io/&quot; + .name + &quot;/:splat&quot;&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; _redirects
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Do not forget to change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yourusername&lt;/code&gt; to your GitHub username! If you
have more than 100 repos on GitHub (check the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Link&lt;/code&gt; response header
for the presence of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rel=next&lt;/code&gt;) repeat this by changing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page=1&lt;/code&gt; to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page=2&lt;/code&gt; and so on.&lt;/p&gt;

&lt;p&gt;There is one more important thing before celebrating success: you need
to tell GitHub to no longer use your custom domain, otherwise
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myusername.github.io/myproject&lt;/code&gt; will keep redirecting to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mydomain.com/myproject&lt;/code&gt; which we just configured to redirect to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myusername.github.io/myproject&lt;/code&gt; creating an infinite redirect loop.&lt;/p&gt;

&lt;p&gt;This can be fixed by deleting the file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CNAME&lt;/code&gt; from the root of
the Git repo and pushing the changes to GitHub.&lt;/p&gt;

&lt;p&gt;At this point, your website should be serving visitors as it was
before.&lt;/p&gt;

&lt;h3 id=&quot;step-four-cleaning-up-seo&quot;&gt;Step Four: Cleaning Up SEO&lt;/h3&gt;

&lt;p&gt;There is one remaining problem which might or might not bother you.
With this setup, the website content can be available under four
different URLs:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The source
 at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://github.com/username/username.github.com/blob/master/_posts/post.markdown&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;The GitHub Pages site at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://username.github.io&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;The Netlify
website at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://some-random-name-6a18c6.netlify.app&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;And &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://yourdomain.com&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is OK, as long as you do not care about &lt;a href=&quot;https://en.wikipedia.org/wiki/Search_engine_optimization&quot;&gt;search engine
optimization&lt;/a&gt;
(SEO), but Googlebot and other crawlers will treat these as &lt;a href=&quot;https://support.google.com/webmasters/answer/66359?hl=en&quot;&gt;duplicate
content&lt;/a&gt;
with appropriate punishments.&lt;/p&gt;

&lt;p&gt;Hiding the website source in the GitHub repo is &lt;a href=&quot;https://stackoverflow.com/a/15987482/759162&quot;&gt;allegedly only
possible by not using the master
branch&lt;/a&gt; unless the repo
is made private, which - now with free private GitHub repos - is also
an option. This is a solution for #1.&lt;/p&gt;

&lt;p&gt;If the repo is not a user or organization repo, GitHub allows turning
Pages off from the repo settings page. This can solve #2.&lt;/p&gt;

&lt;p&gt;For a user or organization page, there is no option to turn Pages off
(which kind of makes sense). Setting the repo to private also &lt;a href=&quot;https://help.github.com/en/github/working-with-github-pages/about-github-pages#publishing-sources-for-github-pages-sites&quot;&gt;does
not disable the public
website&lt;/a&gt;.
The only solution is renaming the repo from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;username.github.io&lt;/code&gt; to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mydomain.com&lt;/code&gt; or whatever you like (from now on, the name does not
matter as we do not rely on GitHub Pages’ magic).&lt;/p&gt;

&lt;p&gt;Disabling the Netlify “default subdomain” (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my-thing.netlify.app&lt;/code&gt;)
does not seem to be possible, which means web crawlers might also
discover it, resulting in duplicate content. Unless there is some
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;robots.txt&lt;/code&gt; trickery I am not aware of, or domain-specific redirect
rules on Netlify are possible (to be figured out) the only solution is
adding &lt;a href=&quot;https://support.google.com/webmasters/answer/139066?hl=en&quot;&gt;canonical
URLs&lt;/a&gt; to
all pages of your website.  This, with the help of the &lt;a href=&quot;https://github.com/jekyll/jekyll-seo-tag&quot;&gt;Jekyll SEO Tag
plugin&lt;/a&gt; is not very
complicated (and is a good idea anyway). Problem #3 solved.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;As we saw the trivial task of moving a static website from one hosting
provider can turn into a deep &lt;a href=&quot;https://www.merriam-webster.com/dictionary/rabbit%20hole&quot;&gt;rabbit
hole&lt;/a&gt;. The
good thing is, it did not take as much time as it may seem. In fact
writing this blog post has taken much more time than the website
migration itself.  The other good thing: I did not even have to pay
for Netlify, because it’s free, including custom domains.&lt;/p&gt;

&lt;p&gt;Well, actually, I did end up paying for Netlify. If you still
remember, the original motivation was to gather page-level analytics
on the website traffic, so I pulled my credit card and forked out $9
for Netlify Analytics.&lt;/p&gt;

&lt;p&gt;Which, to my slight disappointment, is only a tiny bit better than
analytics at CloudFlare, offering nothing more than the top x (5?)
most visited URLs in the pages section:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/netlify-top-pages-screenshot.png&quot; alt=&quot;Screenshot of the Top pages in Netlify Analytics&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(For the curious &lt;a href=&quot;/images/netlify-analytics-screenshot.png&quot;&gt;here is a screenshot of the entire Netlify Analytics
page&lt;/a&gt; few hours after
turning it on.)&lt;/p&gt;

&lt;p&gt;It was an interesting rabbit hole anyway :)&lt;/p&gt;
</description>
				<pubDate>Wed, 13 May 2020 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/moving-from-github-pages-to-netlify.html</link>
				<guid>https://salomvary.com/moving-from-github-pages-to-netlify.html</guid>
			</item>
		
			<item>
				<title>Visualizing Data with React and SVG</title>
				<description>&lt;style&gt;
figure {
  text-align: center;
}

svg {
  background-color: #122b3b;
  font-family: sans-serif;
  font-weight: lighter;
}
&lt;/style&gt;

&lt;p&gt;Although I am a great fan of &lt;a href=&quot;https://d3js.org/&quot;&gt;D3.js&lt;/a&gt; when it comes
to data visualization and &lt;a href=&quot;/soundcloud-d3-stats-presentation.html&quot;&gt;have used it&lt;/a&gt; with great success in
the past, it might not be the best tool for the job in all cases. In
applications where the advanced visualization features of D3 are not
needed and React is in use so that efficient mapping of data to DOM is
taken care of, the extra learning curve of D3 might not be justified.&lt;/p&gt;

&lt;p&gt;This post is a step-by-step introduction to building a simple line
chart that visualizes the price of a stock over a couple of days. It
is assumed that the reader has experience with
&lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; and modern JavaScript, basic knowledge
of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG&quot;&gt;SVG&lt;/a&gt; is useful
too.  The impatient can find a link to the entire working solution in
a Git repo at the end.&lt;/p&gt;

&lt;p&gt;To get started, we need some data:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2020-01-06&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;299.799988&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2020-01-07&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;298.390015&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;2020-01-08&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;303.190002&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The fundamental first step to visualizing data is establishing a
&lt;em&gt;scale&lt;/em&gt; that allows mapping the abstract data (time and price in our
example) into a visual representation.&lt;/p&gt;

&lt;p&gt;For our use case SVG elements offer a convenient way to render our data
onto a two-dimensional canvas. The coordinate system of an SVG image
looks like this:&lt;/p&gt;

&lt;figure&gt;

  &lt;img alt=&quot;SVG Grid&quot; src=&quot;images/svg-grid.svg&quot; /&gt;

  &lt;figcaption&gt;
    &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:SVG_example_markup_grid.svg&quot;&gt;
      Image source: Wikipedia
    &lt;/a&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To get started we will show individual data points - stock prices on a
given day - as small circles using a simple React component:&lt;/p&gt;

&lt;div class=&quot;language-jsx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LineChart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Calculate the extent of the data&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// (not the most efficient way but easily readable)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minDay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;maxDay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;maxPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Create the horizontal and vertical scale functions&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
     &lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minDay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;maxDay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minDay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;maxPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;circle&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The most interesting part are the scale functions: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x(someDate)&lt;/code&gt;
returns the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; coordinate in pixels for a date and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y(somePrice)&lt;/code&gt;
returns the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; coordinate in pixels for a price. This means &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;circle
cx={x(date)} cy={y(price)}/&amp;gt;&lt;/code&gt; will be rendered to the right place in
our two-dimensional space. The resulting SVG looks like this:&lt;/p&gt;

&lt;figure&gt;
  &lt;svg width=&quot;600&quot; height=&quot;200&quot; fill=&quot;greenyellow&quot; stroke=&quot;greenyellow&quot;&gt;&lt;circle cx=&quot;0&quot; cy=&quot;186.1359559756061&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;54.54545454545455&quot; cy=&quot;200&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;109.0909090909091&quot; cy=&quot;152.8024784272328&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;163.63636363636363&quot; cy=&quot;89.47893598405834&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;218.1818181818182&quot; cy=&quot;82.59612243778199&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;381.8181818181818&quot; cy=&quot;17.404329873024437&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;436.3636363636364&quot; cy=&quot;59.48888092210018&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;490.90909090909093&quot; cy=&quot;72.66486188099557&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;545.4545454545454&quot; cy=&quot;34.316830740773156&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;600&quot; cy=&quot;0&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;/svg&gt;
&lt;/figure&gt;

&lt;p&gt;To make the chart less empty and allow reading price change trends
easier we will connect the circles with a line, turning it into a line
chart:&lt;/p&gt;

&lt;div class=&quot;language-jsx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;path&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;strokeWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;none&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;path&amp;gt;&lt;/code&gt; that follows a certain course we need to pass in
the path coordinates in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; attribute. It is a string of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#Path_commands&quot;&gt;path
comands&lt;/a&gt;
and can be generated using a small helper function:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Returns a string like M1,2L3,4L5,6&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Our chart should look much nicer now:&lt;/p&gt;

&lt;figure&gt;
  &lt;svg width=&quot;600&quot; height=&quot;200&quot; fill=&quot;greenyellow&quot; stroke=&quot;greenyellow&quot;&gt;&lt;circle cx=&quot;0&quot; cy=&quot;186.1359559756061&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;54.54545454545455&quot; cy=&quot;200&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;109.0909090909091&quot; cy=&quot;152.8024784272328&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;163.63636363636363&quot; cy=&quot;89.47893598405834&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;218.1818181818182&quot; cy=&quot;82.59612243778199&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;381.8181818181818&quot; cy=&quot;17.404329873024437&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;436.3636363636364&quot; cy=&quot;59.48888092210018&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;490.90909090909093&quot; cy=&quot;72.66486188099557&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;545.4545454545454&quot; cy=&quot;34.316830740773156&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;600&quot; cy=&quot;0&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;path stroke-width=&quot;2&quot; fill=&quot;none&quot; d=&quot;M0,186.1359559756061L54.54545454545455,200L109.0909090909091,152.8024784272328L163.63636363636363,89.47893598405834L218.1818181818182,82.59612243778199L381.8181818181818,17.404329873024437L436.3636363636364,59.48888092210018L490.90909090909093,72.66486188099557L545.4545454545454,34.316830740773156L600,0&quot;&gt;&lt;/path&gt;&lt;/svg&gt;
&lt;/figure&gt;

&lt;p&gt;Because a chart without labels is not a chart (can still be a piece of
art but we are doing data visualization here), we will have to make
further changes.&lt;/p&gt;

&lt;p&gt;But first, in order to prevent our main render function from growing
too large, we break it up into a couple of tiny components:&lt;/p&gt;

&lt;div class=&quot;language-jsx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LineChart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Circles&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Line&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Circles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;circle&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;cy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;path&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;strokeWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;none&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make room for the labels along the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; axes we need to
slightly shrink the actual line chart and then move it a bit towards
the bottom and right. We do this using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translate(x, y)&lt;/code&gt;
transformation which moves it’s target by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; horizontally and by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt;
vertically.&lt;/p&gt;

&lt;div class=&quot;language-jsx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;LineChart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outerWidth&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outerHeight&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Accommodate for the room around the coordinate system&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outerWidth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outerHeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Create the horizontal and vertical scale functions&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minDay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;maxDay&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minDay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;maxPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;outerWidth&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;outerHeight&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* Move down and right to leave room for labels */&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Circles&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Line&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Create SVG translate functions in a readable way&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;translate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`translate(&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And now we can add labels along the axes positioned using
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;translate()&lt;/code&gt; to the left and bottom side. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; axis is easier to
implement:&lt;/p&gt;

&lt;div class=&quot;language-jsx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;XAxis&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outerHeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;XAxis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;textAnchor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;middle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;formatDate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Return short date like &quot;Jan 10&quot;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formatDate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLocaleDateString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;en-US&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;numeric&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;short&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;timeZone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;UTC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because price along the y axis is a continuous value we need to come
up with our own discrete values (“ticks”) for placing the labels.&lt;/p&gt;

&lt;div class=&quot;language-jsx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Calculate ticks for y axis labels&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ticksCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;yTicks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Start from the lowest price&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// End at the highest price&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;maxPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Divide the range into equal parts&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;maxPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ticksCount&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;YAxis&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;yTicks&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;padding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;YAxis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;({&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;textAnchor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;middle&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Generate numeric values within a range&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The final result will look like this:&lt;/p&gt;

&lt;figure&gt;
&lt;svg width=&quot;600&quot; height=&quot;200&quot; fill=&quot;greenyellow&quot; stroke=&quot;greenyellow&quot; font-size=&quot;10px&quot;&gt;&lt;g transform=&quot;translate(40, 40)&quot;&gt;&lt;g&gt;&lt;circle cx=&quot;0&quot; cy=&quot;111.68157358536365&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;47.27272727272727&quot; cy=&quot;120&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;94.54545454545455&quot; cy=&quot;91.68148705633969&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;141.8181818181818&quot; cy=&quot;53.687361590435&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;189.0909090909091&quot; cy=&quot;49.557673462669186&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;330.9090909090909&quot; cy=&quot;10.442597923814674&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;378.1818181818182&quot; cy=&quot;35.693328553260116&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;425.4545454545455&quot; cy=&quot;43.598917128597336&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;472.7272727272727&quot; cy=&quot;20.590098444463905&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;circle cx=&quot;520&quot; cy=&quot;0&quot; r=&quot;4&quot;&gt;&lt;/circle&gt;&lt;/g&gt;&lt;g&gt;&lt;path stroke-width=&quot;2&quot; fill=&quot;none&quot; d=&quot;M0,111.68157358536365L47.27272727272727,120L94.54545454545455,91.68148705633969L141.8181818181818,53.687361590435L189.0909090909091,49.557673462669186L330.9090909090909,10.442597923814674L378.1818181818182,35.693328553260116L425.4545454545455,43.598917128597336L472.7272727272727,20.590098444463905L520,0&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform=&quot;translate(40, 180)&quot;&gt;&lt;text x=&quot;0&quot; text-anchor=&quot;middle&quot;&gt;Jan 6&lt;/text&gt;&lt;text x=&quot;47.27272727272727&quot; text-anchor=&quot;middle&quot;&gt;Jan 7&lt;/text&gt;&lt;text x=&quot;94.54545454545455&quot; text-anchor=&quot;middle&quot;&gt;Jan 8&lt;/text&gt;&lt;text x=&quot;141.8181818181818&quot; text-anchor=&quot;middle&quot;&gt;Jan 9&lt;/text&gt;&lt;text x=&quot;189.0909090909091&quot; text-anchor=&quot;middle&quot;&gt;Jan 10&lt;/text&gt;&lt;text x=&quot;330.9090909090909&quot; text-anchor=&quot;middle&quot;&gt;Jan 13&lt;/text&gt;&lt;text x=&quot;378.1818181818182&quot; text-anchor=&quot;middle&quot;&gt;Jan 14&lt;/text&gt;&lt;text x=&quot;425.4545454545455&quot; text-anchor=&quot;middle&quot;&gt;Jan 15&lt;/text&gt;&lt;text x=&quot;472.7272727272727&quot; text-anchor=&quot;middle&quot;&gt;Jan 16&lt;/text&gt;&lt;text x=&quot;520&quot; text-anchor=&quot;middle&quot;&gt;Jan 17&lt;/text&gt;&lt;/g&gt;&lt;g transform=&quot;translate(20, 40)&quot;&gt;&lt;text y=&quot;122.30097390382971&quot; text-anchor=&quot;middle&quot;&gt;298&lt;/text&gt;&lt;text y=&quot;98.70214920396249&quot; text-anchor=&quot;middle&quot;&gt;302&lt;/text&gt;&lt;text y=&quot;75.10332450409527&quot; text-anchor=&quot;middle&quot;&gt;306&lt;/text&gt;&lt;text y=&quot;51.50449980422806&quot; text-anchor=&quot;middle&quot;&gt;310&lt;/text&gt;&lt;text y=&quot;27.905675104360853&quot; text-anchor=&quot;middle&quot;&gt;314&lt;/text&gt;&lt;text y=&quot;4.30685040449363&quot; text-anchor=&quot;middle&quot;&gt;318&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;/figure&gt;

&lt;p&gt;There are may ways how this basic chart can be improved and I have not
mentioned testing or usability of the components at all (might do it
in a follow-up post). I hope that the basic concepts introduced are a
good start for your next data visualization in React!&lt;/p&gt;

&lt;p&gt;Once things get more complicated however, I strongly recommend looking
at D3. It has amazing documentation, lots of interactive of examples
and supports many advanced techniques and optimizations that are hard
to get right when writing them from scratch. In fact, the whole
approach taken here was inspired with my previous experience with D3.&lt;/p&gt;

&lt;p&gt;D3 is also available as small modules which means even if you use
React for managing the DOM, you can use part of D3 under the hood. For
example the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; scale functions are &lt;a href=&quot;https://github.com/d3/d3-scale/blob/v2.2.2/README.md#scaleLinear&quot;&gt;available in the
standalone
d3-scale&lt;/a&gt;
module, or the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;path&amp;gt;&lt;/code&gt; commands generator in
&lt;a href=&quot;https://github.com/d3/d3-shape/blob/v1.3.7/README.md#lines&quot;&gt;d3-shape&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can see &lt;a href=&quot;https://salomvary.com/react-charts-demo/&quot;&gt;the chart component from above running
here&lt;/a&gt; and the full source
can be found in a &lt;a href=&quot;https://github.com/salomvary/react-charts-demo/blob/master/src/LineChart.js&quot;&gt;Git
repo&lt;/a&gt;.&lt;/p&gt;
</description>
				<pubDate>Tue, 17 Mar 2020 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/react-charts.html</link>
				<guid>https://salomvary.com/react-charts.html</guid>
			</item>
		
			<item>
				<title>How to do Remote Meetings Right</title>
				<description>&lt;p&gt;As the &lt;a href=&quot;https://en.wikipedia.org/wiki/Coronavirus_disease_2019&quot;&gt;COVID-19 pandemic&lt;/a&gt; makes more-and-more companies decide
to go partially or fully remote, it is a very appropriate time to
share experience and advice on what can help teams remain productive
in this new situation.&lt;/p&gt;

&lt;p&gt;I worked remotely for a wide variety of clients in the last couple of
years and learned a lot about what the key components of a remote
collaboration are. &lt;strong&gt;One key ingredient to efficient remote teams is
doing meetings right.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;gitlab-manifesto&quot;&gt;Some say&lt;/a&gt; remote teams should only communicate
asynchronously (via chat, email and documents) but for most,
especially for those who only recently switched, setting up conference
calls with multiple participants regularly will be inevitable. And
it’s harder to do right than most people think.&lt;/p&gt;

&lt;div style=&quot;margin-bottom: 30px&quot;&gt;
&lt;blockquote class=&quot;twitter-tweet&quot; data-dnt=&quot;true&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Probability of &amp;quot;technology works&amp;quot;: 50%. Probability of a video call working with video and audio both ways: 50% * 50% * 50% * 50% = 6.25%&lt;/p&gt;&amp;mdash; Márton Salomváry (@salomvary) &lt;a href=&quot;https://twitter.com/salomvary/status/739755523311013889?ref_src=twsrc%5Etfw&quot;&gt;June 6, 2016&lt;/a&gt;&lt;/blockquote&gt; &lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;There is complicated technology involved in videoconferencing,
participants might have different cultural backgrounds, use diverse
hardware and software equipment and will dial in from less-than-ideal
surroundings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Therefore it is imperative for everyone to aim for the Perfect
Conference Call&lt;/strong&gt; and hope you will only have minor glitches. Below is
my advice on how to do accomplish this.&lt;/p&gt;

&lt;h2 id=&quot;-only-meet-if-necessary&quot;&gt;✋ Only meet if necessary&lt;/h2&gt;

&lt;p&gt;The best meeting is the meeting that did not happen, keep their
numbers low, find other ways to sort things out.&lt;/p&gt;

&lt;h2 id=&quot;-keep-the-number-of-participants-low&quot;&gt;👥 Keep the number of participants low&lt;/h2&gt;

&lt;p&gt;Inviting more people to a conference call drastically increases the
chances of failure. Think twice before inviting someone. Will they
really contribute? Do they really benefit? Can they get quickly
briefed before or after in writing instead?&lt;/p&gt;

&lt;p&gt;If you are invited to a meeting and have doubts whether your
participation is useful, decline politely. Don not hang around just
because someone invited you.&lt;/p&gt;

&lt;p&gt;Also do not invite people as “optional”. Be explicit to avoid spending
the first few minutes discussing “whether we should wait for X to
join”.&lt;/p&gt;

&lt;h2 id=&quot;-be-5-minutes-early&quot;&gt;⏱ Be 5 minutes early&lt;/h2&gt;

&lt;p&gt;Get ready to dial in 5 minutes before the announced start time. This
allows you to get comfortable in whatever space you use and test
whether the equipment is ready for joining. If you think turning on
your laptop at 9:59 for a 10:00 meeting will be fine, you are wrong.
You will be late and being late is not only impolite, it wastes
everyone else’s time.&lt;/p&gt;

&lt;p&gt;If the conferencing tool allows joining early you can even take the
opportunity and have bit of small-talk with your colleagues.&lt;/p&gt;

&lt;h2 id=&quot;-have-a-backup-plan&quot;&gt;🔥 Have a backup plan&lt;/h2&gt;

&lt;p&gt;If you are a participant, know what alternative ways are available for
connecting. Most conferencing solutions allow joining using a good
old phone call in case the fancy app refuses to work.&lt;/p&gt;

&lt;p&gt;As an organizer of a remote meeting be prepared that a key person will
not be able to join due to technical difficulties or they disappear
half way through the meeting. If the issue persists for a minute or
two, cancel and reschedule for another time. Do not make others wait
for someone’s network connection to recover.&lt;/p&gt;

&lt;h2 id=&quot;️-have-a-moderator&quot;&gt;👩‍⚖️ Have a moderator&lt;/h2&gt;

&lt;p&gt;Just like at offline meetings, it is useful to have a person who keeps
track of time, reminds people to stick to the agenda, facilitates
moving conversations to another channel and reminds people to mute
their microphones.&lt;/p&gt;

&lt;h2 id=&quot;-use-headphones&quot;&gt;🎧 Use headphones&lt;/h2&gt;

&lt;p&gt;Even though smartphones and laptops nowadays have decent audio quality
and feedback protection for voice calls, using headphones (preferably
with built-in microphone) is still the best way of preventing other
participants from hearing their own voice back through your
connection. Furthermore headphones help &lt;em&gt;you&lt;/em&gt; to hear and understand
them better and cut you off from distractions.&lt;/p&gt;

&lt;p&gt;If you are typing on a laptop while in a call, it is essential to use
an external microphone as the internal ones are notorious at picking
up and relaying keyboard noise to the other participants.&lt;/p&gt;

&lt;h2 id=&quot;-know-your-equipment&quot;&gt;💻 Know your equipment&lt;/h2&gt;

&lt;p&gt;There are a gazillion audio and videoconferencing hardware and
software, it is not unlikely that you will have to face something
unknown in your next meeting (even if you have been working for the
same company for a while). Be sure you have the necessary software
installed, be that a desktop or mobile app or a browser extension.&lt;/p&gt;

&lt;p&gt;Check whether you need to sign in or sign up to join in advance. If
you need to use a corporate account expect going through a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Multi-factor_authentication&quot;&gt;multi-factor authentication&lt;/a&gt; before. This is more likely if you
are signing in from a new device or a location you have not used
before.&lt;/p&gt;

&lt;p&gt;If you need to use a VPN, be sure it is working.&lt;/p&gt;

&lt;h2 id=&quot;-do-not-use-a-wi-fi-connection&quot;&gt;📶 Do not use a Wi-Fi connection&lt;/h2&gt;

&lt;p&gt;One of the unsolved problems of the 21st century is stable and
reliable wireless networking for everyone. If you have cabled network
connection available, prefer using that. If you do not, consider
buying one, they are cheap and a great investment preventing choppy
audio, frozen video or dropped calls.&lt;/p&gt;

&lt;p&gt;If you are using your home or office network, it is also useful to
have your phone set up to be used as a &lt;a href=&quot;https://en.wikipedia.org/wiki/Tethering&quot;&gt;personal hotspot&lt;/a&gt;
to have backup if your primary connection goes down.&lt;/p&gt;

&lt;h2 id=&quot;-mute-your-microphone&quot;&gt;🔇 Mute your microphone&lt;/h2&gt;

&lt;p&gt;Whenever you are not talking (especially when for longer periods) turn
off your microphone. Nobody wants to hear you breathing or coughing,
your phone notifications or fire engines passing in front of your
house.&lt;/p&gt;

&lt;p&gt;When you want to talk, don’t forget to unmute yourself :)&lt;/p&gt;

&lt;h2 id=&quot;-keep-your-environment-quiet&quot;&gt;🤫 Keep your environment quiet&lt;/h2&gt;

&lt;p&gt;Be sure your environment is as quiet as possible. If you have your
family or kids around, ask them to avoid distracting you during
meetings. If your home-office has a door, close it. If you have
windows open to a busy street, close them.&lt;/p&gt;

&lt;p&gt;Prefer not to participate from a café or bar or a busy street. If you
really must, try to find a quiet corner.&lt;/p&gt;

&lt;p&gt;If you have your camera on, sit in front of a neutral background.
People walking behind you does not help. You think your cat walking
into the scene is funny? It might be funny once, but it’s more of a
distraction for the others.&lt;/p&gt;

&lt;h2 id=&quot;-use-good-quality-conference-rooms&quot;&gt;🏢 Use good quality conference rooms&lt;/h2&gt;

&lt;p&gt;When some of the participants dial in from a regular office meeting
room and some others remotely, keep in mind that people sitting at
various distances around a laptop results in a terrible experience for
the remote people. They will struggle following the conversation since
laptop microphones are designed to pick up the voice of one person
sitting directly in front of them, not conversations from a room full
of people.&lt;/p&gt;

&lt;p&gt;Combined speaker-microphone devices are available for phis purpose,
make you employer buy one for each conference room used for remote
calls.&lt;/p&gt;

&lt;p&gt;Conference rooms often suffer from general poor acoustics, most common
is &lt;a href=&quot;https://en.wikipedia.org/wiki/Reverberation&quot;&gt;reverb&lt;/a&gt;. This can contribute to call quality problems and
even though reducing sound reflections does not cost a fortune, it is
quite common. If you office suffers from this, prefer smaller rooms
where the acoustical problems are usually less apparent.&lt;/p&gt;

&lt;h2 id=&quot;-be-disciplined-and-polite&quot;&gt;👔 Be disciplined and polite&lt;/h2&gt;

&lt;p&gt;Like in other situations, do not talk while someone else it talking,
do not cut others off and only interrupt if absolutely necessary. Keep
in mind that even if in real life some of this is acceptable, in an
on-line situation the slight delay in picture and sound and the
drastically reduced meta-communication small “deviances” can be
highly disruptive.&lt;/p&gt;

&lt;h2 id=&quot;-wear-pants&quot;&gt;👖 Wear pants&lt;/h2&gt;

&lt;p&gt;You never know when you will accidentally turn on the camera instead
of starting screen sharing. It is strongly recommended to wear &lt;em&gt;at
least&lt;/em&gt; a shirt.&lt;/p&gt;

&lt;h2 id=&quot;feedback&quot;&gt;Feedback?&lt;/h2&gt;

&lt;p&gt;How does your team do amazing (or terrible) conference calls? Do you
have something to add or disagree with? Get in touch, see below how!&lt;/p&gt;

&lt;p&gt;Does your software development team struggle with remote work? You can
&lt;a href=&quot;/availability.html&quot;&gt;hire me&lt;/a&gt; for an hour, day, week or more time of
consulting.&lt;/p&gt;

</description>
				<pubDate>Fri, 13 Mar 2020 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/remote-meeting.html</link>
				<guid>https://salomvary.com/remote-meeting.html</guid>
			</item>
		
			<item>
				<title>Memoizing with WeakMaps</title>
				<description>&lt;p&gt;I stumbled upon a performance bottleneck while building a small
progressive web app recently. Turns out that
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Date#toLocaleDateString&lt;/code&gt;&lt;/a&gt;
has terrible performance in Mobile Chrome. This method allows granular
control over how a JavaScript &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Date&lt;/code&gt; instance is turned into a human
readable string. It is a &lt;a href=&quot;https://caniuse.com/#search=toLocaleDateString&quot;&gt;relatively new
feature&lt;/a&gt; and in Mobile
Chrome it might be as slow as ~100 operations per second.&lt;/p&gt;

&lt;p&gt;Does not sound extremely bad but other browsers do several &lt;em&gt;thousand&lt;/em&gt;
operations in a second. My use case is fetching data containing lots
of dates from a server and then formatting them for humans. Certain
user interactions result in redrawing the screen and calling
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toLocaleDateString&lt;/code&gt; about 150 times. That results in  more than a
second delay in Mobile Chrome, definitely not a great user experience.&lt;/p&gt;

&lt;p&gt;Time for some optimization! One way would be not using
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toLocaleDateString&lt;/code&gt; and formatting dates “manually” but doing so is
more complicated than it sounds. I decided to
&lt;a href=&quot;https://en.wikipedia.org/wiki/Memoization&quot;&gt;memoize&lt;/a&gt; the formatted
strings.&lt;/p&gt;

&lt;p&gt;This is how the original function looked like:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formatDate&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLocaleDateString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;en-US&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2-digit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2-digit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Memoizing means, given an input, instead of some expensive
computation, return a cached result. We will use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WeakMaps&lt;/code&gt; to cache
the formatted strings.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WeakMap&lt;/code&gt; allows storing values by keys where keys are JavaScript
objects and the value can be anything. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WeakMaps&lt;/code&gt; are special because
the entries are “removed” whenever the key object is garbage
collected. Perfect fit for our caching needs!&lt;/p&gt;

&lt;p&gt;The memoized version of the function looks like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dates&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;WeakMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formatDateMemoized&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formatted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;formatted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;formatted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toLocaleDateString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;en-US&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;month&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2-digit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;2-digit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;dates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formatted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;formatted&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This runs about 24k times per second if the value is already cached.
As usual with caching, the performance improvement depends on the rate
of cache hits and misses. For my use case this worked fine: the first
render after a network response is slow (but talking to the server
tends to be slow anyway) and then subsequent rerendering of the same
data is very fast. The date instances are garbage collected and the
cache is cleaned up when the next server response is parsed.&lt;/p&gt;

&lt;p&gt;There is a gotcha though. Even though caching formatted dates works
for my use case, this approach is dangerous to generalize.&lt;/p&gt;

&lt;p&gt;Firstly, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Date&lt;/code&gt; in JavaScript is mutable. That means the &lt;em&gt;same date
instance&lt;/em&gt; might represent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2019-01-05 16:00&lt;/code&gt; at some point and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1970-02-23 10:42&lt;/code&gt; in some other point as the application runs. There
is no way to prevent this and if you ever modify the date instances
before formatting them, you will end up with hard to debug issues.&lt;/p&gt;

&lt;p&gt;Secondly, two different instances of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Date&lt;/code&gt; might represent exactly
the same point in time which means we should use a single cached
result for both but we will not because they are cached under
different keys.&lt;/p&gt;

&lt;p&gt;The only way to prevent bugs resulting from this other than “making
sure” Dates are not modified (which relies on humans not making
mistakes) is to always use immutable objects as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WeakMap&lt;/code&gt; keys.  For
dates &lt;a href=&quot;https://github.com/iamkun/dayjs#readme&quot;&gt;Day.js&lt;/a&gt; is an option,
for other data types
  &lt;a href=&quot;https://immutable-js.github.io/immutable-js/&quot;&gt;Immutable.js&lt;/a&gt; is a
  good start.&lt;/p&gt;

&lt;p&gt;A long term solution for my problem would be Chrome fixing the
performance issue though :) In case someone wants to repeat it &lt;a href=&quot;examples/tolocaledatestring-benchmark.html&quot;&gt;I
published the benchmark
here&lt;/a&gt;.&lt;/p&gt;
</description>
				<pubDate>Fri, 24 May 2019 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/memoization-with-weakmaps.html</link>
				<guid>https://salomvary.com/memoization-with-weakmaps.html</guid>
			</item>
		
			<item>
				<title>Old Coding Challenges</title>
				<description>&lt;p&gt;I was browsing through my projects archives and found a couple of old
coding challenges I created during the hiring process of past jobs I
applied for. I thought it might be fun to share them. Enjoy:)&lt;/p&gt;

&lt;h2 id=&quot;soundcloud&quot;&gt;SoundCloud&lt;/h2&gt;

&lt;p&gt;In 2012 I applied for a JavaScript Engineer role at
&lt;a href=&quot;https://soundcloud.com&quot;&gt;SoundCloud&lt;/a&gt;. I got offered a job after
successfully completing the coding challenge and the on-site
interviews in Berlin. I worked for them for 5 years.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Create a web app that allows users to create playlists of ANY tracks
on SoundCloud.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href=&quot;https://github.com/salomvary/sc-playlist&quot;&gt;https://github.com/salomvary/sc-playlist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href=&quot;https://salomvary.com/sc-playlist/&quot;&gt;https://salomvary.com/sc-playlist/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;facebook&quot;&gt;Facebook&lt;/h2&gt;

&lt;p&gt;A Facebook recruiter reached out to me in 2014 about a front-end
engineer role. I could not resist and entered the interview process,
however, after a successful coding challenge submission I failed the
on-site interviews in Menlo Park.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Given a set of events, each with a start time and end time, render
the events on a single day calendar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href=&quot;https://github.com/salomvary/fb-challenge&quot;&gt;https://github.com/salomvary/fb-challenge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href=&quot;https://salomvary.com/fb-challenge/&quot;&gt;https://salomvary.com/fb-challenge/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;axel-springer&quot;&gt;Axel Springer&lt;/h2&gt;

&lt;p&gt;This one was for a freelance project in 2019. I successfully passed
the interview process but ended up not working for them for other
reasons.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[…] App […] which gathers data from two endpoints
asynchronously, merges the responses and displays them in any way in
the browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href=&quot;https://github.com/salomvary/as-challenge/&quot;&gt;https://github.com/salomvary/as-challenge/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; &lt;a href=&quot;https://salomvary.com/as-challenge/&quot;&gt;https://salomvary.com/as-challenge/&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;bonus-track-prezi&quot;&gt;Bonus Track: Prezi&lt;/h2&gt;

&lt;p&gt;I applied to a developer position at &lt;a href=&quot;https://prezi.com/&quot;&gt;Prezi.com&lt;/a&gt; in 2012.
They did not ask for a code challenge but I was invited for a non-binding
probation week and starting with a self-introduction using the company’s own
presentation product. They offered a job but ended up not working for them
because I decided to join SoundCloud instead.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://prezi.com/p23wvus0cec_/?token=8afc191c25dba050f95a44bf8b508a1e1345b123f9ed574aeef55e4a9fd18a44&quot;&gt;&lt;strong&gt;Online presentation on Prezi.com&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
				<pubDate>Fri, 17 May 2019 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/old-coding-challenges.html</link>
				<guid>https://salomvary.com/old-coding-challenges.html</guid>
			</item>
		
			<item>
				<title>Type Checking Vanilla JS with JSDoc and TypeScript</title>
				<description>&lt;p&gt;Ever wondered about writing plain &lt;del&gt;old&lt;/del&gt; modern JavaScript
and still enjoying the benefit of strict type checking? Not quite
ready to fully migrate existing projects to
&lt;a href=&quot;https://www.typescriptlang.org/index.html&quot;&gt;TypeScript&lt;/a&gt; (or
&lt;a href=&quot;https://flow.org/&quot;&gt;Flow&lt;/a&gt; or something else)? The good news is: it’s
doable!&lt;/p&gt;

&lt;p&gt;A less known feature of the TypeScript compiler is it’s ability use
&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html&quot;&gt;type definitions added to JavaScript by using JSDoc
comments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When is this useful?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You are familiar with JSDoc but not ready to learn TypeScript yet.&lt;/li&gt;
  &lt;li&gt;You are prototyping or targeting modern browsers only.&lt;/li&gt;
  &lt;li&gt;You want advanced IDE support like code completion and highlighting
errors.&lt;/li&gt;
  &lt;li&gt;You don’t want to use a transpiler, Webpack and friends.&lt;/li&gt;
  &lt;li&gt;But want to have all the warm feeling of &lt;strong&gt;type safety&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;p&gt;Enabling TypeScript support on a JavaScript project is
straightforward. Add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsconfig.json&lt;/code&gt; file at the project root with
the following contents:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;compilerOptions&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Allow checking JavaScript files&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;allowJs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// I mean, check JavaScript files for real&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;checkJs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Do not generate any output files&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;noEmit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s also add some example code:&lt;/p&gt;

&lt;p&gt;greeter.js:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Greeter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/**
   * @param {string} name
   */&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;greet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`Hello &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;main.js:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Greeter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./greeter.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.js&lt;/code&gt; in Visual Studio Code or another editor that has
integrated TypeScript support you will see a compile error at
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g.greet()&lt;/code&gt; because we forgot to pass in a required string argument.
If you install the TypeScript compiler with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install --save-dev
typescript&lt;/code&gt; you can also use the following command to verify your
JavaScript code from the command line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx tsc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In our case the output will look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ npx tsc
main.js:4:1 - error TS2554: Expected 1 arguments, but got 0.

4 g.greet()
  ~~~~~~~~~

  greeter.js:5:10
    5   greet (message) {
               ~~~~~~~
    An argument for &apos;message&apos; was not provided.


Found 1 error.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The compile error can easily be fixed by adding an argument to the
function call: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g.greet(&apos;John&apos;)&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;using-dependencies&quot;&gt;Using dependencies&lt;/h2&gt;

&lt;p&gt;Any real project will eventually end up using dependencies from the
&lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm registry&lt;/a&gt;. Since all major browsers
support native ES6 modules and an increasing number of npm packages
are published in this format, it is nowadays possible to develop
modularized codebases without any bundling or transpiling tool like
Webpack.&lt;/p&gt;

&lt;p&gt;It is totally fine to use use a tool, but if you are adventurous like
me you can try going all the “vanilla way”. This however needs a bit
of further tweaking.&lt;/p&gt;

&lt;p&gt;We will use the &lt;a href=&quot;https://www.npmjs.com/package/lodash-es&quot;&gt;lodash-es&lt;/a&gt;
npm module as an example. It is &lt;a href=&quot;https://lodash.com/&quot;&gt;Lodash&lt;/a&gt; in the
form of many small ES modules.&lt;/p&gt;

&lt;p&gt;First install using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install --save lodash-es&lt;/code&gt;. We can import and
use the modules directly like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;upperFirst&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;/node_modules/lodash-es/upperFirst.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;upperFirst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;foo bar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However the TypeScript compiler does not give us any “protection”
here, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upperFirst&lt;/code&gt; has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;any&lt;/code&gt; type meaning whatever we do, it &lt;em&gt;will&lt;/em&gt;
compile. We need to install TypeScript definitions separately with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install --save @types/lodash-es&lt;/code&gt;. Once that is done, our
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsconfig.json&lt;/code&gt; needs to be tweaked a bit:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;compilerOptions&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;allowJs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;checkJs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;noEmit&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// This resolves types for imports&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// with /node_modules/ prefix&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;baseUrl&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/node_modules/*.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;node_modules/@types/*&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it. At this point &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx tsc&lt;/code&gt; should check calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upperFirst&lt;/code&gt;
and fail when we make a mistake like passing in an argument of wrong
type like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upperFirst(42)&lt;/code&gt;. Visual Studio Code should also pick up
the definitions:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/vscode-tsc-screenshot.png&quot; alt=&quot;Screenshot of Visual Studio Code showing typed upperFirst
function&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The TypeScript compiler allows type checking code written in plain
JavaScript and also leverages type definitions created for third party
libraries. This not only allows using the compiler as a “very smart
linter” but also improves editor and IDE integration (code completion,
highlighting errors). This - combined with the power of native ES6
modules - results in lightweight tooling for projects of any size.&lt;/p&gt;

&lt;p&gt;One last thing to keep in mind: not all of TypeScript is supported as
JSDoc comments and not all of JSDoc syntax is supported by the
compiler. Be sure to check out &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html&quot;&gt;the
documentation&lt;/a&gt;
and understand the differences.&lt;/p&gt;
</description>
				<pubDate>Wed, 24 Apr 2019 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/type-checking-vanilla-js-with-jdsoc-and-typescript.html</link>
				<guid>https://salomvary.com/type-checking-vanilla-js-with-jdsoc-and-typescript.html</guid>
			</item>
		
			<item>
				<title>Introduction to WebAssembly and Emscripten</title>
				<description>&lt;h2 id=&quot;what-is-web-assembly&quot;&gt;What is Web Assembly?&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;WebAssembly (abbreviated Wasm) is a binary instruction format for a
stack-based virtual machine.
&lt;sup&gt;[&lt;a href=&quot;https://webassembly.org/&quot;&gt;source&lt;/a&gt;]&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;yeah-but-what-is-it-really&quot;&gt;Yeah, but what is it really?&lt;/h3&gt;

&lt;p&gt;Wasm means high level languages like C, C++ or Rust running in the
browser nicely interoperating with JavaScript. At near native speed!&lt;/p&gt;

&lt;h3 id=&quot;why-is-that-interesting&quot;&gt;Why is that interesting?&lt;/h3&gt;

&lt;p&gt;You might say JavaScript is already super fast, why do we need
something even faster? No matter how fast it is, JavaScript will never
beat the performance of a compiled C program in many areas. Think
about computation heavy stuff like multimedia or cryptography. Or
blockchain!&lt;/p&gt;

&lt;p&gt;That on one hand means you can hyper-optimize the heavy part of a
JavaScript application using Wasm and keep the rest untouched. On the
other hand you can take existing high performant C library off the
shelf, compile it to WebAssembly and use it without even knowing how
to write C code.&lt;/p&gt;

&lt;h2 id=&quot;first-steps&quot;&gt;First steps&lt;/h2&gt;

&lt;p&gt;To start we will need something to compile to Wasm and something to
compile with. The C/C++ to Wasm compiler toolkit is called Emscripten
and can be &lt;a href=&quot;https://emscripten.org/docs/getting_started/downloads.html&quot;&gt;downloaded from
here&lt;/a&gt;.
Follow the instructions on the same page to set it up.&lt;/p&gt;

&lt;p&gt;Let’s also find something to compile! Because I am working on a
multimedia project that uses
&lt;a href=&quot;https://github.com/Kagami/ffmpeg.js&quot;&gt;ffmpeg.js&lt;/a&gt; (also compiled with
Emscripten) for encoding audio data into AAC format I was wondering
how easy would it be to port &lt;a href=&quot;https://github.com/mstorsjo/fdk-aac&quot;&gt;a standalone AAC
encoder&lt;/a&gt; to the browser. I will
use it as an example for the rest of this post.&lt;/p&gt;

&lt;p&gt;Any C/C++ project will do do it as long as it uses the standard
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./configure; make; make install&lt;/code&gt; toolchain like (probably) 99% of the
programs written for UNIX/Linux. If you never used these tools &lt;a href=&quot;https://stackoverflow.com/questions/10961439/why-always-configure-make-make-install-as-3-separate-steps&quot;&gt;here
is a brief
explanation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;compiling-the-project-to-wasm&quot;&gt;Compiling the project to Wasm&lt;/h2&gt;

&lt;p&gt;Once Emscripten is set up we can run the compiler on the C/C++ project
with the following commands:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Not every project has autoconf.sh, FDK AAC does
./autoconf.sh
# Note that we run emconfigure ./configure not just ./configure
emconfigure ./configure
# Depending on the project size, this might take a while:
emmake make
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These steps might be slightly different for a given project, it is
recommended to consult the project documentation for compilation
instructions and then substitute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./configure&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emconfigure
./configure&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emmake make&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the last command succeeds you should have a compiled LLVM bitcode
(sort of an intermediary format before the final Wasm binary)
somewhere. Figuring out where it exactly is is a bit tricky as it
depends on the project setup. Usually the last lines printed from the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emmake make&lt;/code&gt; command should give a good hint. In my case, the result
is at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.libs/aac-enc&lt;/code&gt;. You can make sure you are looking at the right
format by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file&lt;/code&gt; on the output file, like in my case:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ file .libs/aac-enc
.libs/aac-enc: LLVM IR bitcode
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last step is producing a Wasm binary and a JavaScript wrapper from
the LLVM binary. It usually goes like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;emcc project.bc -o project.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;project.bc&lt;/code&gt; is the binary produced in the previous step. If it
does not have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bc&lt;/code&gt; extension, just rename it, otherwise &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emcc&lt;/code&gt; will
complain.&lt;/p&gt;

&lt;p&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emcc&lt;/code&gt; complains about “undefined symbols” you probably have to
include the shared dependencies of the “main” binary on the command
line. This step requires familiarity with the build tools and/or the
project or some fiddling and intuition. If it’s not obvious what the
dependencies are try to find more “LLVM IR bitcode” type of files near
the root folder of the project. In the case of the FDK AAC project the
complete command is:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;emcc .libs/aac-enc.bc .libs/libfdk-aac.2.dylib -o aac-enc.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This should result in a JavaScript file and a Wasm file ready to be
executed from Node.js or a web browser. For example the AAC encoder
prints out the usage:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ node aac-enc.js
aac-enc.js [-r bitrate] [-t aot] [-a afterburner] [-s sbr] [-v vbr] in.wav out.aac
Supported AOTs:
  2	AAC-LC
  5	HE-AAC
  29	HE-AAC v2
  23	AAC-LD
  39	AAC-ELD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you load the same file in a browser using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag (or open
the generated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aac-enc.html&lt;/code&gt;) you get the same output logged to the
console. Cool but not very useful.&lt;/p&gt;

&lt;h2 id=&quot;making-use-of-the-compiled-code&quot;&gt;Making use of the compiled code&lt;/h2&gt;

&lt;p&gt;Encoding WAV files with the compiled Wasm code requires a little more
work: we need to pass in files and arguments to the executable.&lt;/p&gt;

&lt;p&gt;First we need to recompile the module with the &lt;a href=&quot;https://emscripten.org/docs/api_reference/Filesystem-API.html&quot;&gt;File System
API&lt;/a&gt;
exposed:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;emcc .libs/aac-enc.bc .libs/libfdk-aac.2.dylib -o aac-enc.js \
  -s EXTRA_EXPORTED_RUNTIME_METHODS=[&apos;FS&apos;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With the recompiled module at hand we have to write a bit of glue
code. Interacting with the generated code is possible via the exposed
&lt;a href=&quot;https://emscripten.org/docs/api_reference/module.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Module&lt;/code&gt;
object&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add this &lt;em&gt;before&lt;/em&gt; loading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aac-enc.js&lt;/code&gt; to prevent running the encoder
with no arguments initially and to actually expose the module:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Module&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;noInitialRun&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note: the generated JavaScript file is neither a CommonJS module nor
an AMD or ES6 one. It’s a global that can optionally be created
beforehand for passing in configuration options. It is possible to
emit a UMD module instead using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-s MODULARIZE=1&lt;/code&gt; flag of the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;emcc&lt;/code&gt; command or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-s EXPORT_ES6=1&lt;/code&gt; for ES6 modules. See more in
&lt;a href=&quot;https://github.com/emscripten-core/emscripten/blob/master/src/settings.js#L740-L805&quot;&gt;settings.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next we will have to set up an input file and copy it to the “virtual”
file system of the module:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input.wav&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Uint8Array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inputBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second argument to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writeFile&lt;/code&gt; is the content of the file in the
form of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TypedArray&lt;/code&gt;. Obtaining &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inputBuffer&lt;/code&gt; depends on the
environment and where the input comes from. Examples:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// If you have a File instance eg. from a form input:&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;inputBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inputFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arrayBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// If you have a URL&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;inputBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inputUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;arrayBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// In Node.js with a file path&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;inputBuffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFileSync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;inputPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can invoke the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; method of the compiled application:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callMain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;input.wav&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;output.aac&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the last step is to read the output back from the file system:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Read virtual file into a TypedArray&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;FS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;output.aac&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Turn the TypedArray into a File and return it&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;outputFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;output.aac&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;audio/aac&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outputFile&lt;/code&gt; can then be turned into a blob URL with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URL.createObjectURL&lt;/code&gt; and downloaded from the browser or directly used
in an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; element as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt;. When using Node.js &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output&lt;/code&gt; can be
directly passed in to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fs.writeFile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The above code is available as a &lt;a href=&quot;/examples/wasm-intro&quot;&gt;working example
here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;where-to-continue-from-here&quot;&gt;Where to continue from here?&lt;/h2&gt;

&lt;p&gt;The above simple example is only the beginning. In real life you might
want to turn the code above into something reusable, which needs a bit
more complicated glue to account for the initialization phase of the
Wasm module and to cover error scenarios. I started turning the FDK
AAC encoder example into a more elaborate library, the
work-in-progress state is &lt;a href=&quot;https://github.com/salomvary/fdk-aac.js/&quot;&gt;available
here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some to-be-ported codebases might do more than read command line
arguments and files, eg. use threads, graphics or audio, which needs
special treatment. The Emscripten documentation has an &lt;a href=&quot;https://emscripten.org/docs/porting/index.html&quot;&gt;entire chapter
on porting&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Even in our arguments-and-files use case, we could do better. The
underlying library &lt;a href=&quot;https://github.com/mstorsjo/fdk-aac/blob/master/libAACenc/include/aacenc_lib.h#L1651-L1654&quot;&gt;exposes C functions to encode
AAC&lt;/a&gt;
in a buffered way, without using files directly. This has the
advantage or more fine grained control of execution and potentially
using less memory but the glue code will be more complicated. It also
requires some knowledge of C programming. A good start is the &lt;a href=&quot;https://emscripten.org/docs/api_reference/preamble.js.html#calling-compiled-c-functions-from-javascript&quot;&gt;Calling
compiled C functions from
JavaScript&lt;/a&gt;
section in the Emscripten docs.&lt;/p&gt;

&lt;p&gt;It is also important to note that the Emscripten commands above
generate &lt;em&gt;non-optimized&lt;/em&gt; code. Make sure you read and follow the
&lt;a href=&quot;https://emscripten.org/docs/optimizing/Optimizing-Code.html&quot;&gt;Optimization section&lt;/a&gt; before using Emscripten and Wasm in production.&lt;/p&gt;

&lt;h2 id=&quot;further-reading&quot;&gt;Further reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/&quot;&gt;A cartoon intro to WebAssembly by Lin Clark&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hacks.mozilla.org/2018/10/webassemblys-post-mvp-future/&quot;&gt;WebAssembly’s post-MVP future: A cartoon skill tree by Lin Clark&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://emscripten.org/docs/getting_started/index.html&quot;&gt;Emscripten doumentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/WebAssembly&quot;&gt;WebAssembly on MDN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
				<pubDate>Mon, 08 Apr 2019 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/introduction-to-web-assembly-and-emscripten.html</link>
				<guid>https://salomvary.com/introduction-to-web-assembly-and-emscripten.html</guid>
			</item>
		
			<item>
				<title>Vue.js State Management vs. Clean Architecture</title>
				<description>&lt;p&gt;Being independent of frameworks is an important attribute of clean
architecture. This post explores the challenges of managing the
application state in a way that is less specific to Vue.js.&lt;/p&gt;

&lt;p&gt;This is how Ucle Bob explains it in &lt;a href=&quot;https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html&quot;&gt;The Clean
Architecture&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The architecture does not depend on the existence of some library of
feature laden software. This allows you to use such frameworks as
tools, rather than having to cram your system into their limited
constraints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means you should not need to be writing the business logic of
your application in a framework prescribed way.&lt;/p&gt;

&lt;h2 id=&quot;vuejs-basics&quot;&gt;Vue.js basics&lt;/h2&gt;

&lt;p&gt;Vue.js is a JavaScript framework for building user interfaces for the
web. At the heart of the framework lies a &lt;b&gt;reactivity system&lt;/b&gt;
that maps the component &lt;b&gt;state&lt;/b&gt; to rendered DOM.&lt;/p&gt;

&lt;p&gt;The example snippet below renders &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;b&amp;gt;Hello Vue!&amp;lt;/b&amp;gt;&lt;/code&gt; and if the value
of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt; changes the rendered element gets updated as well:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Hello Vue!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;lt;b&amp;gt;{{ message }}&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;reactive-state-limitations&quot;&gt;Reactive state limitations&lt;/h2&gt;

&lt;p&gt;Any property under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; becomes a &lt;em&gt;reactive property&lt;/em&gt; when the Vue
instance initializes. One might think that this means &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; can hold
arbitrary data structures which can be manipulated any way we like.
Unfortunately that is not true, there are limitations:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Object property addition and deletion is not reactive&lt;/li&gt;
  &lt;li&gt;Array item reassignment is not reactive&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols&quot;&gt;ES6 iteration
protocols&lt;/a&gt;
are &lt;a href=&quot;https://github.com/vuejs/vue/issues/1319&quot;&gt;not supported&lt;/a&gt; which
means no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v-for&lt;/code&gt; on custom iterables and no support for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Set&lt;/code&gt;,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt; and friends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have not found a single authoritative source explicitly listing
which types are supported and which are not as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; properties in
Vue.js. It seems like the best approach is to stick to using “basic”
types (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Symbol&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Number&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boolean&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;undefined&lt;/code&gt;) only and avoid anything else (no custom prototypes, no
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Date&lt;/code&gt; or other language or browser built-in types).&lt;/p&gt;

&lt;h2 id=&quot;so-where-is-the-problem&quot;&gt;So where is the problem?&lt;/h2&gt;

&lt;p&gt;The limitations on what types and what methods Vue allows to use might
not appear to be a big deal for small and simple use cases. A real
life application might eventually reach a point where there appears to
be a piece of logic that is independent from the presentation and will
benefit from encapsulation - this logic is often called business
logic.&lt;/p&gt;

&lt;p&gt;The clean architecture approach recommends a business logic
implementation to be implemented in a framework independent way but
&lt;strong&gt;Vue’s constraints mean we can not leverage the full power of modern
JavaScript&lt;/strong&gt; (or whatever language we compile to JavaScript). When
implementing data structures or algorithms to be used for representing
or manipulating component state, we have to play by the Vue.js rules.&lt;/p&gt;

&lt;h2 id=&quot;an-example&quot;&gt;An example&lt;/h2&gt;

&lt;p&gt;Let’s say we want to implement an application that shows a timeline
and allows adding events to it:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;v-for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;entry in timeline&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    {{ entry.year }}: {{ entry.event }}
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;v-model=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;year&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;:
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;v-model=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;event&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;click=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;addEvent&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Add event&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;#app&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2018&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Someone clicked a button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1946&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ENIAC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1981&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;IBM PC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2007&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;iPhone&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;addEvent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This simple example works as expected as we are mutating the state in
a Vue.js compliant way.&lt;/p&gt;

&lt;h2 id=&quot;extracting-the-business-logic&quot;&gt;Extracting the business logic&lt;/h2&gt;

&lt;p&gt;As the application evolves we might realize that we often have to add
events to the timeline, we might want to prevent certain operations on
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeline&lt;/code&gt; array or make sure it is always sorted. This will
result in extracting a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timeline&lt;/code&gt; class, something like this:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Timeline&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initialEntries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Keep the cloned entries array private&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;entries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;initialEntries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;c1&quot;&gt;// Create a timeline with some entries&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1946&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ENIAC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1981&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;IBM PC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2007&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;iPhone&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By wrapping the array and keeping it private we can add our own
manipulation methods which can ensure the structure is solid.&lt;/p&gt;

&lt;p&gt;However if we substitute the plain array with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timeline&lt;/code&gt; in Vue
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; it will no longer work. Vue has no idea how to iterate over
a custom class, we have to convert it to an array:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Timeline&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(...)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;toArray&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Clone the private array&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;An even cleaner option would be implementing a custom
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol&quot;&gt;iterable&lt;/a&gt;
but that is not something Vue can directly use.&lt;/p&gt;

&lt;p&gt;Calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toArray()&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v-for&lt;/code&gt; will have the desired results:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;li&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;v-for=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;entry in timeline.toArray()&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We are less lucky however when it comes to adding entries to the
timeline. Even if we replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;push&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timeline.add&lt;/code&gt; this will
&lt;strong&gt;not&lt;/strong&gt; result in updating the rendered DOM:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;addEvent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Looks like we reached a dead end and in fact we have been told to only
use “basic” types and don’t even try to add our own methods.&lt;/p&gt;

&lt;h2 id=&quot;the-way-forward&quot;&gt;The way forward&lt;/h2&gt;

&lt;p&gt;Turns out the workaround is simple: instead of updating an object
assigned to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt; property in-place, entirely &lt;em&gt;replacing&lt;/em&gt; the
instance does kick off Vue’s reactive updates:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;addEvent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Clone the timeline&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Add an entry to the new timeline&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;timeline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;year&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Replace this.timeline with the new instance&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;timeline&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeline&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sharp-eyed readers might notice that at this point we could implement
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timeline&lt;/code&gt; as an immutable data structure as there will never be a
need for in-place updates.&lt;/p&gt;

&lt;p&gt;This approach has some &lt;strong&gt;downsides&lt;/strong&gt; though:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is more code, and more code is more prone to errors and has a
higher maintenance cost.&lt;/li&gt;
  &lt;li&gt;Iterable data structures have to be converted to plain arrays before
passing in to Vue. This has a performance cost.&lt;/li&gt;
  &lt;li&gt;A custom class exposing both immutable and mutable methods can
lead to confusion as using the latter will not work with Vue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Your app might be simple enough or 99% of the business logic lives on
the server so that the Vue.js constraints discussed here have no big
impact on the architecture.&lt;/p&gt;

&lt;p&gt;If you are building something more complicated, my recommendation is
the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use Vue.js for building &lt;strong&gt;simple and “dumb” components&lt;/strong&gt; with as
little business logic, as possible.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Use &lt;a href=&quot;https://vuex.vuejs.org/&quot;&gt;Vuex&lt;/a&gt;&lt;/strong&gt; for managing non-trivial
application state but keep in mind, Vuex &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; has the same
limitations as Vue.js &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Keep everything else outside of Vue.js&lt;/strong&gt; and Vuex and accept some
complexity tradeoffs. For example:
    &lt;ul&gt;
      &lt;li&gt;Implement data structures and algorithms in their own classes and
modules. Consider using immutable data.&lt;/li&gt;
      &lt;li&gt;Separate data access (communication with the server or other
storage) in it’s own layer (see the &lt;a href=&quot;https://www.martinfowler.com/eaaCatalog/repository.html&quot;&gt;Repository
Pattern&lt;/a&gt;).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description>
				<pubDate>Fri, 11 Jan 2019 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/vuejs-clean-architecture.html</link>
				<guid>https://salomvary.com/vuejs-clean-architecture.html</guid>
			</item>
		
			<item>
				<title>Snapshot Testing Web Audio Code Meetup Presentation</title>
				<description>&lt;p&gt;I had the opportunity to do a lightning talk about audio snapshot
testing at the &lt;a href=&quot;https://www.meetup.com/en-AU/Berlin-Web-Audio-Meetup/events/256235820/&quot;&gt;last edition of the Berlin Web Audio
Meetup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The example code and a longer explanation are available in &lt;a href=&quot;https://github.com/salomvary/audio-snapshot-testing/&quot;&gt;this
repository&lt;/a&gt;.&lt;/p&gt;
</description>
				<pubDate>Wed, 05 Dec 2018 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/web-audio-meetup-berlin.html</link>
				<guid>https://salomvary.com/web-audio-meetup-berlin.html</guid>
			</item>
		
			<item>
				<title>My First Year as a Freelance Software Engineer</title>
				<description>&lt;p&gt;In the summer of 2017 &lt;a href=&quot;/things-learned-at-soundcloud.html&quot;&gt;my last permanent job unexpectedly came to an
end&lt;/a&gt;. I used
this opportunity to start doing something different.&lt;/p&gt;

&lt;p&gt;Having been an employee for almost two decades I had the idea of
becoming a freelancer on my mind for many years. I always found the
flexibility of time and space, a wide variety of ever changing
projects and my success more depending on my own abilities (as opposed
to office politics) appealing in freelancing.&lt;/p&gt;

&lt;p&gt;I believe running a business (even if it’s a one-person show) is a
useful experience and therefore my career would benefit from becoming
independent for a while and offering my services directly at the free
market.&lt;/p&gt;

&lt;p&gt;Disclaimer: this post is more about self reflection than giving advice
to others who want to hop on the freelance train. I do not consider
myself an expert in this field, take everything with a grain of salt
here and be sure you talk to a qualified expert before making a move.&lt;/p&gt;

&lt;h2 id=&quot;how-did-i-start&quot;&gt;How did I start?&lt;/h2&gt;

&lt;p&gt;Getting laid off together with another 140 people from SoundCloud was
the best kick start I could have wished for. The layoffs were big news
and when I added myself to the &lt;a href=&quot;https://docs.google.com/spreadsheets/u/0/d/1ZP8FNlL0a-SvSpZFflxOj2LioK66lmB3S095A5FmOjg/htmlview?sle=true#&quot;&gt;Hire a SoundClouder
spreadsheet&lt;/a&gt;
recruiting requests started pouring in. As usual in recruiting, 90% of
the inquiries was irrelevant, still by the time I read through
hundreds of emails I ended up with a handful of promising leads.&lt;/p&gt;

&lt;p&gt;The gardening leave gave me enough time to prepare the paperwork for
becoming a freelancer. The first step was to find a tax accountant
(Steuerberater). Setting up everything on your own is theoretically
possible but I have never heard of anyone in this business doing that.&lt;/p&gt;

&lt;p&gt;I briefly considered becoming an &lt;a href=&quot;https://en.wikipedia.org/wiki/E-Residency_of_Estonia&quot;&gt;e-Resident of
Estonia&lt;/a&gt; too
which supposedly offers no-nonsense bureaucracy for businesses,
however being a resident of Germany means I have to deal with the
local authorities anyway. Maybe one day for a side-business.&lt;/p&gt;

&lt;p&gt;I found a tax advisor who speaks English (back then I was not too
confident with my German) and was willing to help. They are quite old
fashioned bureaucrats: papers, folders, ring binders everywhere.
Germany is a paper-stamp-and-signature based country to an extreme
extent, tax advisors are no exception. I convinced them to exchange
documents via email although they plain refused to use anything else
like DropBox or one of the many cloud based bookkeeping services.&lt;/p&gt;

&lt;p&gt;Running a freelance business requires a bank account which is
recommended to be a separate one from the one you use for your private
needs. As someone who prefers paperless solutions I went for N26 which
fulfilled all my expectations. The only paper they used was the
envelop they sent the credit card in.&lt;/p&gt;

&lt;p&gt;Although this is not a requirement I subscribed for Debitoor, an
invoicing and bookkeeping platform. Services like this allow
collaboration with your tax accountant but I am currently not using
this feature for the above mentioned reasons.&lt;/p&gt;

&lt;p&gt;Registering myself with the tax authorities took about a week and from
then on I was all set.&lt;/p&gt;

&lt;h2 id=&quot;the-first-projects&quot;&gt;The first projects&lt;/h2&gt;

&lt;p&gt;Thanks to the viral Hire a SoundClouder spreadsheet I had two
contracts before even my gardening leave ended. The idea was to work
on two projects the same time with 50%-50% time share totaling about
40 hours per week. The motivation behind: more exposure to projects,
easy backup if one of them flops, testing out how part time commitment
can work.&lt;/p&gt;

&lt;p&gt;One quickly turned out to be a flop due to toxic environment and
wildly misaligned expectations, the other is still a client I work for.
Working on these two contracts taught me that booking myself to 100% of
my available time on multiple projects way too much.&lt;/p&gt;

&lt;p&gt;Despite closing the deals with the very first clients was quick and
low effort, in order to learn more about the market I kept talking to
potential clients and recruiters. I probably had a conversation with
15-20 potential leads.&lt;/p&gt;

&lt;p&gt;I learned the following: many companies want permanent employees and
not freelancers; positioning myself as a generalist might not be the
best idea; pricing is insanely hard.&lt;/p&gt;

&lt;p&gt;Because I had very little idea how to set a price tag, I used the
first leads to experiment in this field. I roughly calculated a
baseline hourly rate from my previous year’s salary as an employee and
used a -50% to +100% range. I received a wide variety of reactions.
They ranged from laughing loud, never answering emails again or to
politely negotiating down by 5%.&lt;/p&gt;

&lt;h2 id=&quot;self-improvements&quot;&gt;Self improvements&lt;/h2&gt;

&lt;p&gt;I believe continuous self improvement in this profession is essential
and have been practicing ever since I entered the software engineering
career decades ago. I made a few moves over the last year specifically
to support my freelance ambitions.&lt;/p&gt;

&lt;p&gt;I hired a German teacher and are taking regular lessons. As a result I
am no longer scared to join project at German speaking companies. It
also gives me more confidence when dealing with legal matter and
paperwork.&lt;/p&gt;

&lt;p&gt;Also read a couple of “self-help” books on how to be better at running
myself as a business:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;How To Win Friends And Influence People by Dale Carnegie: large
collection of anecdotes on getting along with other humans. Not my
favorite but a good start and inspiration on improving people
skills. It’s also &lt;em&gt;very&lt;/em&gt; American.&lt;/li&gt;
  &lt;li&gt;The Software Engineer’s Guide to Freelance Consulting by Zack Burt:
a kick start into an unknown new world, summarized the most
important aspects of freelancing in a compact form. Some parts are
US specific.&lt;/li&gt;
  &lt;li&gt;The Personal MBA by Josh Kaufman (in-progress): another book from
the “for the impatient” league, if you never studied business and
whatever you learned about economics at high school or university is
gone, this book can be used as a refresher. Some concepts are
difficult to apply to services like software development.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-worked-well&quot;&gt;What worked well&lt;/h2&gt;

&lt;p&gt;Over the last year I managed to expose myself to working for a wide
range of actual and potential clients and this helped me to gain
enough experience and confidence to continue. The original plan was to
freelance for at least one year and see if it works for me; today the
answer is a definite yes.&lt;/p&gt;

&lt;p&gt;One important motivation was to be independent of time, space and of
fixed working hours. I had doubts in the past whether remote work is
for me but a 5 months long contract showed me I can be efficient
without showing up in an office every day.&lt;/p&gt;

&lt;p&gt;Working less than 40 hours a week is also a big win! I spent most of
the extra time on well-being, health and self-improvement. I do not
mind the money I did not make while not working at all as long as I
can pay all the bills. Although it is hard to measure I think the time
I spent working was more focused and the outcome of high quality.  The
positive feedback I received so far confirms this.&lt;/p&gt;

&lt;h2 id=&quot;what-could-i-have-done-better&quot;&gt;What could I have done better&lt;/h2&gt;

&lt;p&gt;Contrary to the above I did not organize my time as efficiently as I
should have. I did not end up in complete chaos like working at night
and sleeping during the day but I wish I was more disciplined with my
daily routines. Working remotely on a long project is way more
challenging than it sounds.&lt;/p&gt;

&lt;p&gt;I could have relied on my senses more when spotting problematic
projects and toxic environments. Each disappointment and conflict was
preceded by a bad feeling and I still ignored the feeling and went
ahead. The lesson? No money is worth causing myself a large amount of
pain.&lt;/p&gt;

&lt;p&gt;I had big plans for networking and exposing myself “outside” a year
ago which quickly faded after the initial excitement, primarily due to
laziness. I still believe I should attend meetups or conferences
regularly, maybe even present some times, participate in developer
communities (especially in-real-life mentoring and helping out people
in online forums).&lt;/p&gt;

&lt;p&gt;My online portfolio received a quick facelift and managed to add some
content but the amount of blog posts was nowhere near what I planned.&lt;/p&gt;

&lt;p&gt;Talking of the portfolio, I am still uncertain whether positioning
myself as a &lt;em&gt;full-stack&lt;/em&gt; software engineer is a good idea (as opposed
to a “specialist of something”). Most of the time companies and
recruiters are searching for more specific skills (and &lt;a href=&quot;https://twitter.com/salomvary/status/989453629361975296&quot;&gt;sometimes too
specific for my
taste&lt;/a&gt;). Even
though I could perfectly fit into many of these projects marketing
myself as a generalist might turn potential clients away.&lt;/p&gt;

&lt;h2 id=&quot;how-does-the-future-look-like&quot;&gt;How does the future look like?&lt;/h2&gt;

&lt;p&gt;I will stay on the freelance track, figure out how to position myself
better and work on the improvement areas. I will keep you posted!&lt;/p&gt;

&lt;h2 id=&quot;tools-and-services-i-use&quot;&gt;Tools and services I use&lt;/h2&gt;

&lt;p&gt;Readers might ask what tools I use to keep the freelance business
going. The non-exhaustive list is below:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;N26 for banking&lt;/li&gt;
  &lt;li&gt;Toggl for time tracking and reporting&lt;/li&gt;
  &lt;li&gt;Tadam app for Pomodoro timer to keep me focused&lt;/li&gt;
  &lt;li&gt;Debitoor for invoicing and expense tracking&lt;/li&gt;
  &lt;li&gt;Doxie Go SE scanner to scan and OCR incoming documents&lt;/li&gt;
  &lt;li&gt;Document shredding bin (Datenentsorgungsbox) from BSR (Berlin’s
recycling company) to dispose scanned and archived documents&lt;/li&gt;
&lt;/ul&gt;

</description>
				<pubDate>Fri, 23 Nov 2018 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/one-year-in-freelancing.html</link>
				<guid>https://salomvary.com/one-year-in-freelancing.html</guid>
			</item>
		
			<item>
				<title>Things I Learned While Working at SoundCloud</title>
				<description>&lt;p&gt;My almost 5 years of employment as a software engineer at SoundCloud
&lt;a href=&quot;https://techcrunch.com/2017/07/06/soundcloud-the-youtube-for-audio-cuts-173-jobs-closes-san-francisco-london-offices/&quot;&gt;came to an abrupt end&lt;/a&gt;
last summer. Enough time has passed since my last working day so
that I can now reflect on my experience and recall the most important
takeaways.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Working in a “fast moving environment” taught me how to efficiently
work together with people of a wide variety personalities, adapt to
different team structures and project management methodologies.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Fast moving environment has downsides too. By the time I
established a context with my managers either they moved on or I
switched teams and had to start over with someone else.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Being surrounded smart people - especially ones smarter than me -
was a great experience. Never lacking inspiration and whenever I got
stuck with something there was an expert happy to help out.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I appreciated the &lt;a href=&quot;https://landing.google.com/sre/book/chapters/postmortem-culture.html&quot;&gt;postmortem culture&lt;/a&gt;,
the culture of learning from our own mistakes and not blaming
individuals, which was adopted by the entire engineering team.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I learned how there are different ways of providing feedback some
are better, some are worse, especially when you disagree. Used these
learnings to improve my approach to giving and receiving criticism a
lot.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I had no idea how complicated and in many ways problematic the
Music Industry is before joining SoundCloud. There I learned a lot,
including many things I never wished to know.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;SoundCloud was the first place where I worked in a &lt;a href=&quot;https://en.wikipedia.org/wiki/Microservices&quot;&gt;microservice
architecture&lt;/a&gt;. Learned
many lessons on the good and bad ways of implementing this pattern
while building a couple of services from scratch and contributing to a
lot more.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I learned about the &lt;a href=&quot;https://en.wikipedia.org/wiki/Open_source_model&quot;&gt;open source
model&lt;/a&gt; or collective
&lt;a href=&quot;https://martinfowler.com/bliki/CodeOwnership.html&quot;&gt;code ownership&lt;/a&gt; of
shared libraries or services and how lack of clarity and resources can
defeat this otherwise great idea.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I got completely sold on &lt;a href=&quot;https://en.wikipedia.org/wiki/Continuous_delivery&quot;&gt;continuous delivery&lt;/a&gt; and
continuous integration as the True Way of shipping software in an
agile environment.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;As SoundCloud moved towards self-service end-to-end full-stack
teams I wore the &lt;a href=&quot;https://en.wikipedia.org/wiki/DevOps&quot;&gt;DevOps&lt;/a&gt; hat
too. The upside was shipping things even faster, the downside long
hours of firefighting at weekend nights (with compensation to be
fair).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I also learned the hard way how low quality tooling, slow and flaky
builds and convoluted continuous integration setups cause extreme
amount of developer frustration, anger and loss of productivity.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I learned the importance of &lt;a href=&quot;https://chris.beams.io/posts/git-commit/&quot;&gt;writing high quality commit
comments&lt;/a&gt;, tried to do
my best on improving code documentation too.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I am glad I had the opportunity to write production code in
Scala, Java, Ruby, JavaScript, TypeScript, Go, Clojure, SQL, Bash,
POSIX shell and maybe others I can no longer recall.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I learned to use &lt;a href=&quot;https://en.wikipedia.org/wiki/Singular_they&quot;&gt;singular they&lt;/a&gt;
as a personal pronoun and not to use “guys” to address a group of
people. Had many other lessons about the importance of diversity and
inclusion.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;No other previous job taught me lessons about dealing with my own
&lt;a href=&quot;https://www.ecu.ac.uk/guidance-resources/employment-and-careers/staff-recruitment/unconscious-bias/&quot;&gt;unconscious bias&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I also learned the importance of avoiding &lt;a href=&quot;https://en.wikipedia.org/wiki/Leading_question&quot;&gt;leading
questions&lt;/a&gt; while
interviewing candidates.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Among many other communication tricks I learned how to email a
large group of people using bcc to prevent someone accidentally
sending a reply to the entire company.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Black belt masters mentored me in the skill of contributing to any
topic on any communication channel using only GIFs and emojis.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I learned who &lt;a href=&quot;https://martinfowler.com/&quot;&gt;Martin Fowler&lt;/a&gt; is and how
no serious technical blog post can exist without citing him at least
once.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I researched the Probability of Video Call Working in Both
Directions Theory during my tenure and
&lt;a href=&quot;https://twitter.com/salomvary/status/739755523311013889&quot;&gt;established&lt;/a&gt;
that it is a constant 6.25%.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</description>
				<pubDate>Thu, 26 Apr 2018 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/things-learned-at-soundcloud.html</link>
				<guid>https://salomvary.com/things-learned-at-soundcloud.html</guid>
			</item>
		
			<item>
				<title>Real World Experience with ES6 Modules in Browsers</title>
				<description>&lt;p&gt;All modern browsers &lt;a href=&quot;https://caniuse.com/#feat=es6-module&quot;&gt;landed native ECMAScript 6 modules
support&lt;/a&gt; last year. I was
excited to try them out on a real project and did not have to wait for
too long. I started prototyping a web project for a client a few
months ago and decided to use native modules as long as it does not
prevent me from getting things done.&lt;/p&gt;

&lt;h2 id=&quot;es6-modules-in-browsers&quot;&gt;ES6 modules in browsers?&lt;/h2&gt;

&lt;p&gt;This post does not explain browser-native modules in detail. I
recommend reading &lt;a href=&quot;https://jakearchibald.com/2017/es-modules-in-browsers/&quot;&gt;Jake Archibald’s excellent
article&lt;/a&gt; if
you want to know more. Here is an example with the essentials:&lt;/p&gt;

&lt;p&gt;index.html:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;module&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;app.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;app.js:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lib&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./lib.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;doStuff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;You will need a &lt;em&gt;static&lt;/em&gt; web server (&lt;a href=&quot;https://gist.github.com/willurd/5720255&quot;&gt;most likely you already have
one&lt;/a&gt;) for development as
ES6 modules can not be loaded directly from the file system.&lt;/li&gt;
  &lt;li&gt;If modules are served from a web server with authentication enabled
(eg. HTTP basic) make sure the script tag has the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crossorigin=&quot;use-credentials&quot;&lt;/code&gt; attribute to avoid surprises.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;using-external-dependencies&quot;&gt;Using external dependencies&lt;/h2&gt;

&lt;p&gt;As long as you write your own modules and import them with ES6 module
syntax, you are lucky. Eventually any non-trivial project will depend
on external dependencies. And this is where things will become
complicated.&lt;/p&gt;

&lt;p&gt;Let’s start with an overview of how people write and publish
JavaScript libraries nowadays:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Written in ES6 syntax or older without using ES6 modules&lt;/li&gt;
  &lt;li&gt;Written in ES6 and using ES6 modules&lt;/li&gt;
  &lt;li&gt;Written in a compile-to-JavaScript language (like TypeScript or
CoffeeScript)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before publishing to &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt; or a CDN libraries
are compiled into one or more other formats that can (or can not) be
consumed by browsers or certain versions of Node.js. The most common
publishing formats are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Published as &lt;a href=&quot;http://wiki.commonjs.org/wiki/Modules/1.1.1&quot;&gt;CommonJS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Published as &lt;a href=&quot;http://wiki.commonjs.org/wiki/Modules/1.1.1&quot;&gt;Universal Module Definition&lt;/a&gt;
(UMD) or something else that even older browsers can directly use (or with
a lightweight loader polyfill)&lt;/li&gt;
  &lt;li&gt;Published as ES6 modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now comes the sad reality. Most of these formats can not be used with
the shiny new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import &apos;./app.js&apos;&lt;/code&gt; syntax. Anything that is not in the
new module format simply does not work.&lt;/p&gt;

&lt;p&gt;Unfortunately even most libraries authored or published in ES6 module
format will not work because they target
&lt;a href=&quot;https://en.wikipedia.org/wiki/Source-to-source_compiler&quot;&gt;transpilers&lt;/a&gt;
and rely on the Node.js ecosystem. Why is that a problem? Using bare
module paths like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import _ from &apos;lodash&apos;&lt;/code&gt; is currently invalid,
browsers don’t know what to do with them.&lt;/p&gt;

&lt;p&gt;Using UMD modules with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt; is not possible either. Browsers throw
a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SyntaxError&lt;/code&gt; when attempting to parse a file as an ES6 module if it
contains no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export&lt;/code&gt; definition.&lt;/p&gt;

&lt;p&gt;In real life therefore:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You will still need &lt;a href=&quot;https://webpack.js.org/&quot;&gt;Webpack&lt;/a&gt; or something
similar to load non-ES6 modules or rely on plain old script tags and
global variables. Not pretty but might work until the dependencies
are only a handful.&lt;/li&gt;
  &lt;li&gt;Libraries published in browser-friendly ES6 format can be used
directly from a CDN or from a local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules&lt;/code&gt; folder when
served by the web server. In that case you will have to spell out
the whole path including &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Library usage examples:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Some authors already publish in browser friendly ES6 format.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Vue&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.esm.browser.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Lodash in ES6 format is published under a different name. When&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// using npm, the whole node_modules folder has to be exposed by the&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// static web server.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;camelCase&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./node_modules/lodash-es/camelCase.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Other libraries are authored in ES6 module format but use bare&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// module paths which does not work in browsers. CDNs like Unpkg can&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// help by expanding the modules.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;scale&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://unpkg.com/d3-scale@1.0.7/index.js?module&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Sadly there is no module expansion when installed with npm, this&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// will NOT work.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;scale&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./node_modules/d3-scale/index.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;See these &lt;a href=&quot;/examples/es6-modules&quot;&gt;ES6 module loading examples&lt;/a&gt; in your
browser.&lt;/p&gt;

&lt;h2 id=&quot;loading-templates-or-css-with-import&quot;&gt;Loading templates or CSS with import&lt;/h2&gt;

&lt;p&gt;Quite common practice in transpiler-backed workflows is importing
precompiled template or CSS files as modules. Unfortunately native
browser imports strictly require the response to be served with
JavaScript content type and the body must be parsable as an ES6
module.&lt;/p&gt;

&lt;p&gt;There is hope though. One day browsers will implement the
&lt;a href=&quot;https://whatwg.github.io/loader/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.loader&lt;/code&gt; API&lt;/a&gt; which would
allow adding custom module loaders but the standard is not even ready
to be implemented.&lt;/p&gt;

&lt;h2 id=&quot;custom-loaders-with-service-workers&quot;&gt;Custom loaders with Service Workers&lt;/h2&gt;

&lt;p&gt;As &lt;a href=&quot;https://matthewphillips.info/posts/loading-app-with-script-module&quot;&gt;someone pointed out&lt;/a&gt;
Service Workers can intercept &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;Fetch API&lt;/a&gt;
requests. This comes in handy as ES6 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch&lt;/code&gt; behind the
scenes.&lt;/p&gt;

&lt;p&gt;That means it is possible to turn any fetched resource into a module
by turning the response into a string that the browser can parse as
ES6 code. For example a template file can be turned into an exported
JavaScript string or function.&lt;/p&gt;

&lt;p&gt;Unfortunately Service Workers are &lt;a href=&quot;https://caniuse.com/#feat=serviceworkers&quot;&gt;not yet as widely
implemented&lt;/a&gt; by browsers as
ES6 modules but I was too curious and implemented a rudimentary text
file loader. Check out &lt;a href=&quot;/examples/es6-modules&quot;&gt;the examples&lt;/a&gt; and more
specifically the worker itself:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// Hijack all fetch requests to URLs ending with txt&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.txt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;respondWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// Export the response body as a JavaSript string.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// The response body has to be sanitized before turning it&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// into JavaScript code.&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// Credits: https://stackoverflow.com/a/22837870&lt;/span&gt;
          &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;newBody&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
            &lt;span class=&quot;s2&quot;&gt;`export default &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;`&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;// Replace the original response with an ES6 module&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;newBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
              &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;application/javascript&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This idea can be adopted to load CSS into the page or compile
templates on the fly. Beware however, Service Workers is a very new
technology.&lt;/p&gt;

&lt;p&gt;Fun fact: as of writing you can not write Service Workers as ES6
modules.&lt;/p&gt;

&lt;h2 id=&quot;bundling&quot;&gt;Bundling&lt;/h2&gt;

&lt;p&gt;Any serious project will at some point have to support older browsers
that don’t implement native ES6 modules yet. Unless the project only
has a very few and small dependencies fetching hundreds of small
JavaScript files will become a bottleneck. Bundling is inevitable.&lt;/p&gt;

&lt;p&gt;Widely used bundling tools like Webpack or Browserify will have no
problem transpiling ES6 modules. Likely you will transpile the code
into pre-ES6 modules, therefore &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script type=&quot;module&quot;&amp;gt;&lt;/code&gt; will have to
be rewritten to plain old script tags. Check the documentation of your
tool to see how that is possible.&lt;/p&gt;

&lt;p&gt;I used &lt;a href=&quot;https://github.com/assetgraph/assetgraph-builder&quot;&gt;AssetGraph-builder&lt;/a&gt;
to bundle the entire project and it did indeed need ugly workarounds
as AssetGraph itself does &lt;a href=&quot;https://github.com/assetgraph/assetgraph/issues/161&quot;&gt;not yet support ES6 modules&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;further-gotchas&quot;&gt;Further gotchas&lt;/h2&gt;

&lt;p&gt;Since browser-native modules are a very new technology expect
development tools to choke on them here-and-there, one such example is 
&lt;a href=&quot;https://github.com/karma-runner/karma/issues/2903&quot;&gt;Karma&lt;/a&gt; I had
trouble with.&lt;/p&gt;

&lt;p&gt;Another challenge can be supporting old browsers. If you develop using
ES6 native modules your raw code will only work in the latest browser
versions.&lt;/p&gt;

&lt;p&gt;Sharing code in ES modules in Node.js can be tricky too. &lt;a href=&quot;https://nodejs.org/api/esm.html&quot;&gt;Native ES6
modules in Node&lt;/a&gt; are behind the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--experimental-modules&lt;/code&gt; flag and require naming modules differently
using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mjs&lt;/code&gt; extension. You can use
&lt;a href=&quot;https://babeljs.io/docs/usage/cli/#babel-node&quot;&gt;babel-node&lt;/a&gt; but
(surprise!) “ES6-style module-loading may not function as expected”.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;You can start using ES6 modules for experiments, prototyping and small
projects today. Anything beyond that will eventually require more
tooling. In fact introducing Webpack in development is soon going to
happen on my current client project.&lt;/p&gt;

&lt;p&gt;My wishlist for the future:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First class ES6 native module support in build tools so that we can
still ship optimized and backwards compatible code in production.&lt;/li&gt;
  &lt;li&gt;Custom loaders for importing templates, CSS and others as modules so
that we can use the same approach for modularizing my code
everywhere.&lt;/li&gt;
  &lt;li&gt;Maybe a smart static HTTP/2 server that allows importing large
dependency trees (many small modules) efficiently without bundling
so that there is no need for insanely complicated development
servers. See this interesting &lt;a href=&quot;https://esdiscuss.org/topic/fwd-are-es6-modules-in-browsers-going-to-get-loaded-level-by-level&quot;&gt;topic on ES
Discuss&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;further-reading&quot;&gt;Further reading&lt;/h2&gt;

&lt;p&gt;I used these articles while researching for this post and highly
recommend them to learn further details about native ES6 modules in
browsers and Node.js.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://matthewphillips.info/posts/loading-app-with-script-module&quot;&gt;Loading a Modern Application with &amp;lt;script
type=module&amp;gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jakearchibald.com/2017/es-modules-in-browsers/&quot;&gt;ECMAScript modules in
browsers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://2ality.com/2017/09/native-esm-node.html&quot;&gt;Using ES modules natively in
Node.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
				<pubDate>Thu, 25 Jan 2018 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/es6-modules-in-browsers.html</link>
				<guid>https://salomvary.com/es6-modules-in-browsers.html</guid>
			</item>
		
			<item>
				<title>Switching Back to Firefox</title>
				<description>&lt;p&gt;The first web browser I remember using was &lt;a href=&quot;https://en.wikipedia.org/wiki/Netscape_(web_browser)#Netscape_Navigator_(versions_1.0%E2%80%933.0)&quot;&gt;Netscape Navigator Gold
3.0&lt;/a&gt;.
I kept using Navigator and its successor Firefox (except for a short affair involving &lt;a href=&quot;https://en.wikipedia.org/wiki/Konqueror&quot;&gt;Konqueror&lt;/a&gt; while I was using &lt;a href=&quot;https://en.wikipedia.org/wiki/KDE&quot;&gt;KDE&lt;/a&gt;) as my primary web browser both
for everyday browsing needs and web development until &lt;a href=&quot;https://en.wikipedia.org/wiki/Google_Chrome#Public_release&quot;&gt;Google Chrome came out about 9 years ago&lt;/a&gt;.
Impressed by the speed I switched and did not look back. Until recently…&lt;/p&gt;

&lt;p&gt;I was still frequently exposed to Firefox though. As a web developer I always
worked on projects that had to support all recent mainstream browsers which
includes testing and fixing bugs on Firefox. And I was not impressed.  Neither
with the visual appeal and ergonomy of the user interface nor the speed and
quality (as in lack of bugs and crashes) of the browser engine.&lt;/p&gt;

&lt;h2 id=&quot;why-bother&quot;&gt;Why bother?&lt;/h2&gt;

&lt;p&gt;I believe &lt;a href=&quot;http://quetzalcoatal.blogspot.de/2013/02/why-software-monocultures-are-bad.html&quot;&gt;software monocultures are
bad&lt;/a&gt;
and still remember the days when Internet Explorer dominated the web and
websites often plain refused to accept me with “This website requires Internet
Explorer” or similar messaging.&lt;/p&gt;

&lt;p&gt;Feeling increasingly bad about being part of WebKit and Chrome monoculture I
tried switching to Firefox earlier this year and
&lt;a href=&quot;https://twitter.com/salomvary/status/852217618496933889&quot;&gt;failed&lt;/a&gt;. It was way
too slow, way too buggy and particularly bad for my web development needs.&lt;/p&gt;

&lt;p&gt;Then came the &lt;a href=&quot;https://blog.mozilla.org/blog/2017/11/14/introducing-firefox-quantum/&quot;&gt;Firefox Quantum
announcement&lt;/a&gt;
in November 2017 where Mozilla promised to have made Firefox &lt;em&gt;real fast&lt;/em&gt; and
and pretty this time. So I decided to give it another try, something I did not
regret.&lt;/p&gt;

&lt;h2 id=&quot;the-new-firefox&quot;&gt;The new Firefox&lt;/h2&gt;

&lt;p&gt;My first impressions about the user interface were like “wow, this looks way
better than it used to!” There are some minor quirks I don’t particularly like,
for example &lt;a href=&quot;https://twitter.com/salomvary/status/932650781689483265&quot;&gt;the contrast between an active and an inactive
window&lt;/a&gt; but in general
it looks modern (flat design!) and ergonomic.&lt;/p&gt;

&lt;p&gt;The download and page rendering speed improvement feels impressive. It still
does not feel as fast as Chrome but the difference is small enough not to
bother me. I have not had more than one or two crashes over the first month and
one of that was caused by a developer tools extension.&lt;/p&gt;

&lt;p&gt;Talking of developer tools, this is the only area where Firefox falls short.
It has all the features I am used to from Chrome but the whole experience feels
sluggish especially when it comes to using the debugger. Sometimes a reloading
and stopping on a breakpoint takes 10-20 seconds which is beyond my patience
threshold. I also keep bumping into an annoying bug where “quick look” into
variables sometimes shows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;undefined&lt;/code&gt; when there is definitely a value. Still,
the features of developer tools perfectly meet my needs, the built-in &lt;a href=&quot;https://hacks.mozilla.org/2014/06/introducing-the-web-audio-editor-in-firefox-developer-tools/&quot;&gt;Web
Audio
debugger&lt;/a&gt;
is a nice extra I never discovered before.&lt;/p&gt;

&lt;p&gt;One more thing. With the Quantum release Firefox &lt;a href=&quot;https://blog.mozilla.org/addons/2017/11/20/extensions-in-firefox-58/&quot;&gt;deprecated the old extensions
api&lt;/a&gt;
which means from now on extensions have to be written using the new
cross-browser &lt;a href=&quot;https://developer.mozilla.org/en-US/Add-ons/WebExtensions&quot;&gt;Web Extensions
standard&lt;/a&gt; (sans
Safari). All but one of the Firefox extensions I use have been rewritten to the
new format. The only thing that drove me mad is the &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/sendtokindle/&quot;&gt;Send to Kindle
extension&lt;/a&gt; which
Amazon did not bother to upgrade.  I really do not see how a &lt;a href=&quot;http://www.businessinsider.de/morgan-stanley-amazon-facebook-google-trillion-dollar-company-2018-2017-11?r=UK&amp;amp;IR=T&quot;&gt;near trillion
dollars worth&lt;/a&gt;
company did not have the time and resources to fix their official extension…&lt;/p&gt;

&lt;h2 id=&quot;firefox-mobile&quot;&gt;Firefox mobile&lt;/h2&gt;

&lt;p&gt;Based on the success on desktop I installed Firefox on my iPhone too. Not that
it makes a big difference on terms of &lt;a href=&quot;https://en.wikipedia.org/wiki/Firefox_for_iOS#Features&quot;&gt;browser
engine&lt;/a&gt;, rather for
going full-on with the Firefox journey.&lt;/p&gt;

&lt;p&gt;No big complaints here, it does the job. The “readability mode” is slightly
inferior to the one of Safari and I never know certain features are under the
top “…” menu or the bottom hamburger one.&lt;/p&gt;

&lt;p&gt;Firefox mobile actually comes in two separate editions the second being Firefox
Focus (branded as &lt;a href=&quot;https://support.mozilla.org/en-US/kb/difference-between-firefox-focus-and-firefox-klar&quot;&gt;Firefox Klar in German speaking
countries&lt;/a&gt;)
for enhanced privacy and ad blocking but I have not had time so far to form an
opinion about it.&lt;/p&gt;

&lt;h2 id=&quot;my-future-with-firefox&quot;&gt;My future with Firefox&lt;/h2&gt;

&lt;p&gt;Looks like Firefox will remain my primary browser on all platforms for the
foreseeable future. I have high hopes that Firefox will remain on the market,
hopefully even gets stronger and Mozilla will &lt;a href=&quot;https://blog.mozilla.org/firefox/update-looking-glass-add/&quot;&gt;not mess it
up&lt;/a&gt;.&lt;/p&gt;

</description>
				<pubDate>Fri, 22 Dec 2017 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/switching-to-firefox.html</link>
				<guid>https://salomvary.com/switching-to-firefox.html</guid>
			</item>
		
			<item>
				<title>How to Continuously Deploy a Front-end Project Using GitLab and Surge</title>
				<description>&lt;p&gt;I recently built a web project from scratch for a client who had no existing
infrastructure for web development. As a believer in &lt;a href=&quot;https://about.gitlab.com/features/gitlab-ci-cd/&quot;&gt;continuous
integration&lt;/a&gt; I decided to go
with automated deployment from the very beginning.&lt;/p&gt;

&lt;p&gt;After a quick research on off the shelf solutions and came up with the
following combination:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Host the source code on &lt;a href=&quot;https://gitlab.com/&quot;&gt;GitLab&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Build and deploy using GitLab’s &lt;a href=&quot;https://about.gitlab.com/features/gitlab-ci-cd/&quot;&gt;continuous integration
pipelines&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Host the web application on &lt;a href=&quot;http://surge.sh/&quot;&gt;Surge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advantage of this setup is the lack of the need for setting up your own
server software while both cloud solutions have free plans to start your project
with. Moreover GitLab’s free plan includes private repositories.&lt;/p&gt;

&lt;p&gt;This blog post will guide you through the first steps of setting up GitLab and
Surge for your own web project.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;It is assumed you know the basics of Git, have a &lt;a href=&quot;https://gitlab.com/users/sign_in&quot;&gt;GitLab
account&lt;/a&gt; set up and &lt;a href=&quot;https://gitlab.com/profile/keys&quot;&gt;configured with your ssh
key&lt;/a&gt; and have the following software
installed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Recent &lt;a href=&quot;https://nodejs.org/en/&quot;&gt;Node.js&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/downloads&quot;&gt;Git&lt;/a&gt; command line&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;setting-up-the-project&quot;&gt;Setting up the project&lt;/h2&gt;

&lt;p&gt;If you don’t have one yet, use your favorite text editor or IDE to create a web
project in an empty folder with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt; at the top level. You can also
copy the html, js and css files from the &lt;a href=&quot;https://gitlab.com/salomvary/frontend-quickstart&quot;&gt;example project I
created&lt;/a&gt; for this post.&lt;/p&gt;

&lt;h2 id=&quot;publishing-the-project-to-gitlab&quot;&gt;Publishing the project to GitLab&lt;/h2&gt;

&lt;p&gt;First you will need a local Git repository. Set one up using the commands
below:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git init
git add index.html main.css main.html
git commit -m &apos;First commit&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next &lt;a href=&quot;https://gitlab.com/projects/new&quot;&gt;create a new project on GitLab&lt;/a&gt; and
publish the local repository with these commands (copy the origin URL from
GitLab):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git remote add origin git@gitlab.com:some-username/my-project.git
git push -u origin --all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;adding-static-web-hosting&quot;&gt;Adding static web hosting&lt;/h2&gt;

&lt;p&gt;We will use &lt;a href=&quot;http://surge.sh/&quot;&gt;Surge&lt;/a&gt; for quick and free static web hosting. It
comes with a command line tool that can be installed using npm. Run these
commands in the root of the project:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm init -y # If you don&apos;t have a package.json yet
npm install --save-dev surge
node_modules/.bin/surge .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first time it’s used Surge prompts for an email and password. Choose a
subdomain you like or let Surge generate a random one. The output should look
like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ node_modules/.bin/surge .

Surge - surge.sh

          email: example@example.com
          token: *****************
   project path: .
           size: 1 files, 57 bytes
         domain: my-awesome-project.surge.sh
         upload: [====================] 100%, eta: 0.0s
propagate on CDN: [====================] 100%
           plan: Free
          users: example@example.com
     IP Address: 45.55.110.124

Success! Project is published and running at my-awesome-project.surge.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Open http://my-awesome-project.surge.sh in your browser to see the published
web project.&lt;/p&gt;

&lt;p&gt;Important: Surge prompts for a subdomain every time you run it, if you want to
stick with one, save it with the project:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo my-awesome-project.surge.sh &amp;gt; CNAME
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the right moment to make a Git commit:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo node_modules &amp;gt;&amp;gt; .gitignore # Avoid commiting Node.js dependencies
git add CNAME package.json package-lock.json .gitignore
git commit -m &apos;Added Surge static hosting&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;setting-up-continuous-deployment&quot;&gt;Setting up continuous deployment&lt;/h2&gt;

&lt;p&gt;GitLab’s continuous integration pipeline can be configured with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt; placed in the root folder of the project. Create one with the
following content:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image: node

before_script:
  - npm install

deploy:
  stage: deploy
  script:
    - node_modules/.bin/surge .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Before pushing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt; to GitLab the pipeline has to be configured
with Surge access credentials so that it can publish to the domain you
configured in the previous step. First obtain a token from Surge:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ node_modules/.bin/surge token

    Surge - surge.sh

              email: me@example.com
              token: *****************
              token: &amp;lt;COPY THE TOKEN FROM HERE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next go to GitLab -&amp;gt; Your project -&amp;gt; Settings -&amp;gt; CI / CD -&amp;gt; Secret variables&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add a key named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SURGE_LOGIN&lt;/code&gt; and set the value to the email you use with
Surge&lt;/li&gt;
  &lt;li&gt;Add a key named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SURGE_TOKEN&lt;/code&gt; and paste the token into the value from the
output of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;surge token&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now commit and push &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt; to GitLab. In a short while you should
see the pipeline running under GitLab -&amp;gt; Your project -&amp;gt; CI / CD -&amp;gt; Pipelines.
If it fails click on the red failed pipeline and the failed job to see the full
terminal output of the deploy job for troubleshooting.&lt;/p&gt;

&lt;p&gt;From now on every change you push to GitLab will automatically be deployed to
Surge.&lt;/p&gt;

&lt;h2 id=&quot;bundling-the-project&quot;&gt;Bundling the project&lt;/h2&gt;

&lt;p&gt;No serious front-end project can get away without a bundling tool nowadays. To
keep things simple I used
&lt;a href=&quot;https://github.com/assetgraph/assetgraph-builder&quot;&gt;AssetGraph-builder&lt;/a&gt;. This
tool has the advantage of taking care of bundling JavaScript, CSS and HTML
files by using an HTML file as an entry point with zero configuration - perfect
for a newly created simple web application.&lt;/p&gt;

&lt;p&gt;Setting up AssetGraph-builder is not more complicated than running these two
commands:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install --save-dev assetgraph-builder
node_modules/.bin/buildProduction --outroot=dist index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check the contents of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt; folder to see the bundled project.&lt;/p&gt;

&lt;p&gt;To do the bundling automatically on GitLab, tweak the contents of
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitlab-ci.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image: node

before_script:
  - npm install

deploy:
  stage: deploy
  script:
    - node_modules/.bin/buildProduction --outroot=dist index.html
    - cp CNAME dist/
    - node_modules/.bin/surge dist
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Commit and push all changes to GitLab to run the pipeline. Don’t forget to add
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist&lt;/code&gt; folder  to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt;. After a short while the bundled project should
be republished to Surge.&lt;/p&gt;

&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;

&lt;p&gt;It should be easy to adopt this approach to your favorite front-end toolchain.
Using &lt;a href=&quot;https://yarnpkg.com/lang/en/&quot;&gt;Yarn&lt;/a&gt; instead of npm or bundling with
&lt;a href=&quot;https://webpack.js.org/&quot;&gt;Webpack&lt;/a&gt; or &lt;a href=&quot;http://browserify.org/&quot;&gt;Browserify&lt;/a&gt; is
not much different from the tools used here.&lt;/p&gt;

&lt;p&gt;A decent software project has automated tests. Fortunately we already have
a pipeline set up with the potential of running our tests. Read the &lt;a href=&quot;https://docs.gitlab.com/ee/ci/&quot;&gt;GitLab CI
documentation&lt;/a&gt; on how to set up advanced
pipelines with multiple jobs and stages.&lt;/p&gt;

&lt;p&gt;The examples above are &lt;a href=&quot;https://gitlab.com/salomvary/frontend-quickstart&quot;&gt;published to a GitLab
project&lt;/a&gt; including &lt;a href=&quot;https://gitlab.com/salomvary/frontend-quickstart/pipelines&quot;&gt;public
access to the
pipeline&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;alternatives&quot;&gt;Alternatives&lt;/h2&gt;

&lt;p&gt;If your project is actually a static web site without the need for a
sophisticated bundling process or automated tests a static site generator
and/or &lt;a href=&quot;https://about.gitlab.com/features/pages/&quot;&gt;GitLab Pages&lt;/a&gt; or &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub
Pages&lt;/a&gt; would do the job.&lt;/p&gt;

&lt;p&gt;There are also alternatives for continuous integration, &lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis
CI&lt;/a&gt; for example is free for public projects.&lt;/p&gt;
</description>
				<pubDate>Fri, 15 Dec 2017 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/front-end-continuous-delivery.html</link>
				<guid>https://salomvary.com/front-end-continuous-delivery.html</guid>
			</item>
		
			<item>
				<title>Getting Started with MapReduce the TDD Way</title>
				<description>&lt;p&gt;Writing and running the first lines of &lt;a href=&quot;https://en.wikipedia.org/wiki/MapReduce&quot;&gt;MapReduce&lt;/a&gt; code for &lt;a href=&quot;http://hadoop.apache.org/&quot;&gt;Hadoop&lt;/a&gt; is
an involved process that often turns beginners away. I will use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;test
driven development&lt;/a&gt; approach to give an introduction to writing MapReduce
jobs and unit and integration tests without even installing a Hadoop server.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;I will use &lt;a href=&quot;https://www.scala-lang.org/&quot;&gt;Scala&lt;/a&gt; and &lt;a href=&quot;http://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt; for the code examples below because I like
conciseness of the language but the approach should be easy to adopt to Java or
any other JVM language.&lt;/p&gt;

&lt;p&gt;To run the code examples the only thing you will have to install is &lt;a href=&quot;http://www.scala-sbt.org/&quot;&gt;sbt&lt;/a&gt;. I
used version 1.0.2. Use your favorite terminal application to launch sbt
commands.&lt;/p&gt;

&lt;p&gt;You will also need a text editor or an IDE with built-in sbt support (I
recommend &lt;a href=&quot;https://www.jetbrains.com/idea/&quot;&gt;IntelliJ IDEA&lt;/a&gt; with &lt;a href=&quot;https://www.jetbrains.com/help/idea/creating-and-running-your-scala-application.html&quot;&gt;Scala plugin&lt;/a&gt;). The examples will assume
you are using sbt from the command line, feel free to use your IDE’s equivalent
of the sbt commands.&lt;/p&gt;

&lt;p&gt;Although they do not have to be installed separately, this tutorial uses Scala
2.12.3 and Hadoop 2.8.1.&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Big data is a hot topic nowadays and &lt;a href=&quot;http://hadoop.apache.org/&quot;&gt;Apache Hadoop&lt;/a&gt; is one of the popular
software in use for storing and processing large data sets. There are many
tutorials on how to get started with MapReduce - Hadoop’s underlying programming
model - but not many of them serve the needs of the impatient developer.
Moreover there is almost no time spent on testing a MapReduce job and Google
search results often suggest using MRUnit, a now defunct and unsupported tool.&lt;/p&gt;

&lt;p&gt;The official MapReduce Tutorial and many other learning material use the “word
count problem” as an example - this tutorial will follow the tradition. The goal
is to count the occurrence of the words in a bunch of text files.&lt;/p&gt;

&lt;p&gt;While I was an engineer &lt;a href=&quot;https://developers.soundcloud.com/blog&quot;&gt;SoundCloud&lt;/a&gt; I had to learn Hadoop the hard way.
Working with seasoned big data engineers meant I had access to a well
maintained cluster and there was always someone to help out solving complicated
problems but material or good advice on making your first steps did not exist.
This experience inspired me to write this post.&lt;/p&gt;

&lt;h2 id=&quot;the-basics-of-mapreduce&quot;&gt;The basics of MapReduce&lt;/h2&gt;

&lt;p&gt;To give you a basic understanding of what Hadoop and MapReduce are, these are the
basics:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Hadoop manages and stores &lt;strong&gt;data in sets of key value pairs&lt;/strong&gt; both of which
can be treated as certain types or simply as binary data.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Mapper&lt;/strong&gt; is a function that takes a key - value pair and returns zero or more
key - value pairs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reducer&lt;/strong&gt; is a function that takes one key and many values (outputs from the
mapper) and returns zero or more key - value pairs.&lt;/li&gt;
  &lt;li&gt;Input and output key and value types might be different for both the mapper
and the reducer but there are a few important rules:
    &lt;ul&gt;
      &lt;li&gt;The input types of the mapper must match the key - value type of the input data.&lt;/li&gt;
      &lt;li&gt;The input types of the reducer must match the output types of the mapper.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep in mind there is a lot more beyond this explanation. This post will not
discuss how Hadoop stores the data in a distributed manner neither how the
mapper and reducer functions are executed. If you want to know more I recommend
starting with the official &lt;a href=&quot;http://hadoop.apache.org/docs/current/&quot;&gt;Hadoop documentation&lt;/a&gt; and the &lt;a href=&quot;http://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html&quot;&gt;MapReduce
Tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-a-project&quot;&gt;Setting up a project&lt;/h2&gt;

&lt;p&gt;To start a new sbt project create an empty folder with a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.sbt&lt;/code&gt;
in it having the following contents:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hadoop-mapreduce-tutorial&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;scalaVersion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2.12.3&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;libraryDependencies&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;org.apache.hadoop&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hadoop-client&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2.8.1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;org.scalatest&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%%&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;scalatest&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;3.0.1&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;org.mockito&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mockito-core&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2.8.47&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Test&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will add the Hadoop Java library and the test frameworks as sbt-managed
dependencies.&lt;/p&gt;

&lt;p&gt;As you might have noticed I use &lt;a href=&quot;http://www.scalatest.org/&quot;&gt;ScalaTest&lt;/a&gt; test framework and the
&lt;a href=&quot;http://mockito.org/&quot;&gt;Mockito&lt;/a&gt; library for managing &lt;a href=&quot;https://martinfowler.com/bliki/TestDouble.html&quot;&gt;test doubles&lt;/a&gt;. Any other framework should
do it, feel free to adopt the code to your favorite one.&lt;/p&gt;

&lt;p&gt;Running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbt test&lt;/code&gt; from the terminal should be successful and print something
like “No tests were executed”.&lt;/p&gt;

&lt;h2 id=&quot;writing-a-mapper&quot;&gt;Writing a mapper&lt;/h2&gt;

&lt;p&gt;To repeat the basics, &lt;em&gt;mapper&lt;/em&gt; is a function that takes a key - value pair and
returns zero or more key - value pairs.&lt;/p&gt;

&lt;p&gt;In our case the &lt;em&gt;input&lt;/em&gt; values are all the lines in the text files, the mapper
will split these into words (tokenize). The &lt;em&gt;output&lt;/em&gt; key will be the word itself
and the value always &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; meaning “this word occurred once”. The &lt;em&gt;input keys&lt;/em&gt; are
ignored in this mapper.&lt;/p&gt;

&lt;p&gt;Let’s create an incomplete implementation in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main/scala/TokenizerMapper.scala&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.io._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.mapreduce._&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenizerMapper&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LongWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LongWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Mapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LongWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// TODO: implement the mapper&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;add-a-unit-test-for-the-mapper&quot;&gt;Add a unit test for the mapper&lt;/h3&gt;

&lt;p&gt;A simple unit test would look like this in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/test/TokenizerMapperSpec.scala&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.io._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.mockito.Mockito._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.scalatest._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.scalatest.mockito.MockitoSugar&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenizerMapperSpec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FlatSpec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MockitoSugar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;s&quot;&gt;&quot;map&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;output the words split on spaces&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;mapper&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenizerMapper&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;mapper.Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;mapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo bar foo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is what happens here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;strong&gt;test subject&lt;/strong&gt; will be an instance of the mapper.&lt;/li&gt;
  &lt;li&gt;The MapReduce framework provides a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt; instance in production. In unit
tests we will pass in a &lt;strong&gt;test double&lt;/strong&gt; instead.&lt;/li&gt;
  &lt;li&gt;Invoke the mapper by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map()&lt;/code&gt; with some test input and the context.&lt;/li&gt;
  &lt;li&gt;Ensure there were calls to context.write() with the given arguments (the
words and 1) using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verify()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbt test&lt;/code&gt; to see the test failing.&lt;/p&gt;

&lt;h3 id=&quot;implement-the-mapper&quot;&gt;Implement the mapper&lt;/h3&gt;

&lt;p&gt;And now we are ready to implement a real mapper, let’s get back to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenizerMapper&lt;/code&gt;. Change the contents to the following:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.io._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.mapreduce._&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenizerMapper&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LongWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;LongWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                   &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Mapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;LongWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;words&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\\s+&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;words&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a rather naive implementation but it should be fine for out exercise.
Verify the tests are passing by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbt test&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;writing-a-reducer&quot;&gt;Writing a reducer&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Reducer&lt;/em&gt; is a function that takes one key and many values (outputs from the
mapper) and returns zero or more key - value pairs.&lt;/p&gt;

&lt;p&gt;The word count reducer will take a word as an input key and the value set will
be as many ones (value &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;) as many times the word occurred. The reducer has to
count the ones to output the total occurrences for the given word.&lt;/p&gt;

&lt;p&gt;A minimal reducer looks like this in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main/scala/IntSumReducer.scala&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntSumReducer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Reducer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                      &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Iterable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt;
                      &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Reducer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// TODO: implement the reducer&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;add-a-unit-test-for-the-reducer&quot;&gt;Add a unit test for the reducer&lt;/h3&gt;

&lt;p&gt;Writing a unit test for the reducer is very similar to how we did it for the
mapper. Add these lines to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/test/IntSumReducerSpec.scala&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.io.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.mockito.Mockito._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.scalatest._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.scalatest.mockito.MockitoSugar&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;scala.collection.JavaConverters._&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntSumReducerSpec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FlatSpec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Matchers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MockitoSugar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;reduce&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sum the values&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;reducer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntSumReducer&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;reducer.Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;reducer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;one&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asJava&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;nf&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;one&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;strong&gt;test subject&lt;/strong&gt; will be an instance of the reducer.&lt;/li&gt;
  &lt;li&gt;The MapReduce framework provides a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Context&lt;/code&gt; instance in production. In unit
tests we will pass in a &lt;strong&gt;test double&lt;/strong&gt; instead.&lt;/li&gt;
  &lt;li&gt;Invoke the reducer by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reduce()&lt;/code&gt; with some test input and the context.&lt;/li&gt;
  &lt;li&gt;Ensure there were calls to context.write() with the given arguments (the
words and 1) using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verify()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbt test&lt;/code&gt; to make sure everything compiles and see the newly added test
failing.&lt;/p&gt;

&lt;h3 id=&quot;implement-the-reducer&quot;&gt;Implement the reducer&lt;/h3&gt;

&lt;p&gt;At this point we are ready to implement an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntSumReducer&lt;/code&gt; that passes the tests:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.lang.Iterable&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.io._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.mapreduce._&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;scala.collection.JavaConverters._&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntSumReducer&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Reducer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                      &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Iterable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt;
                      &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Reducer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;, &lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;asScala&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;sum&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbt test&lt;/code&gt; to see all tests passing.&lt;/p&gt;

&lt;h2 id=&quot;running-the-mapreduce-job&quot;&gt;Running the MapReduce job&lt;/h2&gt;

&lt;p&gt;It is not a widely advertised feature but the Hadoop Java library allows
executing MapReduce jobs locally without any Hadoop server running. This is a
useful step towards learning how to come up with something that can later be
deployed to a production environment and more importantly for our topic:
it allows easy integration testing.&lt;/p&gt;

&lt;p&gt;Running MapReduce jobs with or without a Hadoop cluster requires some glue code
which acts both as an entry point (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; method of the runnable
application) and the wiring that connects inputs with the mapper and reducer and
dumps the output somewhere.&lt;/p&gt;

&lt;p&gt;Let’s use Scala’s built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;App&lt;/code&gt; class to create a simple executable in
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main/scala/WordCount.scala&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.conf.Configuration&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.fs.Path&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.io.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.mapreduce.Job&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.mapreduce.lib.input.FileInputFormat&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.mapreduce.lib.output.FileOutputFormat&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WordCount&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;App&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;conf&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Configuration&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Word Count&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;setJarByClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;TokenizerMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;FileInputFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;addInputPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;setMapperClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;TokenizerMapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;setReducerClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;IntSumReducer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;setOutputKeyClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;setOutputValueClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;IntWritable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;FileOutputFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;setOutputPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;waitForCompletion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What happens here?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Job&lt;/code&gt; instance is created&lt;/li&gt;
  &lt;li&gt;The job is configured with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt; as the input path (relative to the current
working directory)&lt;/li&gt;
  &lt;li&gt;The mapper and the reducer are added to the job&lt;/li&gt;
  &lt;li&gt;Output is set up to a folder named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output&lt;/code&gt; with key and value types
specified&lt;/li&gt;
  &lt;li&gt;The job is started and the application will not exit until the job completes
or fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a lot more to learn about job setup, one important thing to know is the
lack of compile time “compatibility checks”. If the input/output types do not
match somewhere, the job will fail with an exception at some stage of running.&lt;/p&gt;

&lt;h3 id=&quot;running-the-job-locally&quot;&gt;Running the job locally&lt;/h3&gt;

&lt;p&gt;Some test input and a logger configuration is missing before we can run the job
locally. First create a few text files in a folder named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt; under the project root.&lt;/p&gt;

&lt;p&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input/file1&lt;/code&gt; with the following contents:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hello World Bye World
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input/file2&lt;/code&gt; with the following contents:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Hello Hadoop Goodbye Hadoop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To enable verbose logging create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/main/resources/log4j.properties&lt;/code&gt; with
the contents below. This helps tracking job progress and allows debugging
issues.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%nV
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point you are ready to run the application with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbt &apos;runMain
WordCount&apos;&lt;/code&gt;. After a few seconds the job should exit with 0 status and write a
text file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output/part-r-00000&lt;/code&gt;. If you check the contents of the file, it
should look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Bye     1
Goodbye 1
Hadoop  2
Hello   2
World   2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note: the entire &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;output&lt;/code&gt; folder has to be deleted before running the job again,
otherwise it will fail.&lt;/p&gt;

&lt;h2 id=&quot;adding-an-integration-test&quot;&gt;Adding an integration test&lt;/h2&gt;

&lt;p&gt;The above setup is almost good enough for a basic integration test with the
exception that it is not automated. Automating is fairly easy, just create
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/test/scala/WordCountIntegrationSpec.scala&lt;/code&gt; with the contents below.&lt;/p&gt;

&lt;div class=&quot;language-scala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.conf.Configuration&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.hadoop.fs.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;FileSystem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.scalatest._&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;scala.io.Source&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WordCountIntegrationSpec&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FlatSpec&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Matchers&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BeforeAndAfterEach&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;s&quot;&gt;&quot;WordCount&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;write out word counts to output folder&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;WordCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;Source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;fromFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;output/part-r-00000&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;mkString&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;should&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;|Bye	1
         |Goodbye	1
         |Hadoop	2
         |Hello	2
         |World	2
         |&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;stripMargin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;afterEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;fs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;FileSystem&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;output&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; method is invoked “manually” in the same process as the test
runner.&lt;/li&gt;
  &lt;li&gt;Once the job finished, the output file is read to a string and compared
against the expected contents.&lt;/li&gt;
  &lt;li&gt;The test deletes the outputs after each run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should see the integration test passing when running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sbt &apos;testOnly
WordCountIntegrationSpec&apos;&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;From here on you should be able to extend these ideas to build and test more
complicated real life MapReduce jobs. As a next step I recommend going in-depth
with the &lt;a href=&quot;http://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html&quot;&gt;official MapReduce Tutorial&lt;/a&gt; and continue from there to more
detailed sections of the Hadoop documentation.&lt;/p&gt;

&lt;p&gt;Packaging a MapReduce job into a jar file for running it on a real Hadoop
cluster is beyond the scope of this post. If you are using Scala and sbt I
recommend checking out &lt;a href=&quot;https://github.com/sbt/sbt-assembly&quot;&gt;sbt-assembly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also find &lt;a href=&quot;https://github.com/salomvary/hadoop-mapreduce-tutorial&quot;&gt;heavily commented code examples from above in this GitHub
repo&lt;/a&gt;.&lt;/p&gt;

</description>
				<pubDate>Tue, 17 Oct 2017 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/getting-started-with-mapreduce-tdd.html</link>
				<guid>https://salomvary.com/getting-started-with-mapreduce-tdd.html</guid>
			</item>
		
			<item>
				<title>Delivering Applications with Electron Presentation</title>
				<description>&lt;p&gt;There are many steps and obstacles between a working &lt;a href=&quot;http://electron.atom.io&quot;&gt;Electron&lt;/a&gt; application
and people happily downloading and using it on various platforms. I recently
rebuilt &lt;a href=&quot;https://github.com/salomvary/soundcleod&quot;&gt;SoundCleod&lt;/a&gt;, the unofficial SoundCloud desktop player from
Objective-C into an Electron app.&lt;/p&gt;

&lt;p&gt;I had the opportunity to give a talk at the &lt;a href=&quot;http://berlinjs.org/&quot;&gt;Berlin.JS&lt;/a&gt; meetup about
integrating with the framework and shipping an installable application to end
users. &lt;a href=&quot;Delivering_Applications_with_Electron.pdf&quot;&gt;Download the slides for the presentation&lt;/a&gt;.&lt;/p&gt;

</description>
				<pubDate>Mon, 30 Jan 2017 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/delivering-with-electron.html</link>
				<guid>https://salomvary.com/delivering-with-electron.html</guid>
			</item>
		
			<item>
				<title>Level Up On Shell Scripting</title>
				<description>&lt;p&gt;I recently decided to &lt;em&gt;replace myself with a bunch of small shell scripts&lt;/em&gt; in as
many tedious daily routine tasks as I can. But knowing how rusty my scripting
skills were I decided to refresh my knowledge a little bit.&lt;/p&gt;

&lt;p&gt;Here is advice, resources and tools I found useful on this journey:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Try writing portable scripts.&lt;/li&gt;
  &lt;li&gt;Forget Bash, &lt;a href=&quot;http://stackoverflow.com/questions/5725296/difference-between-sh-and-bash&quot;&gt;understand what POSIX shell is&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Watch the &lt;a href=&quot;http://shellhaters.herokuapp.com/&quot;&gt;Shell Haters Handbook video&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Use this &lt;a href=&quot;http://shellhaters.herokuapp.com/posix&quot;&gt;POSIX Reference&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wiki.ubuntu.com/DashAsBinSh&quot;&gt;Dash as /bin/sh&lt;/a&gt; is a useful collection of portability challenges.&lt;/li&gt;
  &lt;li&gt;Install &lt;a href=&quot;http://www.shellcheck.net/about.html&quot;&gt;ShellCheck&lt;/a&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install shellcheck&lt;/code&gt; when using HomeBrew).&lt;/li&gt;
  &lt;li&gt;Add ShellCheck to your editor (automagically works when using
  Vim+&lt;a href=&quot;https://github.com/scrooloose/syntastic&quot;&gt;Syntastic&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;Write tests to verify your scripts. &lt;a href=&quot;https://github.com/bmizerany/roundup&quot;&gt;Roundup&lt;/a&gt; is a simple and useful test
  runner, check &lt;a href=&quot;https://blog.scraperwiki.com/2012/12/how-to-test-shell-scripts/&quot;&gt;this page&lt;/a&gt; out for a more thorough overview of the options.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And what’s next once we mastered shell scripting? Maybe learn &lt;a href=&quot;http://en.wikipedia.org/wiki/AWK&quot;&gt;AWK&lt;/a&gt;…&lt;/p&gt;

</description>
				<pubDate>Wed, 15 Oct 2014 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/level-up-on-shell-scripting.html</link>
				<guid>https://salomvary.com/level-up-on-shell-scripting.html</guid>
			</item>
		
			<item>
				<title>D3.js on SoundCloud Stats Presentation</title>
				<description>&lt;p&gt;During my long stay in San Francisco I took the chance to dive deep into the
local tech communities. I recommend everyone who spends at least a couple of
weeks there to check out meetup.com and visit a couple of local groups - it will
be an interesting experience.&lt;/p&gt;

&lt;p&gt;What I would also strongly recommend is &lt;em&gt;presenting&lt;/em&gt; something at one of those
meetups - which is what I did there. I had the opportunity to give a talk at the
&lt;a href=&quot;http://www.meetup.com/Bay-Area-d3-User-Group/&quot;&gt;Bay Area d3 User Group&lt;/a&gt; on how we rebuilt the new stats pages of SoundCloud
using &lt;a href=&quot;http://d3js.org/&quot;&gt;D3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;How_We_Built_New_SoundCloud_Stats_Using_D3.pdf&quot;&gt;Download the slides&lt;/a&gt; or &lt;a href=&quot;https://www.youtube.com/watch?v=vXIPywh_ZxA&quot;&gt;watch the video&lt;/a&gt;.&lt;/p&gt;

</description>
				<pubDate>Sun, 30 Mar 2014 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/soundcloud-d3-stats-presentation.html</link>
				<guid>https://salomvary.com/soundcloud-d3-stats-presentation.html</guid>
			</item>
		
			<item>
				<title>Introduction to AngularJS</title>
				<description>&lt;p&gt;A while ago I gave a talk at &lt;a href=&quot;http://www.meetup.com/budapest-js/&quot;&gt;budapest.js JavaScript
Meetup&lt;/a&gt; about AngularJS. The slides of my
presentation are available for download here: &lt;a href=&quot;Introducing_AngularJS.pdf&quot;&gt;Introducing
AngularJS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks everyone for coming!&lt;/p&gt;
</description>
				<pubDate>Sat, 05 Oct 2013 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/angularjs-presentation.html</link>
				<guid>https://salomvary.com/angularjs-presentation.html</guid>
			</item>
		
			<item>
				<title>JSHint Everywhere</title>
				<description>&lt;p&gt;What is &lt;a href=&quot;http://www.jshint.com/&quot;&gt;JSHint&lt;/a&gt;? It is JS&lt;strong&gt;L&lt;/strong&gt;int tamed for
humans.&lt;/p&gt;

&lt;p&gt;I always wanted to have JSHint &lt;em&gt;properly&lt;/em&gt; integrated into my development flow
but never took the plunge. Until now. Prerequisites: nodejs installed in your
PATH.&lt;/p&gt;

&lt;h2 id=&quot;install-jshint-cli&quot;&gt;Install JSHint CLI&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://npmjs.org/package/jshint&quot;&gt;command line interface to JSHint&lt;/a&gt; can be
installed via npm:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install -g jshint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you will be able to do this in the shell:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# JSHint can verify recursively every js file in a folder:
$ jshint .

# … or a single file
$ jshint assets/app.js

# to ignore others&apos; problems:
$ jshint assets/!(vendor|require.js)
assets/app.js: line 23, col 1, &apos;console&apos; is not defined.

1 error
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The jshint command searches the current folder and its parents for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.jshintrc&lt;/code&gt;
file and falls back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.jshintrc&lt;/code&gt;. This enables project-level jshint options.
A gitignore-style &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.jshintignore&lt;/code&gt; can be provided to exclude certain files or
folders from syntax check.&lt;/p&gt;

&lt;p&gt;Note: to have the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!()&lt;/code&gt; syntax you might need to enable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;extglob&lt;/code&gt; feature in
bash (see bash manpage).&lt;/p&gt;

&lt;h2 id=&quot;add-jshint-to-vim&quot;&gt;Add JSHint to VIM&lt;/h2&gt;

&lt;p&gt;Install &lt;a href=&quot;https://github.com/scrooloose/syntastic&quot;&gt;syntastic&lt;/a&gt;. Done.&lt;/p&gt;

&lt;p&gt;Syntastic will automatically pick the appropriate syntax checker for most
languages. If you happen to have both JSHint and JSLint installed, syntastic has
to be configured to ue the former. Add this to vimrc:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;g:syntastic_javascript_checker&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;jshint&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;add-jshint-as-a-compiler-to-vim&quot;&gt;Add JSHint as a compiler to VIM&lt;/h2&gt;

&lt;p&gt;Useful for JSHinting whole projects with large number of JavaScript files -
possibly with many errors.&lt;/p&gt;

&lt;p&gt;Install jshint cli (see above) and create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.vim/compiler/jshint.vim&lt;/code&gt; with the
following contents:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;CompilerSet &lt;span class=&quot;nb&quot;&gt;errorformat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;%&lt;span class=&quot;k&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;\ &lt;span class=&quot;nb&quot;&gt;line&lt;/span&gt;\ %&lt;span class=&quot;k&quot;&gt;l&lt;/span&gt;\\&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;\ &lt;span class=&quot;k&quot;&gt;col&lt;/span&gt;\ %&lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;\\&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;\ %&lt;span class=&quot;k&quot;&gt;m&lt;/span&gt;
CompilerSet &lt;span class=&quot;nb&quot;&gt;makeprg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;jshint&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then in VIM comand line:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;compiler&lt;/span&gt; jshint

&lt;span class=&quot;c&quot;&gt;&quot;Validate current folder recursively:&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;make&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;&quot;Validate current file:&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;make&lt;/span&gt; %

&lt;span class=&quot;c&quot;&gt;&quot;Validate selected files:&lt;/span&gt;
jshint assets/&lt;span class=&quot;p&quot;&gt;!(&lt;/span&gt;vendor\&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;require&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You might need to add this line to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; to enable advanced patters in bash:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;k&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/bin/&lt;/span&gt;bash\ &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;O\ extglob&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Make collects errors output the quickfix window (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:copen&lt;/code&gt;). You might want to
use the location list window instead. In this case use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:lmake&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:lopen&lt;/code&gt;. Read more about quickfix/location list window: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:help quickfix.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Credit goes to &lt;a href=&quot;http://jonathan.jsphere.com/post/9163688573/jshint-errors-in-your-vim-quickfix-window&quot;&gt;Jonathan
Jacobs&lt;/a&gt;
for this trick.&lt;/p&gt;

&lt;h2 id=&quot;add-jshint-to-rake-build-pipeline&quot;&gt;Add JSHint to Rake build pipeline&lt;/h2&gt;

&lt;p&gt;It might be useful to automate running JSHint on the codebase before deployment,
from CI or as part of the test phase. Using rake it is as simple as adding this
to Rakefile (replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assets&lt;/code&gt; with the folder to be JSHinted):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:jshint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;jshint&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;assets&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;JSHint check failed&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The downside of this approach is that it requires JSHint cli to be installed. If
that’s not an option, there is a &lt;a href=&quot;https://github.com/stereobooster/jshintrb&quot;&gt;gem called
jshintrb&lt;/a&gt; which a provides ruby
wrapper around JSHint and a Rake task (JavaScript runtime is still required).&lt;/p&gt;

&lt;p&gt;Install:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem install jshintrb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or add this line to Gemfile:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem &quot;jshintrb&quot;, &quot;~&amp;gt; 0.2.1&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add jshint task to Rakefile:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;jshintrb/jshinttask&apos;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Jshintrb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JshintTask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:jshint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;assets/**/*.js&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exclude_pattern&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;assets/{vendor/**/*,require}.js&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;.jshintrc&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cons of JshintTask: does not use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.jshintignore&lt;/code&gt;, neither the same magic for
config lookup as JSHint cli does.&lt;/p&gt;
</description>
				<pubDate>Sat, 24 Nov 2012 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/jshint-everywhere.html</link>
				<guid>https://salomvary.com/jshint-everywhere.html</guid>
			</item>
		
			<item>
				<title>How to Center Horizontally a Position Fixed Element with CSS?</title>
				<description>&lt;p&gt;The common approach is 
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;width:100px; left:50%; margin-left:-50px&lt;/code&gt; which is simple but will only work
with fixed width. Here is how to do it in a more flexible way.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;CSS:&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-css&quot; data-lang=&quot;css&quot;&gt;&lt;span class=&quot;nc&quot;&gt;.container&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
	&lt;span class=&quot;c&quot;&gt;/* fixed position a zero-height full width container */&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;position&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;/* center all inline content */&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.container&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;/* make the block inline */&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inline-block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;c&quot;&gt;/* reset container&apos;s center alignment */&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;text-align&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;…and &lt;strong&gt;HTML:&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;container&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;I will always be at the top center.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Tested on recent Chrome, Firefox, iOS 5, Android 4, IE9. Should work in every
browser that supports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inline-block&lt;/code&gt;. Try it on the
&lt;a href=&quot;examples/centerfixed.html&quot;&gt;demo page&lt;/a&gt;.&lt;/p&gt;
</description>
				<pubDate>Tue, 24 Apr 2012 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/position-fixed-horizontally-centered.html</link>
				<guid>https://salomvary.com/position-fixed-horizontally-centered.html</guid>
			</item>
		
			<item>
				<title>Modular JavaScript, AMD and RequireJS</title>
				<description>&lt;p&gt;I had the opportunity today to give a talk at the &lt;a href=&quot;http://www.meetup.com/budapest-js/&quot;&gt;budapest.js JavaScript
Meetup&lt;/a&gt;. Here are the slides of my
presentation on &lt;a href=&quot;requirejs.html&quot;&gt;Modular JavaScript, AMD and RequireJS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks everyone for coming!&lt;/p&gt;
</description>
				<pubDate>Mon, 12 Mar 2012 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/modular-javascript-amd-requirejs.html</link>
				<guid>https://salomvary.com/modular-javascript-amd-requirejs.html</guid>
			</item>
		
			<item>
				<title>IFRAMEs Automagically Fit Their Content on iOS</title>
				<description>&lt;p&gt;The result of a recent struggle with IFRAMEs on iOS Safari is an interesting
discovery: IFRAMEs can expand to fit their (content documents) content without
any JavaScript. Even cross-domain! There are two important notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scrolling=&quot;no&quot;&lt;/code&gt; attribute to IFRAME disables this behavior.&lt;/li&gt;
  &lt;li&gt;It only works with dynamically changed content: the IFRAME &lt;em&gt;will not fit the
initial content&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;width=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;100&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;height=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;100&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;content.html&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;content.html:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; 
		&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button&quot;&lt;/span&gt;
		&lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;this.parentNode.style.height=&apos;300px&apos;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
		resize
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Click the button and see the magic! Try &lt;a href=&quot;examples/iframe/&quot;&gt;example code
online&lt;/a&gt; from your iOS device.&lt;/p&gt;

&lt;p&gt;Tested on iOS4 and iOS5, does &lt;em&gt;not&lt;/em&gt; work in any other browser I tried. Could not
find any relevant documentation on this behavior except a &lt;a href=&quot;http://stackoverflow.com/a/7771648/759162&quot;&gt;StackOverflow
post&lt;/a&gt;.&lt;/p&gt;
</description>
				<pubDate>Fri, 16 Dec 2011 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/iframe-resize-ios-safari.html</link>
				<guid>https://salomvary.com/iframe-resize-ios-safari.html</guid>
			</item>
		
			<item>
				<title>Getting Started with GitHub Pages and Jekyll</title>
				<description>&lt;p&gt;GitHub Pages are the perfect way for geeks to create personal or project
websites, blogs. GitHub uses &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, a &lt;dfn title=&quot;&apos;It takes a template directory (representing the raw form of a website), runs it through Textile or Markdown and Liquid converters, and spits out a complete, static website suitable for serving with Apache or your favorite web server.&apos;&quot;&gt;static site generator&lt;/dfn&gt;.&lt;/p&gt;

&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;

&lt;p&gt;Install Jekyll if you want to preview the generated pages locally. Ruby and Gem
are the only prerequisites.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem install -v 0.11.0 jekyll
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Optional: install Pygments if you want to test code syntax highlight locally.
See the &lt;a href=&quot;https://github.com/mojombo/jekyll/wiki/install&quot;&gt;Jekyll Install&lt;/a&gt; page
for details. It is recommended to use the &lt;a href=&quot;http://pages.github.com/#using_jekyll_for_complex_layouts&quot;&gt;same Jekyll version as
GitHub&lt;/a&gt; if you want
to push it.&lt;/p&gt;

&lt;h2 id=&quot;create-the-basic-layout-of-your-site&quot;&gt;Create the Basic Layout of Your Site&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; .
 ├── _layouts
 │   └── default.html
 ├── _posts
 │   └── 2011-11-11-hello-world.markdown
 └── index.markdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.html&lt;/code&gt; will hold the HTML code that wraps your content, e.g:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
		&lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Getting Started with GitHub Pages and Jekyll&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
		{{ content}}
	&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Fire up your favorite text editor, open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts/2011-11-11-hello-world.markdown&lt;/code&gt;
and create your first post in your favorite syntax (mine is Markdown). The
post file name &lt;em&gt;must&lt;/em&gt; start with a properly formatted date.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;---
title: My First Post
layout: default
---

## Hello world!&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now you can run Jekyll locally:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jekyll --server --auto
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Open &lt;a href=&quot;http://localhost:4000/2011/11/11/hello-world.html&quot;&gt;http://localhost:4000/2011/11/11/hello-world.html&lt;/a&gt; and you should see the
miracle.&lt;/p&gt;

&lt;p&gt;It is useful to have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt; to list your posts, let’s create
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.markdown&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;---
title: My First Jekyll Blog
layout: default
---

{% for post in site.posts %}
- [{{ post.title }}]({{ post.url }})
{% endfor %}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Open &lt;a href=&quot;http://localhost:4000/&quot;&gt;http://localhost:4000/&lt;/a&gt; and voilà! Now you can add posts, improve the
layout and implement some nice design. The following pages are useful for
learning more about the tools used:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mojombo/jekyll/blob/master/README.textile&quot;&gt;Jekyll README&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mojombo/jekyll/wiki/yaml-front-matter&quot;&gt;Jekyll’s YAML Front
  Matter&lt;/a&gt; is the
  page &lt;strong&gt;metadata&lt;/strong&gt; between the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;---&lt;/code&gt; lines.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/mojombo/jekyll/wiki/template-data&quot;&gt;Jekyll’s Template Data&lt;/a&gt;
  are the &lt;strong&gt;variables&lt;/strong&gt; available in the pages.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/shopify/liquid/wiki/liquid-for-designers&quot;&gt;Learn to use
  Liquid&lt;/a&gt;, the
  &lt;strong&gt;‘macro’ language&lt;/strong&gt; of the pages, 
  and &lt;a href=&quot;https://github.com/mojombo/jekyll/wiki/Liquid-Extensions&quot;&gt;Jekyll’s Liquid
  extensions&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://daringfireball.net/projects/markdown/syntax&quot;&gt;Learn Markdown&lt;/a&gt; or
  whatever &lt;strong&gt;markup syntax&lt;/strong&gt; avaliable to Jekyll you prefer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;push-it-to-github&quot;&gt;Push it to GitHub&lt;/h2&gt;

&lt;p&gt;Once you are happy with the newly created site, you can publish it to GitHub.
Two options are available:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;User/organization pages:&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt; myname.github.com&lt;/code&gt; repository will be available at
  &lt;a href=&quot;http://myname.github.com&quot;&gt;http://myname.github.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Project pages: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myproject&lt;/code&gt; repository’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch will be available at
  &lt;a href=&quot;http://myname.github.com/myproject&quot;&gt;http://myname.github.com/myproject&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More instructions can be found at the &lt;a href=&quot;http://pages.github.com/&quot;&gt;GitHub Pages
Introduction&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;tips-and-tricks&quot;&gt;Tips and Tricks&lt;/h2&gt;

&lt;h3 id=&quot;use-baseurl&quot;&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;baseurl&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;If you are creating project pages, every url has to be prefixed with the project
name, e.g:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/myproject{{ post.url }}&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;{{ post.title }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It can even be made configurable:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{{ site.baseurl }}{{ post.url }}&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;{{post.title }}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;baseurl: /test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Override this with Jekyll’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--base-url [url]&lt;/code&gt; command line switch locally if
you like so (e.g. to serve from the root).&lt;/p&gt;

&lt;h3 id=&quot;use-html5-in-pages-or-layouts&quot;&gt;Use HTML5 in Pages or Layouts&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://maruku.rubyforge.org/maruku.html&quot;&gt;Maruku&lt;/a&gt; understands HTML5 tags. Add
this to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;markdown: maruku
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
				<pubDate>Thu, 03 Nov 2011 00:00:00 +0000</pubDate>
				<link>https://salomvary.com/jekyll-gh-pages-getting-started.html</link>
				<guid>https://salomvary.com/jekyll-gh-pages-getting-started.html</guid>
			</item>
		
	</channel>
</rss>
