<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Pascal Widdershoven</title>
    <description>The latest articles on Forem by Pascal Widdershoven (@pascalw).</description>
    <link>https://forem.com/pascalw</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F190873%2Fa43ae9da-dd9d-4444-92e0-f132d7792e85.jpg</url>
      <title>Forem: Pascal Widdershoven</title>
      <link>https://forem.com/pascalw</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pascalw"/>
    <language>en</language>
    <item>
      <title>Django &amp; Webpack - without any plugins </title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Mon, 20 Apr 2020 05:31:25 +0000</pubDate>
      <link>https://forem.com/pascalw/django-webpack-without-any-plugins-94p</link>
      <guid>https://forem.com/pascalw/django-webpack-without-any-plugins-94p</guid>
      <description>&lt;p&gt;This post explores setting up Webpack in a Django project with minimal friction. The defacto solution for this, &lt;code&gt;django-webpack-loader&lt;/code&gt;, is too heavy handed in my opinion. This post aims to provide a guide to setup Webpack in Django, without any plugins and leveraging both Webpack's and Django's strengths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Django-webpack-loader
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/owais/django-webpack-loader"&gt;django-webpack-loader&lt;/a&gt; seems to be the defacto way of setting up Webpack in Django projects. I tried using it but didn't like that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It requires a &lt;a href="https://github.com/owais/webpack-bundle-tracker"&gt;non-standard manifest plugin&lt;/a&gt; (that was also buggy for me).&lt;/li&gt;
&lt;li&gt;It provides a set of custom Django template helpers, instead of levering the excellent Django built-in &lt;code&gt;static&lt;/code&gt; functionality.&lt;/li&gt;
&lt;li&gt;It required a bunch of configuration in Django, to make the custom helpers work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It feels like a very heavy handed solution requiring both a Webpack plugin and a Django plugin.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: While I'm criticizing django-webpack-loader here this is not meant to knock on the hard work that the maintainer(s) have undoubtedly put into this.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Vanilla Django &amp;amp; Webpack
&lt;/h3&gt;

&lt;p&gt;Making Django and Webpack play nice together turns out to be pretty straightforward, without needing any Webpack or Django plugins.&lt;/p&gt;

&lt;p&gt;Django has &lt;a href="https://docs.djangoproject.com/en/3.0/howto/static-files/"&gt;built-in support&lt;/a&gt; for handling static assets. Django can serve static assets in development and compress files and hash filenames during production deployment. Webpack can do this too (and much more), but it's much more convenient to let Webpack only worry about &lt;em&gt;producing&lt;/em&gt; your assets and Django about &lt;em&gt;handling&lt;/em&gt; your assets.&lt;/p&gt;

&lt;p&gt;In practise this works as follows:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Use Django's default Static support
&lt;/h4&gt;

&lt;p&gt;Refer to the &lt;a href="https://docs.djangoproject.com/en/3.0/howto/static-files/"&gt;Django docs&lt;/a&gt; for details. Important settings are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;STATICFILES_DIRS&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;STATIC_URL&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;STATICFILES_STORAGE&lt;/code&gt; - Use &lt;code&gt;ManifestStaticFilesStorage&lt;/code&gt; to let Django hash filenames for cachability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Configure Webpack to write files to  &lt;code&gt;STATICFILES_DIRS&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;You can use any Webpack configuration you like. Only a couple of settings are important:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Have Webpack write to a directory in &lt;code&gt;STATICFILES_DIRS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;output.publicPath&lt;/code&gt; to match Django's &lt;code&gt;STATIC_URL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't&lt;/strong&gt; let Webpack hash filenames (except for chunks, see below)&lt;/li&gt;
&lt;li&gt;Make sure &lt;code&gt;webpack-dev-server&lt;/code&gt; writes files to disk (so Django can serve them in development)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Relevant parts of webpack.config.js&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;myapp/static&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Should be in STATICFILES_DIRS&lt;/span&gt;
  &lt;span class="nx"&gt;publicPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/static/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Should match Django STATIC_URL&lt;/span&gt;
  &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[name].js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// No filename hashing, Django takes care of this&lt;/span&gt;
  &lt;span class="nx"&gt;chunkFilename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[id]-[chunkhash].js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// DO have Webpack hash chunk filename, see below&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="nx"&gt;devServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;writeToDisk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Write files to disk in dev mode, so Django can serve the assets&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, during development you run both &lt;code&gt;webpack-dev-server&lt;/code&gt; and the Django server. You can use a  Procfile and a tool like &lt;a href="https://github.com/mattn/goreman"&gt;Goreman&lt;/a&gt; to conveniently do this without having to open two terminals. Webpack writes files into &lt;code&gt;STATICFILES_DIRS&lt;/code&gt; and Django serves the files.&lt;/p&gt;

&lt;p&gt;To include an asset produced by Webpack you use the Django &lt;a href="https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#std:templatetag-static"&gt;&lt;code&gt;static&lt;/code&gt;&lt;/a&gt; template tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;{% load static %}
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{% static 'app.css' %}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"{% static 'app.js' %}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For production deployments you first have Webpack build your assets and then use &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/staticfiles/#django-admin-collectstatic"&gt;manage.py collectstatic&lt;/a&gt; as usual. Presto! Just plain Django and Webpack, nice and simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webpack chunks (dynamic imports)
&lt;/h3&gt;

&lt;p&gt;As I mentioned above it's important to do let Webpack hash chunk filenames. By default Webpack uses numeric chunk ids to refer to chunks at runtime. When Django's &lt;code&gt;collectstatic&lt;/code&gt; runs, it hashes the filenames and then looks for unhashed references in other files to replace them.&lt;/p&gt;

&lt;p&gt;For example if you have a stylesheet that refers to a file &lt;code&gt;foo.jpg&lt;/code&gt; and Django renames &lt;code&gt;foo.jpg&lt;/code&gt; to &lt;code&gt;foo.695e1b313f34.jpg&lt;/code&gt;, it will replace the &lt;code&gt;foo.jpg&lt;/code&gt; reference in the stylesheet with &lt;code&gt;foo.695e1b313f34.jpg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Webpack at runtime refers to chunks by their numeric &lt;code&gt;chunk id&lt;/code&gt; by default and as such &lt;code&gt;collectstatic&lt;/code&gt; will not correctly recognize the chunk filenames. By letting Webpack hash the chunk filenames we get properly hashed chunk filenames, so these files can be cached properly by the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example project
&lt;/h3&gt;

&lt;p&gt;I've created a demo project for this setup on GitHub &lt;a href="https://github.com/pascalw/django-webpack-boilerplate"&gt;here&lt;/a&gt; so you can see it in action. It's also deployed on Heroku &lt;a href="https://django-webpack.herokuapp.com/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If this was useful to you I'd love to hear from you!&lt;/p&gt;

</description>
      <category>django</category>
      <category>webpack</category>
    </item>
    <item>
      <title>Kubernetes workshop in a box</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Thu, 09 Apr 2020 08:26:05 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/kubernetes-workshop-in-a-box-37pn</link>
      <guid>https://forem.com/kabisasoftware/kubernetes-workshop-in-a-box-37pn</guid>
      <description>&lt;p&gt;We recently organised an internal Kubernetes workshop at our office. Each participant got their own 3-node Kubernetes cluster and all they needed was a laptop with an SSH client, no other software had to be installed! How? Read on to find out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why
&lt;/h2&gt;

&lt;p&gt;I wanted participants to have a real cluster to experiment with, but didn't want to ask everyone to install Minikube. It's heavy weight, can be a bit of a hassle to setup and I just didn't want people to have to mess with stuff like this. Workshops should have a low as possible barrier to entry.&lt;/p&gt;

&lt;p&gt;To pull this off I used &lt;a href="https://kind.sigs.k8s.io/"&gt;Kind&lt;/a&gt; (Kubernetes In Docker) + a bit of scripting and Unix magic! 💪&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The setup works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;single&lt;/strong&gt;, beefy cloud server is used to host all Kubernetes clusters.&lt;/li&gt;
&lt;li&gt;Kind creates true Kubernetes clusters inside Docker containers. This way, each cluster is fully isolated from the other clusters and clusters can run side-by-side on a single host machine. Every cluster consists of one master and two worker nodes.&lt;/li&gt;
&lt;li&gt;The clusters are (automatically) provisioned in advance to the workshop, as creating the clusters is quite resource intensive and takes a couple of minutes per cluster.&lt;/li&gt;
&lt;li&gt;During the workshop participants can claim their own cluster via SSH and then access their private cluster via a new SSH session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can see the process from a participant perspective in action below:&lt;/p&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/8v4g74v8oew0/tvE1HoF8b7N7QoFARJ6xd/6c8844b64ac82f4c890a0268afce8161/demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/8v4g74v8oew0/tvE1HoF8b7N7QoFARJ6xd/6c8844b64ac82f4c890a0268afce8161/demo.gif" alt="GIF Kubernetes Workshop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware &amp;amp; costs
&lt;/h2&gt;

&lt;p&gt;A Kind cluster is quite resource intensive, though in part it depends on the workloads your workshop participants are running of course. For our workshop with 15 participants we used an AWS &lt;code&gt;m5.8xlarge&lt;/code&gt; machine. This machine type has 32 VCPU's and 128GB of RAM. It costs $1,712 per hour (in &lt;code&gt;eu-west-1&lt;/code&gt;) and we needed it only for about 3 hours so that's about $5 total 🙂.&lt;/p&gt;

&lt;p&gt;In hindsight, this machine was way over provisioned for this number of clusters, so you could go even cheaper 😀&lt;/p&gt;

&lt;h2&gt;
  
  
  You can use this too to run your own Kubernetes workshops!
&lt;/h2&gt;

&lt;p&gt;We've open-sourced the tooling we built to do this here: &lt;a href="https://github.com/kabisa/k8s-workshop-in-a-box"&gt;https://github.com/kabisa/k8s-workshop-in-a-box&lt;/a&gt;. Feel free to use it and if you do please reach out to me I'd love to hear from you! ❤️&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
      <category>workshop</category>
    </item>
    <item>
      <title>Localizing your app with Flutter's new gen_l10n tool</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Sun, 08 Mar 2020 23:00:00 +0000</pubDate>
      <link>https://forem.com/pascalw/localizing-your-app-with-flutter-s-new-genl10n-tool-454k</link>
      <guid>https://forem.com/pascalw/localizing-your-app-with-flutter-s-new-genl10n-tool-454k</guid>
      <description>&lt;p&gt;I'm recently getting into Flutter so I'm still figuring out some common practises. I was surprised to find that the &lt;a href="https://flutter.dev/docs/development/accessibility-and-localization/internationalization#defining-a-class-for-the-apps-localized-resources"&gt;recommended approach&lt;/a&gt; for internationalization was quite cumbersome and laborious.&lt;/p&gt;

&lt;p&gt;Fortunately, the Flutter team is working on a &lt;a href="https://github.com/flutter/flutter/issues/41437"&gt;simplified i18n process&lt;/a&gt; that can already be used today and in this post I'll show you how!&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed new i18n process
&lt;/h2&gt;

&lt;p&gt;The proposed workflow for this new i18n process is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Start with handwritten &lt;code&gt;.arb&lt;/code&gt; files. One of these files will contain the standard per-message metadata required by the &lt;a href="https://pub.dev/packages/intl"&gt;intl package&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A new tool (&lt;code&gt;gen_l10n&lt;/code&gt;) will be used to generate a single class that contains one method per message.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Message lookup is based on the inherited locale, using the current &lt;code&gt;BuildContext&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;MyLocalizations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;myMessage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Compared to some other solutions available on &lt;a href="https://pub.dev"&gt;https://pub.dev&lt;/a&gt; this has a few advantages in my opinion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since this solution uses code generation, the compiler will help you use your localized messages correctly. This also means that your IDE can autocomplete translations keys.&lt;/li&gt;
&lt;li&gt;It's based on the official &lt;a href="https://pub.dev/packages/intl"&gt;intl package&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;It uses a pretty versatile localization format (ARB).&lt;/li&gt;
&lt;li&gt;It's an official solution encouraged by the Flutter team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the following section I'll describe how to adopt this workflow for an existing project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adopt gen_l10n for an existing project
&lt;/h2&gt;

&lt;p&gt;To get started we need a few new dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add gen_l10n dependencies
&lt;/h3&gt;

&lt;p&gt;The code generated by &lt;code&gt;gen_l10n&lt;/code&gt; depends on the &lt;code&gt;intl&lt;/code&gt; and &lt;code&gt;flutter_localizations&lt;/code&gt; package, so add these to your &lt;code&gt;dependencies&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pubspec.yaml&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ... other dependencies ...&lt;/span&gt;
  &lt;span class="na"&gt;flutter_localizations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter&lt;/span&gt;
  &lt;span class="na"&gt;intl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.16.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;gen_l10n&lt;/code&gt; tool itself depends on &lt;code&gt;intl_translation&lt;/code&gt;, so add this as a development dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pubspec.yaml&lt;/span&gt;
&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ... other dev dependencies ...&lt;/span&gt;
  &lt;span class="na"&gt;intl_translation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^0.17.9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Define translations
&lt;/h3&gt;

&lt;p&gt;Ok, so now we have all dependencies we need, let's start by defining some localized messages.&lt;/p&gt;

&lt;p&gt;Create a directory in your project to store your localized messages. This can be any directory you like, I'm using &lt;code&gt;lib/i18n&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;lib/i18n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In this directory create a file called &lt;code&gt;messages_en_US.arb&lt;/code&gt;. The name &lt;code&gt;messages&lt;/code&gt; here can be anything you want, but the last part of the file name should refer to the locale of the messages inside.&lt;/p&gt;

&lt;p&gt;As you can see this file is an ARB file, an &lt;code&gt;Application Resource Bundle&lt;/code&gt;. It's a localization resource format designed by Google. You can read the specification &lt;a href="https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now to define localised messages we'll start by adding the messages we want to localize to the &lt;code&gt;messages_en_US.arb&lt;/code&gt; file we created earlier. I won't dive into everything the ARB format supports, but it's a pretty versatile format so be sure to read the specification I linked above!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"greeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@greeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A friendly greeting."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"newMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{newMessages, plural, =0 {No new messages} =1 {One new message} other {{newMessages} new messages}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@newMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Number of new messages in inbox."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"placeholders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"newMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see for every message we want to localize we add both a &lt;code&gt;key&lt;/code&gt; and a &lt;code&gt;meta key&lt;/code&gt; to the file. The &lt;code&gt;key&lt;/code&gt; is how you will refer to the localized message from your code later and the &lt;code&gt;meta key&lt;/code&gt; is that same key prefixed with &lt;code&gt;@&lt;/code&gt;. These &lt;code&gt;meta keys&lt;/code&gt; only need to be added in one of the locales; the locale you will use as a template.&lt;/p&gt;

&lt;p&gt;In the example above we have both a simple message &lt;code&gt;greeting&lt;/code&gt; and a more complex, pluralized, message that changes depending on a piece of &lt;code&gt;context&lt;/code&gt;'; the number of new messages in this case.&lt;/p&gt;

&lt;p&gt;Now for every other language we want to support we need to create matching files with the desired locale in the &lt;code&gt;lib/i18n&lt;/code&gt; directory we created earlier. For example I'm going to translate these messages in Dutch so I'll add a file named &lt;code&gt;messages_nl_NL.arb&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"greeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hallo, wereld!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newMessages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{newMessages, plural, =0 {Geen nieuwe berichten} =1 {Één nieuw bericht} other {{newMessages} nieuwe berichten}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see in this case the file only contains the keys themselves and no metadata.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate localization classes
&lt;/h3&gt;

&lt;p&gt;With everything prepared we can now use the &lt;code&gt;gen_l10n&lt;/code&gt; tool to generate the Dart classes we'll use to access these localized messages in our code.&lt;/p&gt;

&lt;p&gt;As the &lt;code&gt;gen_l10n&lt;/code&gt; is still experimental it's not yet distributed as a package on the package registry. Instead it's bundled with the Flutter SDK itself. The tool is located in the Flutter SDK at &lt;code&gt;dev/tools/localization/gen_l10n.dart&lt;/code&gt;. In my case the Flutter SDK is installed in &lt;code&gt;/Users/Pascal/.asdf/installs/flutter/1.12.13+hotfix.8-stable/&lt;/code&gt; so the full path to the tool is &lt;code&gt;/Users/Pascal/.asdf/installs/flutter/1.12.13+hotfix.8-stable/dev/tools/localization/gen_l10n.dart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From your terminal, run the &lt;code&gt;gen_l10n&lt;/code&gt; tool from the root of your Flutter project as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter pub run &lt;span class="s2"&gt;"/path/to/flutter/sdk/dev/tools/localization/gen_l10n.dart"&lt;/span&gt; &lt;span class="nt"&gt;--arb-dir&lt;/span&gt; lib/i18n &lt;span class="nt"&gt;--template-arb-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;messages_en_US.arb &lt;span class="nt"&gt;--output-localization-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;translations.dart &lt;span class="nt"&gt;--output-class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Translations
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;(Be sure to replace &lt;code&gt;/path/to/flutter/sdk&lt;/code&gt; above with the actual path to your Flutter SDK!)&lt;/p&gt;

&lt;p&gt;This will generate a couple of files in &lt;code&gt;lib/i18n&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;lib/i18n
├── messages_all.dart
├── messages_en_US.dart
├── messages_nl_NL.dart
├── messages_en_US.arb
├── messages_nl_NL.arb
└── translations.dart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;All these files should be checked into version control. The only files you manually edit, however, are the ARB files. Don't touch the generated Dart files :-)&lt;/p&gt;

&lt;h3&gt;
  
  
  Make localized messages available via BuildContext
&lt;/h3&gt;

&lt;p&gt;To make Flutter aware of our localized messages we need to configure the &lt;code&gt;localizationsDelegates&lt;/code&gt; and &lt;code&gt;supportedLocales&lt;/code&gt; properties on the &lt;code&gt;MaterialApp&lt;/code&gt; widget.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;lib/main.dart&lt;/code&gt; file import the generated &lt;code&gt;translations&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:hello_world/i18n/translations.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And configure the &lt;code&gt;MaterialApp&lt;/code&gt; widget to pick up your translations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// ... other properties ...&lt;/span&gt;
  &lt;span class="nl"&gt;localizationsDelegates:&lt;/span&gt; &lt;span class="n"&gt;Translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;localizationsDelegates&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;supportedLocales:&lt;/span&gt; &lt;span class="n"&gt;Translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;supportedLocales&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Use localized messages
&lt;/h3&gt;

&lt;p&gt;With all these out of the way, we can now actually use our localized messages.&lt;/p&gt;

&lt;p&gt;From any widget in the widget tree below &lt;code&gt;MaterialApp&lt;/code&gt; you can now access your localized messages.&lt;/p&gt;

&lt;p&gt;Start by importing the generated &lt;code&gt;translations.dart&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:hello_world/i18n/translations.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And use the messages defined in the ARB files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyLocalizedWidget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;[&lt;/span&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;greeting&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// "greeting"&lt;/span&gt;
        &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Translations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;newMessages&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// "newMessages"&lt;/span&gt;
      &lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The compiler will check you're using your messages correctly and the IDE will help you with great auto completion:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ctAoB9mu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pascalw.me/img/2020-03-09-localizing-your-app-with-flutters-new-gen-l10n-tool/autocomplete.22dc8ae10b86e8fa7920046acb403226.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ctAoB9mu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pascalw.me/img/2020-03-09-localizing-your-app-with-flutters-new-gen-l10n-tool/autocomplete.22dc8ae10b86e8fa7920046acb403226.png" alt="autocomplete"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy &lt;code&gt;i18n&lt;/code&gt;-ing! 🎉&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobile</category>
      <category>i18n</category>
    </item>
    <item>
      <title>Extending Flutter Driver with custom commands</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Mon, 17 Feb 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/extending-flutter-driver-with-custom-commands-2bm3</link>
      <guid>https://forem.com/kabisasoftware/extending-flutter-driver-with-custom-commands-2bm3</guid>
      <description>&lt;p&gt;&lt;a href="https://flutter.dev/docs/cookbook/testing/integration/introduction"&gt;Flutter Driver&lt;/a&gt; is a library to write end-to-end integration tests for Flutter apps. It’s similar to Selenium WebDriver (for web apps), Espresso (for native Android apps) and Earl Grey (for native iOS apps). It works by instrumenting the Flutter app, deploying it on a real device or emulator and then ‘driving’ the application using a suite of Dart &lt;a href="https://pub.dev/packages/test"&gt;tests&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A typical, basic Flutter Driver test looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_driver/flutter_driver.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:test/test.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Counter App'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;counterTextFinder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byValueKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'counter'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;buttonFinder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;byValueKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'increment'&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;FlutterDriver&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Connect to the Flutter driver before running any tests.&lt;/span&gt;
    &lt;span class="n"&gt;setUpAll&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterDriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Close the connection to the driver after the tests have completed.&lt;/span&gt;
    &lt;span class="n"&gt;tearDownAll&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'starts at 0'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counterTextFinder&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'increments the counter'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buttonFinder&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

      &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counterTextFinder&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;setUpAll&lt;/code&gt; hook a connection between the test and the running application is setup via the Flutter Driver API. This works because the application is instrumented with a Flutter Driver extension; basically an API injected into your app that can receive requests from our tests to “drive” the application.&lt;/p&gt;

&lt;p&gt;The instrumentation of the Flutter app works by wrapping your app’s &lt;code&gt;main&lt;/code&gt; function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_driver/driver_extension.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:my_app/main.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;enableFlutterDriverExtension&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- ENABLE INSTRUMENTATION&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Flutter Driver supports a handful of API’s to communicate with the running app. For example &lt;code&gt;getText&lt;/code&gt;, &lt;code&gt;tap&lt;/code&gt;, &lt;code&gt;waitFor&lt;/code&gt; etc. For me, coming from &lt;a href="http://nightwatchjs.org/"&gt;Nightwatch.js&lt;/a&gt;, the number of things that can be done to drive the application is quite limited.&lt;/p&gt;

&lt;p&gt;Fortunately it’s possible to extend Flutter Driver to support custom commands. These commands allow you to communicate between your tests and the application and are also the foundation for all of Flutter Driver’s own API’s like &lt;code&gt;getText&lt;/code&gt;, &lt;code&gt;tap&lt;/code&gt; etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extending Flutter Driver
&lt;/h3&gt;

&lt;p&gt;To extend Flutter Driver with a custom command we need to provide a &lt;a href="https://api.flutter.dev/flutter/flutter_driver_extension/DataHandler.html"&gt;&lt;code&gt;DataHandler&lt;/code&gt;&lt;/a&gt;. As the docs say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Optionally you can pass a [DataHandler] callback. It will be called if the
test calls [FlutterDriver.requestData].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Flutter Driver will pass whatever is sent from test with &lt;code&gt;driver.requestData(...)&lt;/code&gt; to the DataHandler. DataHandler only supports sending and receiving Strings, so you might want to encode your messages using JSON.&lt;/p&gt;

&lt;p&gt;To demonstrate this, let’s implement a handler to navigate back to the root route of our app. This way we can ensure that every test starts from the root page of our application.&lt;/p&gt;

&lt;p&gt;The first step is to provide a &lt;code&gt;DataHandler&lt;/code&gt; to Flutter Driver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;enableFlutterDriverExtension&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;handler:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The handler will receive a String payload and can optionally return a String response.&lt;/p&gt;

&lt;p&gt;For the sake of simplicity let’s use a String as payload for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;enableFlutterDriverExtension&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;handler:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"navigate_to_root"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do something smart here&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;From here, we need to implement something that will allow us to navigate to the root of our app. I’m not sure if the following is necessarily the best way to do this (if you know a better way please let me know!), but it works and is relatively straightforward.&lt;/p&gt;

&lt;p&gt;We’ll use a &lt;a href="https://api.flutter.dev/flutter/widgets/NavigatorObserver-class.html"&gt;NavigationObserver&lt;/a&gt; to get a hold of the &lt;a href="https://api.flutter.dev/flutter/widgets/NavigatorState-class.html"&gt;NavigatorState&lt;/a&gt;, which we can use to push and pop routes. We need to be able to pass in a NavigationObserver from our test entry point, so we can access it when we receive a command to navigate to root.&lt;/p&gt;

&lt;p&gt;Change the &lt;code&gt;main&lt;/code&gt; function of your app as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;mainTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NavigatorObserver&lt;/span&gt; &lt;span class="n"&gt;navigatorObserver&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;_main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navigatorObserver&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NavigatorObserver&lt;/span&gt; &lt;span class="n"&gt;navigatorObserver&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;navigatorObserver:&lt;/span&gt; &lt;span class="n"&gt;navigatorObserver&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
  &lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatelessWidget&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;NavigatorObserver&lt;/span&gt; &lt;span class="n"&gt;navigatorObserver&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;navigatorObserver&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MaterialApp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;// all other MaterialApp initialisation here&lt;/span&gt;
      &lt;span class="nl"&gt;navigatorObservers:&lt;/span&gt; &lt;span class="n"&gt;navigatorObserver&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;navigatorObserver&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This allows us to hook up a NavigationObserver from our test wrapper like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter_driver/driver_extension.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:my_app/main.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;navigationObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NavigatorObserver&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;enableFlutterDriverExtension&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;handler:&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"navigate_to_root"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;navigationObserver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;navigator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;popUntil&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModalRoute&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;

  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mainTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;navigationObserver&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now from our tests we can send our custom command, for example in a &lt;code&gt;setUp&lt;/code&gt; hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;FlutterDriver&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;setUpAll&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;FlutterDriver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;

  &lt;span class="n"&gt;tearDownAll&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;

  &lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;await&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requestData&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"navigate_to_root"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;   

  &lt;span class="cm"&gt;/* Actual tests here */&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will make sure that before every test, the app navigates back to the root route no matter where we navigated to in our tests.&lt;/p&gt;

&lt;p&gt;Of course, this is just an example of how you can implement communication between your Driver tests and your app. If you’re going to send more complex commands that require arguments you might want to send JSON data, but I’ll leave that as an exercise to you, dear reader ;-)&lt;/p&gt;

</description>
      <category>flutterdriver</category>
      <category>flutter</category>
      <category>driver</category>
    </item>
    <item>
      <title>Generating CSS from scratch with PostCSS</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Mon, 06 Jan 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/generating-css-from-scratch-with-postcss-1j7m</link>
      <guid>https://forem.com/kabisasoftware/generating-css-from-scratch-with-postcss-1j7m</guid>
      <description>&lt;p&gt;For a soon-to-be open-sourced project I had to generate a CSS stylesheet based on user input.&lt;/p&gt;

&lt;p&gt;Initially I started out with good ‘ol string templating and interpolation but it soon became pretty complex, as I needed to conditionally add certain properties and declarations.&lt;/p&gt;

&lt;p&gt;It occurred to me that what I wanted was a representation of the CSS structure in code, an Abstract Syntax Tree (AST). That would allow me to build up the CSS tree structure in code and turn it into a string later on.&lt;/p&gt;

&lt;p&gt;I decided to see if I could use &lt;a href="https://postcss.org/"&gt;PostCSS&lt;/a&gt;, since I figured it must be turning CSS into an AST already.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostCSS
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://postcss.org/"&gt;PostCSS&lt;/a&gt; is “A tool for transforming CSS with JavaScript”. It’s widely used in the industry for things like &lt;a href="https://autoprefixer.github.io/"&gt;auto-prefixing CSS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As the tag line says, PostCSS main business is &lt;em&gt;transforming&lt;/em&gt; CSS, not generating it from scratch as I wanted to do. Looking at the PostCSS codebase I noticed &lt;a href="https://github.com/postcss/postcss/blob/master/test/postcss.test.js#L116"&gt;a test&lt;/a&gt; named &lt;code&gt;it allows to build own CSS&lt;/code&gt; so I figured it should be possible!&lt;/p&gt;

&lt;p&gt;Fast forward a couple of hours diving through the PostCSS codebase, reading the API docs and several PostCSS plugins, I had a working solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating CSS from scratch
&lt;/h3&gt;

&lt;p&gt;To generate CSS with PostCSS you first need to build up an AST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

const postcss = require("postcss");

const fontFamily = "My Family";
const root = postcss.root();

const bodyRule = postcss.rule({ selector: "body" }).append(
  postcss.decl({
    prop: "font-family",
    value: `"${fontFamily}"`
  })
);

const fontFace = postcss.atRule({ name: "font-face" }).append([
   postcss.decl({ prop: "font-family", value: `"${fontFamily}"` }),
   postcss.decl({ prop: "src", value: "url(./fonts/myfont.woff2)" })
]);

root.append([
  fontFace,
  bodyRule
]);

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

&lt;/div&gt;



&lt;p&gt;The CSS code can then be generated from the CSS with &lt;code&gt;root.toString()&lt;/code&gt;, resulting in this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1
2
3
4
5
6
7

@font-face {
    font-family: "My Family";
    src: url(./fonts/myfont.woff2)
}
body {
    font-family: "My Family"
}

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

&lt;/div&gt;



&lt;p&gt;A couple of things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostCSS does not provide much help in generating proper declaration values &lt;a href="https://github.com/TrySound/postcss-value-parser"&gt;out of the box&lt;/a&gt;. For example quoting a &lt;code&gt;font-family&lt;/code&gt; value in case it contains multiple words is not handled by PostCSS automatically.&lt;/li&gt;
&lt;li&gt;It’s easy to conditionally generate properties, rules, declarations etc. The API follows a typical builder pattern, which makes it easy to conditionally call &lt;code&gt;append&lt;/code&gt;, or not.&lt;/li&gt;
&lt;li&gt;The CSS output is opinionated. There are no semicolons after the last declaration and no newlines between rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fortunately, PostCSS is architected quite well and allows you to provide your own &lt;a href="http://api.postcss.org/global.html#stringifier"&gt;“Stringifier”&lt;/a&gt;. I didn’t find much documentation or guidance on this though, but after a bit of code diving I settled on this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

const Stringifier = require("postcss/lib/stringifier");

class PrettyStringifier extends Stringifier {
  static new() {
    return (node, builder) =&amp;gt; {
      let str = new this(builder);
      str.stringify(node);
    };
  }

  constructor(builder) {
    super(builder);
  }

  rule(node) {
    if (node.prev()) {
      // Add a newline after a rule, if it's preceded by another rule.
      this.builder("\n", node);
    }

    return super.rule(node);
  }

  decl(node) {
    return super.decl(node, true /* force semicolon */);
  }
}

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

&lt;/div&gt;



&lt;p&gt;As you can see, this is inheriting most of the default behaviour except around rules and declarations.&lt;/p&gt;

&lt;p&gt;The custom &lt;code&gt;Stringifier&lt;/code&gt; can be used like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1

root.toString(PrettyStringifier.new());

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;All in all I achieved what I had to do, but it didn’t feel like PostCSS was particularly well suited for this use-case as everything in PostCSS is geared towards &lt;em&gt;transforming&lt;/em&gt; CSS.&lt;/p&gt;

&lt;p&gt;Nevertheless it was a nice experiment. Having used PostCSS a lot via Autoprefixer it was interesting to dig into the internals of PostCSS to see how everything works!&lt;/p&gt;

&lt;p&gt;If you have any tips on using PostCSS for this purpose, or perhaps other tools that might be better suited for this, please let me know!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Generating CSS from scratch with PostCSS</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Mon, 06 Jan 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/generating-css-from-scratch-with-postcss-4ce7</link>
      <guid>https://forem.com/kabisasoftware/generating-css-from-scratch-with-postcss-4ce7</guid>
      <description>&lt;p&gt;For a soon-to-be open-sourced project I had to generate a CSS stylesheet based on user input.&lt;/p&gt;

&lt;p&gt;Initially I started out with good ‘ol string templating and interpolation but it soon became pretty complex, as I needed to conditionally add certain properties and declarations.&lt;/p&gt;

&lt;p&gt;It occurred to me that what I wanted was a representation of the CSS structure in code, an Abstract Syntax Tree (AST). That would allow me to build up the CSS tree structure in code and turn it into a string later on.&lt;/p&gt;

&lt;p&gt;I decided to see if I could use &lt;a href="https://postcss.org/"&gt;PostCSS&lt;/a&gt;, since I figured it must be turning CSS into an AST already.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostCSS
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://postcss.org/"&gt;PostCSS&lt;/a&gt; is “A tool for transforming CSS with JavaScript”. It’s widely used in the industry for things like &lt;a href="https://autoprefixer.github.io/"&gt;auto-prefixing CSS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As the tag line says, PostCSS main business is &lt;em&gt;transforming&lt;/em&gt; CSS, not generating it from scratch as I wanted to do. Looking at the PostCSS codebase I noticed &lt;a href="https://github.com/postcss/postcss/blob/master/test/postcss.test.js#L116"&gt;a test&lt;/a&gt; named &lt;code&gt;it allows to build own CSS&lt;/code&gt; so I figured it should be possible!&lt;/p&gt;

&lt;p&gt;Fast forward a couple of hours diving through the PostCSS codebase, reading the API docs and several PostCSS plugins, I had a working solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating CSS from scratch
&lt;/h3&gt;

&lt;p&gt;To generate CSS with PostCSS you first need to build up an AST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postcss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postcss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fontFamily&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;My Family&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postcss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bodyRule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postcss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;postcss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-family&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fontFace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postcss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atRule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-face&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
   &lt;span class="nx"&gt;postcss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-family&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
   &lt;span class="nx"&gt;postcss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url(./fonts/myfont.woff2)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;fontFace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;bodyRule&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The CSS code can then be generated from the CSS with &lt;code&gt;root.toString()&lt;/code&gt;, resulting in this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@font-face&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"My Family"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url(./fonts/myfont.woff2)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"My Family"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A couple of things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostCSS does not provide much help in generating proper declaration values &lt;a href="https://github.com/TrySound/postcss-value-parser"&gt;out of the box&lt;/a&gt;. For example quoting a &lt;code&gt;font-family&lt;/code&gt; value in case it contains multiple words is not handled by PostCSS automatically.&lt;/li&gt;
&lt;li&gt;It’s easy to conditionally generate properties, rules, declarations etc. The API follows a typical builder pattern, which makes it easy to conditionally call &lt;code&gt;append&lt;/code&gt;, or not.&lt;/li&gt;
&lt;li&gt;The CSS output is opinionated. There are no semicolons after the last declaration and no newlines between rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fortunately, PostCSS is architected quite well and allows you to provide your own &lt;a href="http://api.postcss.org/global.html#stringifier"&gt;“Stringifier”&lt;/a&gt;. I didn’t find much documentation or guidance on this though, but after a bit of code diving I settled on this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Stringifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postcss/lib/stringifier&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PrettyStringifier&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stringifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Add a newline after a rule, if it's preceded by another rule.&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;decl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="cm"&gt;/* force semicolon */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see, this is inheriting most of the default behaviour except around rules and declarations.&lt;/p&gt;

&lt;p&gt;The custom &lt;code&gt;Stringifier&lt;/code&gt; can be used like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PrettyStringifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;All in all I achieved what I had to do, but it didn’t feel like PostCSS was particularly well suited for this use-case as everything in PostCSS is geared towards &lt;em&gt;transforming&lt;/em&gt; CSS.&lt;/p&gt;

&lt;p&gt;Nevertheless it was a nice experiment. Having used PostCSS a lot via Autoprefixer it was interesting to dig into the internals of PostCSS to see how everything works!&lt;/p&gt;

&lt;p&gt;If you have any tips on using PostCSS for this purpose, or perhaps other tools that might be better suited for this, please let me know!&lt;/p&gt;

</description>
      <category>css</category>
      <category>postcss</category>
    </item>
    <item>
      <title>HTTP Caching Gotcha: Heuristic Freshness</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Mon, 09 Dec 2019 14:59:45 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/http-caching-gotcha-heuristic-freshness-ej9</link>
      <guid>https://forem.com/kabisasoftware/http-caching-gotcha-heuristic-freshness-ej9</guid>
      <description>&lt;h1&gt;
  
  
  HTTP caching gotcha: Heuristic Freshness
&lt;/h1&gt;

&lt;p&gt;I recently ran into an issue where after deployment of an SPA (Single Page Application), a situation would occur where the page looked broken because CSS could not be loaded.&lt;/p&gt;

&lt;p&gt;During analysis of the issue I ran into a thing I had never heard of: &lt;em&gt;Heuristic Freshness&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;For context, the application is a typical SPA. The compiled application consists of a bunch of files looking something like this (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── index.html
    └── styles.4a3f9848037579025b00.css
    └── main.31f7dadf6d2b01fc08c7.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser loads &lt;code&gt;index.html&lt;/code&gt;, which includes various CSS and JS files.&lt;/p&gt;

&lt;p&gt;Caching related headers for the CSS and JS looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date: Fri, 06 Dec 2019 13:09:03 GMT
Etag: "31957a05a5df3c3b315b728b40b6e10e"
Last-Modified: Mon, 02 Dec 2019 14:12:09 GMT
Expires:    Sat, 07 Dec 2019 03:09:03 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Date:   Fri, 06 Dec 2019 13:09:03 GMT
Etag: "c4238385fe77f826b5584fed1f1f1659"
Last-Modified: Tue, 03 Dec 2019 10:50:54 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first sight things looked okay, but then I noticed there weren't any &lt;code&gt;Cache-Control&lt;/code&gt; or &lt;code&gt;Expires&lt;/code&gt; on the &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;I'm quite well aware what all these caching headers do when they &lt;em&gt;are&lt;/em&gt; present, but I wasn't sure what would happen if they are &lt;strong&gt;not&lt;/strong&gt; present 🤔.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heuristic Freshness
&lt;/h2&gt;

&lt;p&gt;This brings us to &lt;code&gt;Heuristic Freshness&lt;/code&gt;. The HTTP specification &lt;a href="https://tools.ietf.org/html/rfc7234#section-4.2.2"&gt;defines&lt;/a&gt; that, when a server does &lt;em&gt;not&lt;/em&gt; explicitly specify expiration times, the client (browser) can use heuristics to estimate a plausible expiration time itself.&lt;/p&gt;

&lt;p&gt;How exactly this 'plausible' expiration time is determined is left up to the client, but it seems that in practise most browsers use the following algorithm: &lt;code&gt;(now() - Last-Modified) * 0.10&lt;/code&gt;. This means a couple of things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When you don't have any &lt;code&gt;Cache-Control&lt;/code&gt; or &lt;code&gt;Expires&lt;/code&gt; headers, the browser calculates this plausible expiry time itself.&lt;/li&gt;
&lt;li&gt;Once your assets are cached by browsers, there's no way for you to evict them from the cache.&lt;/li&gt;
&lt;li&gt;The files will be cached longer as time passes after deployment (assuming your &lt;code&gt;Last-Modified&lt;/code&gt; headers reflect the time of the last deployment).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, this can result in some pretty nasty caching issues that are hard to diagnose as the duration for which files are cached will differ case by case depending on time and potentially browser used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache-Control and Expires headers to the rescue!
&lt;/h2&gt;

&lt;p&gt;The lesson I take away from this is that it's crucial to set either &lt;code&gt;Cache-Control&lt;/code&gt; or &lt;code&gt;Expires&lt;/code&gt;, to ensure &lt;em&gt;you&lt;/em&gt; control how long files can be cached by the browser.&lt;/p&gt;

&lt;p&gt;For single page apps like I outlined above where you have an &lt;code&gt;index.html&lt;/code&gt; and a bunch of assets with hashed filenames, the following is a good, safe practise:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache-control: private, max-age=0, no-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will ensure that the browser will never use a cached copy of your &lt;code&gt;index.html&lt;/code&gt;, without checking with the server if the cache is still valid via a &lt;a href="https://tools.ietf.org/html/rfc7234#section-4.3"&gt;conditional GET request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For other assets with hashed filenames, you want the opposite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache-Control: public, max-age=31557600
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These files can be cached for a long, long time, as when they change their filenames will change as well.&lt;/p&gt;

&lt;p&gt;With this in place the following will happen during and after a deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new &lt;code&gt;index.html&lt;/code&gt; will be uploaded to the server.&lt;/li&gt;
&lt;li&gt;When a user loads your application, the browser will send a conditional GET request &lt;code&gt;If-Modified-Since: &amp;lt;previous Last-Modified value&amp;gt;&lt;/code&gt; , and the server will respond with the new version of your &lt;code&gt;index.html&lt;/code&gt;, since the file was modified by the deployment. This happens because you've instructed the browser to always verify if the cached page can be used, using the &lt;code&gt;Cache-Control&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;The browser will no longer us the old cached page.&lt;/li&gt;
&lt;li&gt;The browser will store the new page in cache and will continue sending conditional GET requests in the future, to verify if the cached page can still be used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Problem solved!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;For more information on HTTP caching (especially about what headers do when they &lt;em&gt;are&lt;/em&gt; present) see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching"&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching"&gt;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>http</category>
      <category>heuristic</category>
      <category>freshness</category>
      <category>heuristicfreshness</category>
    </item>
    <item>
      <title>HTTP caching gotcha: Heuristic Freshness</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Mon, 09 Dec 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/http-caching-gotcha-heuristic-freshness-1e05</link>
      <guid>https://forem.com/kabisasoftware/http-caching-gotcha-heuristic-freshness-1e05</guid>
      <description>&lt;p&gt;I recently ran into an issue where after deployment of an SPA (Single Page Application), a situation would occur where the page looked broken because CSS could not be loaded. During analysis of the issue I ran into a thing I had never heard of: &lt;em&gt;Heuristic Freshness&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;For context, the application is a typical SPA. The compiled application consists of a bunch of files looking something like this (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1
2
3

├── index.html
    └── styles.4a3f9848037579025b00.css
    └── main.31f7dadf6d2b01fc08c7.js

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



&lt;p&gt;The browser loads &lt;code&gt;index.html&lt;/code&gt;, which includes various CSS and JS files.&lt;/p&gt;

&lt;p&gt;Caching related headers for the CSS and JS looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1
2
3
4

Date: Fri, 06 Dec 2019 13:09:03 GMT
Etag: "31957a05a5df3c3b315b728b40b6e10e"
Last-Modified: Mon, 02 Dec 2019 14:12:09 GMT
Expires:    Sat, 07 Dec 2019 03:09:03 GMT

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



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

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1
2
3

Date:   Fri, 06 Dec 2019 13:09:03 GMT
Etag: "c4238385fe77f826b5584fed1f1f1659"
Last-Modified: Tue, 03 Dec 2019 10:50:54 GMT

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



&lt;p&gt;At first sight things looked okay, but then I noticed there weren’t any &lt;code&gt;Cache-Control&lt;/code&gt; or &lt;code&gt;Expires&lt;/code&gt; on the &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;I’m quite well aware what all these caching headers do when they &lt;em&gt;are&lt;/em&gt; present, but I wasn’t sure what would happen if they are &lt;strong&gt;not&lt;/strong&gt; present 🤔.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heuristic Freshness
&lt;/h2&gt;

&lt;p&gt;This brings us to &lt;code&gt;Heuristic Freshness&lt;/code&gt;. The HTTP specification &lt;a href="https://tools.ietf.org/html/rfc7234#section-4.2.2"&gt;defines&lt;/a&gt; that, when a server does &lt;em&gt;not&lt;/em&gt; explicitly specify expiration times, the client (browser) can use heuristics to estimate a plausible expiration time itself.&lt;/p&gt;

&lt;p&gt;How exactly this ‘plausible’ expiration time is determined is left up to the client, but it seems that in practise most browsers use the following algorithm: &lt;code&gt;(now() - Last-Modified) * 0.10&lt;/code&gt;. This means a couple of things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When you don’t have any &lt;code&gt;Cache-Control&lt;/code&gt; or &lt;code&gt;Expires&lt;/code&gt; headers, the browser calculates this plausible expiry time itself.&lt;/li&gt;
&lt;li&gt;Once your assets are cached by browsers, there’s no way for you to evict them from the cache.&lt;/li&gt;
&lt;li&gt;The files will be cached longer as time passes after deployment (assuming your &lt;code&gt;Last-Modified&lt;/code&gt; headers reflect the time of the last deployment).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, this can result in some pretty nasty caching issues that are hard to diagnose as the duration for which files are cached will differ case by case depending on time and potentially browser used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache-Control and Expires headers to the rescue!
&lt;/h2&gt;

&lt;p&gt;The lesson I take away from this is that it’s crucial to set either &lt;code&gt;Cache-Control&lt;/code&gt; or &lt;code&gt;Expires&lt;/code&gt;, to ensure &lt;em&gt;you&lt;/em&gt; control how long files can be cached by the browser.&lt;/p&gt;

&lt;p&gt;For single page apps like I outlined above where you have an &lt;code&gt;index.html&lt;/code&gt; and a bunch of assets with hashed filenames, the following is a good, safe practise:&lt;/p&gt;

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

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1

Cache-control: private, max-age=0, no-cache

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



&lt;p&gt;This will ensure that the browser will never use a cached copy of your &lt;code&gt;index.html&lt;/code&gt;, without checking with the server if the cache is still valid via a &lt;a href="https://tools.ietf.org/html/rfc7234#section-4.3"&gt;conditional GET request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For other assets with hashed filenames, you want the opposite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1

Cache-Control: public, max-age=31557600

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



&lt;p&gt;These files can be cached for a long, long time, as when they change their filenames will change as well.&lt;/p&gt;

&lt;p&gt;With this in place the following will happen during and after a deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A new &lt;code&gt;index.html&lt;/code&gt; will be uploaded to the server.&lt;/li&gt;
&lt;li&gt;When a user loads your application, the browser will send a conditional GET request &lt;code&gt;If-Modified-Since: &amp;lt;previous Last-Modified value&amp;gt;&lt;/code&gt; , and the server will respond with the new version of your &lt;code&gt;index.html&lt;/code&gt;, since the file was modified by the deployment. This happens because you’ve instructed the browser to always verify if the cached page can be used, using the &lt;code&gt;Cache-Control&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;The browser will no longer us the old cached page.&lt;/li&gt;
&lt;li&gt;The browser will store the new page in cache and will continue sending conditional GET requests in the future, to verify if the cached page can still be used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Problem solved!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;For more information on HTTP caching (especially about what headers do when they &lt;em&gt;are&lt;/em&gt; present) see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching"&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching"&gt;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>http</category>
      <category>heuristicfreshness</category>
    </item>
    <item>
      <title>k3s HTTPS with Let’s Encrypt</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Tue, 02 Jul 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/pascalw/k3s-https-with-let-s-encrypt-den</link>
      <guid>https://forem.com/pascalw/k3s-https-with-let-s-encrypt-den</guid>
      <description>&lt;p&gt;&lt;a href="https://k3s.io/"&gt;K3s&lt;/a&gt; is a Certified Kubernetes distribution designed for production workloads in unattended, resource-constrained, remote locations or inside IoT appliances.&lt;br&gt;
It provides a ready to go Kubernetes instance packaged a single binary.&lt;/p&gt;

&lt;p&gt;This guide will show you how to easily set up k3s with HTTPS via Let’s Encrypt.&lt;/p&gt;

&lt;p&gt;(READMORE)&lt;/p&gt;

&lt;p&gt;K3s comes with &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt; out of the box. Traefik itself supports automatic HTTPS via Let’s Encrypt, but setting this up with k3s turned out to be pretty cumbersome. Instead using the excellent &lt;a href="https://github.com/jetstack/cert-manager"&gt;cert-manager&lt;/a&gt; add-on, it's a breeze!&lt;/p&gt;
&lt;h2&gt;
  
  
  0: Setup k3s.
&lt;/h2&gt;

&lt;p&gt;This guide assumes you have a working k3s instance and &lt;code&gt;kubectl&lt;/code&gt; configured to talk to your k3s instance. If you haven't already just follow the &lt;a href="https://github.com/rancher/k3s/blob/master/README.md"&gt;docs&lt;/a&gt; and you'll be up and running in minutes.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Install Helm
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://helm.sh/"&gt;Helm&lt;/a&gt; is a package manager for Kubernetes. It consists of a local client and a server component.&lt;br&gt;
Installing Helm is quite &lt;a href="https://helm.sh/docs/using_helm/#installing-helm"&gt;straightfoward&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kubernetes-helm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Create the service account so Helm can interact with your k3s instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create serviceaccount tiller &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kube-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Grant it admin privileges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create clusterrolebinding tiller-admin &lt;span class="nt"&gt;--serviceaccount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kube-system:tiller &lt;span class="nt"&gt;--clusterrole&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cluster-admin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note: granting admin privileges to Helm might not be a good idea depending on how your cluster is setup. Read &lt;a href="https://helm.sh/docs/using_helm/#role-based-access-control"&gt;here&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Install Helm on k3s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm init &lt;span class="nt"&gt;--service-account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tiller
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;h2&gt;
  
  
  2. Install cert-manager
&lt;/h2&gt;

&lt;p&gt;Alright, so now we have Helm installed let's move on to installing &lt;code&gt;cert-manager&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;cert-manager&lt;/code&gt; is a Kubernetes add-on to automate the management and issuance of TLS certificates from various issuing sources, amongst which Let’s Encrypt.&lt;/p&gt;

&lt;p&gt;Start with installing &lt;code&gt;cert-manager&lt;/code&gt;s CRDs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/jetstack/cert-manager/release-0.8/deploy/manifests/00-crds.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Add the &lt;code&gt;cert-manager&lt;/code&gt; Helm repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm repo add jetstack https://charts.jetstack.io &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; helm repo update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Install &lt;code&gt;cert-manager&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;helm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; cert-manager &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; cert-manager &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; v0.8.1 &lt;span class="se"&gt;\&lt;/span&gt;
  jetstack/cert-manager
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This might take a minute or so. Verify the installation by checking all &lt;code&gt;cert-manager&lt;/code&gt; pods are running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get all &lt;span class="nt"&gt;-n&lt;/span&gt; cert-manager
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;h2&gt;
  
  
  3. Configure cert-manager
&lt;/h2&gt;

&lt;p&gt;With  &lt;code&gt;cert-manager&lt;/code&gt; installed we need to configure it to use Let’s Encrypt, by creating a &lt;em&gt;certificate issuer&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; letsencrypt-prod-issuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Issuer
metadata:
 name: letsencrypt-prod
spec:
 acme:
   # The ACME server URL
   server: https://acme-v02.api.letsencrypt.org/directory
   # Email address used for ACME registration, update to your own.
   email: user@example.com
   # Name of a secret used to store the ACME account private key
   privateKeySecretRef:
     name: letsencrypt-prod
   # Enable the HTTP-01 challenge provider
   http01: {}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; letsencrypt-prod-issuer.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;By now, &lt;code&gt;cert-manager&lt;/code&gt; is ready to provision Let’s Encrypt certificates as we need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Deploy a service with automatic HTTPS
&lt;/h2&gt;

&lt;p&gt;To try this out let's deploy a simple service. You should have a DNS name pointed to your k3s cluster for this to work. The example below uses &lt;code&gt;bootcamp.k3s.example.org&lt;/code&gt;, be sure to update this to your own hostname!&lt;/p&gt;

&lt;p&gt;Create the manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;$ cat &amp;lt;&amp;lt;EOF &amp;gt; k8s-bootcamp.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/google-samples/kubernetes-bootcamp:v1&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik"&lt;/span&gt;
    &lt;span class="s"&gt;certmanager.k8s.io/issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;letsencrypt-prod"&lt;/span&gt;
    &lt;span class="s"&gt;certmanager.k8s.io/acme-challenge-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http01&lt;/span&gt;

&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Change this to your own hostname&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bootcamp.k3s.example.org&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bootcamp-k3s-example-org-tls&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Change this to your own hostname&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bootcamp.k3s.example.org&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k8s-bootcamp&lt;/span&gt;
          &lt;span class="na"&gt;servicePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s-bootcamp.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Wait a minute or so and your endpoint should become available with a Let’s Encrypt certificate! 🎉&lt;/p&gt;

</description>
      <category>k3s</category>
      <category>k8s</category>
      <category>letsencrypt</category>
    </item>
    <item>
      <title>Caveats storing large amounts of data in Elixir Agents</title>
      <dc:creator>Pascal Widdershoven</dc:creator>
      <pubDate>Thu, 25 Apr 2019 00:00:00 +0000</pubDate>
      <link>https://forem.com/kabisasoftware/caveats-storing-large-amounts-of-data-in-elixir-agents-490p</link>
      <guid>https://forem.com/kabisasoftware/caveats-storing-large-amounts-of-data-in-elixir-agents-490p</guid>
      <description>&lt;p&gt;Recently while working on an Elixir project I ran into an interesting gotcha with Agents that caused massive amounts of resource usage. Read on to find out what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Agents in Elixir?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Agents are a simple abstraction around state.&lt;/p&gt;

&lt;p&gt;Often in Elixir there is a need to share or store state that must be accessed from different processes or by the same process at different points in time.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://hexdocs.pm/elixir/Agent.html#content"&gt;&lt;code&gt;Agent&lt;/code&gt;&lt;/a&gt; module provides a basic server implementation that allows state to be retrieved and updated via a simple API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Elixir is an immutable language where nothing is shared by default. This has many benefits, but it also means that when you &lt;em&gt;do&lt;/em&gt; want to share data between processes you need to do some extra work. Fortunately, Elixir provides a lot of great building blocks to achieve this like &lt;a href="https://hexdocs.pm/elixir/Agent.html"&gt;Agents&lt;/a&gt;, &lt;a href="https://elixir-lang.org/getting-started/mix-otp/ets.html"&gt;ETS&lt;/a&gt; and &lt;a href="https://elixirschool.com/en/lessons/specifics/mnesia/"&gt;Mnesia&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what’s the problem?
&lt;/h2&gt;

&lt;p&gt;There are two ways to use the state stored in an agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;By operating on the data form within the Agents process:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Compute in the agent/server&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;do_something_expensive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;By pulling the data into the client process and operating on it there:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Compute in the client&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;get_something&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;do_something_expensive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you look at the code the differences are very subtle. The difference in behaviour, however, is &lt;strong&gt;not&lt;/strong&gt; subtle.&lt;/p&gt;

&lt;p&gt;In approach #1 the data will remain in the Agent process. However, if you perform expensive operations there the agent will be blocked for the entire duration of the operation, meaning no other process can access the data until the operation is finished. Using this model to respond to an HTTP request is killing for performance.&lt;/p&gt;

&lt;p&gt;In approach #2 the Agent will not be blocked, but the data will be copied into the process that is accessing the data. When the amount of data is small this is not really a problem, but if you start storing larger amounts of data this becomes really expensive real quick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real life example
&lt;/h2&gt;

&lt;p&gt;The impact of this can be huge as I will demonstrate in the case below.&lt;/p&gt;

&lt;p&gt;In the project I’m working on we were storing a set of rules in an Agent. A rule is a struct with 27 fields and we were storing approximately ~ 5000 rules in the Agent. There’s an HTTP endpoint that for every request uses these rules to determine the response.&lt;/p&gt;

&lt;p&gt;For a while this was fine, but when the load started increasing we noticed the server going out of memory. To debug this I started throwing load at the endpoint using &lt;a href="https://github.com/wg/wrk"&gt;wrk&lt;/a&gt;. Results below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1m test @ http://localhost:4001
  4 threads and 1000 connections
  Thread Stats Avg Stdev Max +/- Stdev
    Latency 2.23s 553.43ms 3.00s 57.78%
    Req/Sec 59.72 121.98 680.00 88.64%
  Latency Distribution
     50% 2.24s
     75% 2.67s
     90% 2.88s
     99% 3.00s
  5551 requests in 1.00m, 11.80MB read
  Socket errors: connect 0, read 5971, write 3, timeout 5506
  Non-2xx or 3xx responses: 4575
Requests/sec: 92.38
Transfer/sec: 201.05KB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see 92 requests per second are handled and a lot of requests time out (take more than 3 seconds). During the test, the Elixir process consumed around &lt;strong&gt;10GB of memory&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions
&lt;/h2&gt;

&lt;p&gt;As we’ve seen in the previous section, storing these amounts of data in an Agent requires a lot of memory and performance is frankly not great.&lt;/p&gt;

&lt;p&gt;Looking at the code and reading the Agent documentation, I quickly realised that the root cause of this issue was the fact that all rules were copied to the process handling the HTTP request, for &lt;em&gt;every request&lt;/em&gt;. So how can we prevent this?&lt;/p&gt;

&lt;p&gt;‘Shared nothing’ is a very core principle of Elixir/Erlang, so the short answer is you can’t prevent the data from being copied if you want to share the data between processes. This affects all ways of storing data in memory, so not just Agents.&lt;/p&gt;

&lt;p&gt;There are workarounds, like &lt;a href="https://github.com/discordapp/fastglobal"&gt;fast_global&lt;/a&gt;. Fastglobal works by dynamically compiling a module at runtime, but it’s not without &lt;a href="https://github.com/discordapp/fastglobal#caveats"&gt;drawbacks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So the solution is to make sure the data does not &lt;em&gt;have&lt;/em&gt; to be shared between processes. There are a variety of ways to do this. The approach I took was to create a pool of worker processes (with &lt;a href="https://elixirschool.com/en/lessons/libraries/poolboy/"&gt;Poolboy&lt;/a&gt;) that handle executing the rules. When an HTTP request comes in, the rule matching is handled by one of the worker processes.&lt;/p&gt;

&lt;p&gt;In code this looks roughly like this (simplified):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Worker&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;State&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:match_rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match_rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When a worker starts it loads (copies) the rules from the Agent (&lt;code&gt;State&lt;/code&gt; is a module wrapping the Agent) into the worker process. Each worker process contains a copy of the rules so the memory usage is predictable.&lt;/p&gt;

&lt;p&gt;If the rules change at runtime, the processes are simply killed and restarted so the new rules will be used automatically. Poolboy takes care of starting N workers and selecting a worker from the pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  End result
&lt;/h2&gt;

&lt;p&gt;With that in place, &lt;code&gt;wrk&lt;/code&gt; results started looking as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Running 1m test @ http://localhost:4001
  4 threads and 1000 connections
  Thread Stats Avg Stdev Max +/- Stdev
    Latency 1.04s 270.71ms 1.66s 69.89%
    Req/Sec 221.14 65.34 405.00 66.08%
  Latency Distribution
     50% 1.07s
     75% 1.25s
     90% 1.36s
     99% 1.47s
  52823 requests in 1.00m, 14.41MB read
  Socket errors: connect 0, read 1014, write 0, timeout 0
Requests/sec: 879.16
Transfer/sec: 245.55KB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see the throughput increased from 92 req/sec to 879 req/sec. Average latency went down from 2.23s to 1.04s. Memory used went down from 10GB to &lt;strong&gt;400MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not bad!&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>phoenix</category>
    </item>
  </channel>
</rss>
