<?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: Jesper Høy</title>
    <description>The latest articles on Forem by Jesper Høy (@jesperhoy).</description>
    <link>https://forem.com/jesperhoy</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%2F211870%2Ff0922145-191b-4ade-b441-2ef2a5523ba7.jpeg</url>
      <title>Forem: Jesper Høy</title>
      <link>https://forem.com/jesperhoy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jesperhoy"/>
    <language>en</language>
    <item>
      <title>Introducing OwnCDN</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Fri, 17 Apr 2026 10:07:09 +0000</pubDate>
      <link>https://forem.com/jesperhoy/introducing-owncdn-589b</link>
      <guid>https://forem.com/jesperhoy/introducing-owncdn-589b</guid>
      <description>&lt;p&gt;&lt;a href="https://owncdn.com" rel="noopener noreferrer"&gt;OwnCDN&lt;/a&gt; is self-hosted CDN server software&lt;br&gt;
running as an IIS website on your own Windows computer/VM/VPS.&lt;/p&gt;

&lt;p&gt;It does caching (disk/memory), replicated and versioned object storage (S3 compatible), and image resizing and optimization - effectively your own CDN node.&lt;/p&gt;

&lt;p&gt;Services can be chained together in any order to provide a powerful system.&lt;br&gt;&lt;br&gt;
For example: HTTP Publisher → Disk Cache → Image Optimization → Blob/object storage.&lt;/p&gt;

&lt;p&gt;OwnCDN is configurable through a web-based user interface.&lt;/p&gt;

&lt;p&gt;It has a simple HTTP API which also supports Amazon S3 API syntax, making it compatible with a long list of software and services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  File/object storage
&lt;/h3&gt;

&lt;p&gt;Provide centralized file/object storage.&lt;/p&gt;

&lt;p&gt;The "Storage service" provides versioned and replicable storage (like Amazon S3).&lt;/p&gt;

&lt;p&gt;The "File System service" provides access to files in the local file system.&lt;/p&gt;

&lt;p&gt;You can make store, retrieve, and delete files/object via the "HTTP API service" or provide public access to the files with the "HTTP Publisher" service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching
&lt;/h3&gt;

&lt;p&gt;Provide caching to bring files/objects closer to users, to serve files/objects faster, and/or to reduce traffic to origin servers.&lt;/p&gt;

&lt;p&gt;The "Memory Cache service" provides temporary caching for files/objects which are requested very frequently and/or need very fast delivery (like Redis).&lt;/p&gt;

&lt;p&gt;The "Disk Cache service" provides temporary file/object caching - persisted to disk so that it survives server restarts.&lt;/p&gt;

&lt;p&gt;You can configure the cache services to retrieve files from a remote web-server using the "HTTP Fetcher service", or you can upload files via the "HTTP API service".&lt;/p&gt;

&lt;p&gt;You can provide public access to the cached files with the "HTTP Publisher service".&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Optimization
&lt;/h3&gt;

&lt;p&gt;The "Image Optimization service" reduces image file size using and automatically scales and/or crops images.&lt;/p&gt;

&lt;h2&gt;
  
  
  OwnCDN origin story
&lt;/h2&gt;

&lt;p&gt;My company (&lt;a href="https://jhsoftware.dk" rel="noopener noreferrer"&gt;JH Software&lt;/a&gt;) also develop and run an educational platform with on-line tests and exercises for Danish elementary and middle schools - see &lt;a href="https://tjek.net" rel="noopener noreferrer"&gt;https://tjek.net&lt;/a&gt; (Danish language only).&lt;/p&gt;

&lt;p&gt;For years, we ran this as a single ASP.NET website under IIS on a Windows VPS (virtual private server) at various cloud providers.&lt;br&gt;
But when GDPR (EU privacy regulations) hit, it became difficult to be in compliance while keeping the data with cloud providers.&lt;/p&gt;

&lt;p&gt;So, we decided to move the core of Tjek.net home to a server running in our own office where we can ensure privacy and security for our users in accordance with GDPR.&lt;/p&gt;

&lt;p&gt;As part of Tjek.net, we also serve a lot of images and videos (not user-specific) as illustrations in the on-line tests and exercises, and we didn't want to serve these through the limited bandwidth of our office Internet connection.&lt;/p&gt;

&lt;p&gt;Since we were still running a (now underutilized) Windows VPS at a cloud provider for other purposes, we wanted to try and use this for serving the images/videos and to cache some dynamically generated content from the office server.&lt;/p&gt;

&lt;p&gt;This meant that we needed some kind of object storage software (like AWS S3) and web caching software (like a CDN) running on this server.&lt;/p&gt;

&lt;p&gt;Since we couldn't find any suitable software for this, we started putting together our own.&lt;/p&gt;

&lt;p&gt;After refining this software for several years, this is, of course, what eventually became OwnCDN.&lt;/p&gt;

&lt;p&gt;Today OwnCDN is serving 250K+ images/videos for Tjek.net every day, while barely registering on the CPU usage chart on the VPS.&lt;/p&gt;

&lt;p&gt;Check out OwnCDN at &lt;a href="https://owncdn.com" rel="noopener noreferrer"&gt;https://owncdn.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cdn</category>
      <category>redis</category>
      <category>s3</category>
    </item>
    <item>
      <title>Filter CouchDB query results with arbitrary JavaScript - like SQL WHERE...</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Sun, 04 Jan 2026 11:33:18 +0000</pubDate>
      <link>https://forem.com/jesperhoy/filter-couchdb-query-results-with-arbitrary-javascript-like-sql-where-3lf5</link>
      <guid>https://forem.com/jesperhoy/filter-couchdb-query-results-with-arbitrary-javascript-like-sql-where-3lf5</guid>
      <description>&lt;p&gt;&lt;a href="https://couchdb.apache.org" rel="noopener noreferrer"&gt;CouchDB&lt;/a&gt; has a &lt;a href="https://docs.couchdb.org/en/stable/ddocs/ddocs.html#list-functions" rel="noopener noreferrer"&gt;"List function" feature&lt;/a&gt; which allows you to transform query results.&lt;/p&gt;

&lt;p&gt;This can be used to filter results and/or generate HTML, XML, JSON, etc. - in CouchDB - before it is returned to the client.&lt;/p&gt;

&lt;p&gt;A List function (JavaScript) needs to be stored in a &lt;a href="https://docs.couchdb.org/en/stable/ddocs/ddocs.html" rel="noopener noreferrer"&gt;design document&lt;/a&gt; in the same database as the data to be queried. So this JavaScript code is going to be static.&lt;/p&gt;

&lt;p&gt;However, when executing a List function, it is possible to provide various parameters to it via URL query parameters (available as members of the &lt;code&gt;Request.query&lt;/code&gt; object).&lt;/p&gt;

&lt;p&gt;By sending JavaScript code as one such parameters, it is actually possible to run arbitrary JavaScript - dynamically generated client side(*) and potentially unique for each query - as part of a List function.&lt;br&gt;
And so, a single List function can be used to execute any number of client defined(*) filters/transforms.&lt;/p&gt;

&lt;p&gt;This is very similar to SQL WHERE functionality - only using JavaScript instead of SQL.&lt;/p&gt;

&lt;p&gt;The following is the JavaScript code for a List function which takes 3 custom URL query parameters; "js_code" as the filter code to execute for each result row, "js_skip" as the number of initial rows to skip, and "js_limit" to limit the number of rows returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&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;filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_r&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;return (&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js_code&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;)(_r);&lt;/span&gt;&lt;span class="dl"&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;Skip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js_skip&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;Limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js_limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;headers&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&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;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="o"&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;row&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getRow&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&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;Skip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;Skip&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&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;Limit&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;Limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&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="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resp&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;The code above would need to be included in a design document under &lt;code&gt;lists&lt;/code&gt; / &lt;code&gt;jsfilter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I now add this exact List function to most of my CouchDB design documents, so that I always have this functionality readily available.&lt;/p&gt;

&lt;p&gt;The JavaScript code that you send in the &lt;code&gt;js_code&lt;/code&gt; parameters would look somthing like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&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;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OrderNumber&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Color&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, the short version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OrderNumber&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(*) By "client side" / "client defined", I mean whatever code is sending the query to CouchDB. This would typically be code running on a server, like web-server server side script. You obviously don't want to give end-users direct access to this.&lt;/p&gt;

&lt;p&gt;NOTE: The CouchDB documentation states that List functions are deprecated and will be removed in "CouchDB v. 4".&lt;br&gt;
"V. 4" was a, now abandoned, effort to replace the underlying storage engine for CouchDB.&lt;br&gt;
Since that "v. 4" will never be, and the current v. 3 is very actively maintained, I think (and hope) List functions will be around for the foreseeable future.&lt;/p&gt;

</description>
      <category>couchdb</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why I am moving to CouchDB</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Tue, 20 Feb 2024 00:44:21 +0000</pubDate>
      <link>https://forem.com/jesperhoy/why-i-am-moving-to-couchdb-1mph</link>
      <guid>https://forem.com/jesperhoy/why-i-am-moving-to-couchdb-1mph</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update April 2026&lt;/strong&gt;&lt;br&gt;
I am still a big fan of CouchDB, but I no longer use it after making&lt;br&gt;
&lt;a href="https://owncdn.com" rel="noopener noreferrer"&gt;OwnCDN&lt;/a&gt;.&lt;br&gt;
Indeed CouchDB was a big inspiration for the replication system in OwnCDN.&lt;br&gt;
I now use OwnCDN for everything that needs "live continuous replication/backup" (JSON documents, images, and otherwise), and SQLite for everything else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Starting today, I will be using &lt;a href="https://couchdb.apache.org" rel="noopener noreferrer"&gt;CouchDB&lt;/a&gt; as my primary database server for new projects and will gradually be moving existing projects to it.&lt;/p&gt;

&lt;p&gt;I have been using Microsoft SQL Server for many years and never really gave it much thought - it just works - and I know how to make it work. And it is pretty fast given the right hardware and configured with the right indexes etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Late to the No-SQL party?
&lt;/h3&gt;

&lt;p&gt;I know this sounds like swimming against the current - or being late to the No-SQL party. But this is really about the overall feature set of CouchDB specifically - and not an SQL vs No-SQL thing. &lt;/p&gt;

&lt;h3&gt;
  
  
  I don't have big data...
&lt;/h3&gt;

&lt;p&gt;Many articles on why you might use No-SQL databases (like CouchDB) point to "big data" as the main reason. But I don't have "big data". My largest SQL database is around 5GB with a few million records total. So that's not it.&lt;/p&gt;

&lt;h3&gt;
  
  
  I don't need master-to-master replication...
&lt;/h3&gt;

&lt;p&gt;As for CouchDB specifically, many articles point to master-to-master replication, filtered synchronization with client devices using &lt;a href="https://pouchdb.com" rel="noopener noreferrer"&gt;PouchDB&lt;/a&gt;, or offline-first scenarios. Those are great features - but that's not it either.&lt;/p&gt;

&lt;h3&gt;
  
  
  So why CouchDB?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It is simple!&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Other database systems certainly have a lot more features.&lt;br&gt;
But for 99% of what I do - CouchDB is plenty.&lt;br&gt;
And I'd much rather use a simple system that I can get my head around, than some complex thing that I will never fully understand or utilize.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lightweight&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In my tests with 500K records, it used less than 100MB of memory and otherwise hums along at below 40MB.&lt;br&gt;&lt;br&gt;
This makes it feasible to run it on a small server / VPS alongside other stuff - like your web-server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live continuous replication/backup&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Setting up live continuous replication/backup to another CouchDB instance is super easy.&lt;br&gt;
Having an always up-to-date replica beats daily SQL server backups by a long shot (virtually no risk of data loss vs. risk of losing up to 24 hours' worth of data).&lt;br&gt;&lt;br&gt;
You can even do filtered replication - providing different clients different parts of the database - something that is impossible with most other database systems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data is just JSON&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
JSON is THE universal data language.&lt;br&gt;&lt;br&gt;
We have to use it when doing any kind of web front-end stuff, so we might as well use it with the database too.&lt;br&gt;&lt;br&gt;
This also makes it possible to re-use a lot of serialization code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP REST interface&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No client libraries needed.&lt;br&gt;&lt;br&gt;
Makes it universally and easily accessible - from code, command line, HTTP tools, etc.&lt;br&gt;&lt;br&gt;
For example, using &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/test/http-files" rel="noopener noreferrer"&gt;.http files&lt;/a&gt; in Visual Studio or the &lt;a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client" rel="noopener noreferrer"&gt;REST Client&lt;/a&gt; extension in VS Code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web-based admin panel&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
CouchDB comes with a built-in web-based admin panel called "Fauxton" for basic server administration.&lt;br&gt;&lt;br&gt;
It is easy to also install &lt;a href="https://github.com/ermouth/couch-photon" rel="noopener noreferrer"&gt;Photon&lt;/a&gt; (a much nicer admin panel alternative), which additionally lets you do ad hoc SQL queries, diff document revisions, perform backup/restore, and much more - all through a browser from anywhere.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Map/Reduce&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In addition to traditional indexes (fixed fields, sort-order, partial-condition), called "Mango indexes" in CouchDB, CouchDB also has a very powerful Map/Reduce feature.&lt;br&gt;&lt;br&gt;
Map/Reduce lets you define keys and conditions through JavaScript functions (the "map" part of map/reduce) giving you a lot more flexibility.&lt;br&gt;&lt;br&gt;
To sum (or do other calculations on) the values in a column for a set of rows, with SQL, the server needs to go through every one of those rows every time you execute the query. With CouchDB, the server can do most of this work ahead of time along with the indexing (the "reduce" part of map/reduce), making such queries much more efficient. And again, because this is defined through JavaScript functions, you get a lot more flexibility.&lt;br&gt;&lt;br&gt;
Map/Reduce is also what lets you put invoice-lines in the same document (record) as the rest of the invoice, while still being able to index and query those lines individually.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No schema&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
With SQL, I have to constantly tweak the database tables and columns as I write my code.&lt;br&gt;
And I have to "normalize" my data (= structure it in a weird way - like putting invoice-lines in a separate table).&lt;br&gt;
The schema-less nature of CouchDB makes life so much easier in this regard.&lt;br&gt;&lt;br&gt;
My data is already validated and structured by the application (like server-side code on web-sites), so repeating this in the database is just extra work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No cloud vendor lock-in&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Because CouchDB is software that runs on your own hardware (or VM / container), you can easily move your data to another CouchDB instance running anywhere - on-premise or at any cloud provider - using the built-in replication feature.&lt;br&gt;&lt;br&gt;
You cannot do this with Amazon DynamoDB, Microsoft Azure Cosmos DB, or Google Firestore / Bigtable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Free / open-source / Apache&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Who doesn't like free? Being under the Apache Foundation means that it will stick around for a while.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What about ad-hoc queries?
&lt;/h3&gt;

&lt;p&gt;One advantage of SQL databases is that you can always write a quick SQL query to get out your data filtered, and sorted just the way you want - without regard to indexes (it may take a while but it will run).&lt;br&gt;&lt;br&gt;
With Photon (see above), you can actually do the same with CouchDB - query it using basic SQL statements.&lt;br&gt;&lt;br&gt;
CouchDB also has its own JSON-based query language called "Mango" (which is what Couch Photon SQL queries use behind the scenes).&lt;br&gt;&lt;br&gt;
You are NOT forced to create map/reduce views for all queries - like some internet posts would have you believe.  &lt;/p&gt;

&lt;h3&gt;
  
  
  What about joins?
&lt;/h3&gt;

&lt;p&gt;You won't need joins as much with CouchDB because of "de-normalization" (invoice-lines in same document as invoice itself).&lt;br&gt;&lt;br&gt;
But you can have &lt;a href="https://docs.couchdb.org/en/stable/ddocs/views/joins.html" rel="noopener noreferrer"&gt;linked documents&lt;/a&gt; which is a simple way of doing 1st level joins.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about the horror stories of slow re-indexing when changing design documents?
&lt;/h3&gt;

&lt;p&gt;I read them too. So I ran some tests.&lt;br&gt;&lt;br&gt;
I first created a CouchDB database with 500,000 documents (real-life data copied from a SQL database), then created a new design document / view, then did a query against it, then waited... for about 1 minute, and then it was all indexed and responding crazy fast. And this was with CouchDB running on my old laptop!&lt;br&gt;&lt;br&gt;
This does not scare me.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about rumors that CouchDB is a disk hog?
&lt;/h3&gt;

&lt;p&gt;I read them too. So I ran some tests.&lt;br&gt;&lt;br&gt;
Compared to SQL Server, it does use almost twice as much disk space for data (after compression / compacting).&lt;br&gt;
This is understandable given that "column names" are stored in every "row".&lt;br&gt;&lt;br&gt;
And it seems that you need to schedule compression on a regular basis to prevent data files from "exploding".&lt;br&gt;&lt;br&gt;
For me, this is an acceptable tradeoff.&lt;br&gt;&lt;br&gt;
One of the original selling points of SQL and "normalization" was preservation of disk space - because disks used to be slow and expensive. Neither is the case today.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about MongoDB?
&lt;/h3&gt;

&lt;p&gt;I did of course also look at &lt;a href="https://www.mongodb.com" rel="noopener noreferrer"&gt;MongoDB&lt;/a&gt; - the more popular choice amongst No-SQL databases and similar to CouchDB in concept.&lt;br&gt;&lt;br&gt;
From what I gather, MongoDB has better performance and tooling, but it is also more complex and not as lightweight (it uses much more memory).&lt;br&gt;&lt;br&gt;
MongoDB uses a proprietary client access protocol and thus needs proprietary client libraries/drivers (vs just using HTTP REST).&lt;br&gt;&lt;br&gt;
It uses BSON rather than JSON.&lt;br&gt;&lt;br&gt;
MongoDB is a 572MB download - CouchDB is 66MB.&lt;br&gt;&lt;br&gt;
From what I have seen, CouchDB is plenty fast, and I do prefer simple and lightweight over complex and heavy :-)&lt;/p&gt;

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

&lt;p&gt;I have been "playing" with CouchDB on a few side projects, and the experience has just been fantastic so far.&lt;br&gt;&lt;br&gt;
I am now ready to go all-in :-)&lt;/p&gt;

</description>
      <category>couchdb</category>
      <category>sqlserver</category>
      <category>mongodb</category>
      <category>database</category>
    </item>
    <item>
      <title>Evaluating VB.NET to C# converters</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Tue, 02 Jan 2024 11:03:00 +0000</pubDate>
      <link>https://forem.com/jesperhoy/evaluating-vbnet-to-c-converters-391n</link>
      <guid>https://forem.com/jesperhoy/evaluating-vbnet-to-c-converters-391n</guid>
      <description>&lt;p&gt;I am converting several projects from VB.NET to C#. In preparation for this, I tested 4 different VB.NET to C# conversion tools with sample code from those projects.&lt;/p&gt;

&lt;p&gt;I encountered several problems, and I thought that I'd share my findings with others who might be going through the same process (I initially paid for the wrong tool by mistake).&lt;/p&gt;

&lt;p&gt;Converting the following tiny VB.NET application with each of the tools demonstrates some of the problems that I ran into:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv39ahcnhae51ft0v7gay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv39ahcnhae51ft0v7gay.png" alt="vb.net code" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Telerik Code Converter
&lt;/h2&gt;

&lt;p&gt;Free online tool: &lt;a href="https://converter.telerik.com" rel="noopener noreferrer"&gt;https://converter.telerik.com&lt;/a&gt; (tested on Dec 30th, 2023)&lt;/p&gt;

&lt;p&gt;On the first attempt, this converter "crashes" (included a long error message in the output) because of the date literal &lt;code&gt;#1970-01-02#&lt;/code&gt; in line 6 of the VB code.&lt;/p&gt;

&lt;p&gt;After changing the date literal in the VB code to &lt;code&gt;New DateTime(1970,1,2)&lt;/code&gt;, I get this output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4n1k30oogebw1ydaq3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv4n1k30oogebw1ydaq3r.png" alt="c# code" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only remaining issue is in line 11 of the output - the VB ternary operator &lt;code&gt;if(...,...,...)&lt;/code&gt; was correctly converted to C# &lt;code&gt;...?...:...&lt;/code&gt; - but missing surrounding parentheses. This could lead to some nasty bugs...&lt;br&gt;&lt;br&gt;
After adding the missing parentheses, this C# code compiled and ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  ICSharpCode Converter
&lt;/h2&gt;

&lt;p&gt;Free online tool: &lt;a href="https://icsharpcode.github.io/CodeConverter" rel="noopener noreferrer"&gt;https://icsharpcode.github.io/CodeConverter&lt;/a&gt; (tested on Dec 30th, 2023)&lt;br&gt;&lt;br&gt;
Also available as a Visual Studio extension: &lt;a href="https://marketplace.visualstudio.com/items?itemName=SharpDevelopTeam.CodeConverter&amp;amp;ssr=false#overview" rel="noopener noreferrer"&gt;Code Converter (VB - C#)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2jxndsdwywp9bndhyui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2jxndsdwywp9bndhyui.png" alt="c# code" width="606" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice lines 10-12 (corresponding to lines 6-8 of the VB code). The code for those lines is just gone! This appears to have something to do with the lambda function as it does not happen in regular functions.&lt;br&gt;&lt;br&gt;
The same thing happens in the Visual Studio extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  VBConversions
&lt;/h2&gt;

&lt;p&gt;Windows desktop application from &lt;a href="https://vbconversions.com" rel="noopener noreferrer"&gt;https://vbconversions.com&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Testing with version 5.08 (paid $295 for "Lifetime License") using the "Snippet converter" function.&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6id22w9y2ptki1q2po3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6id22w9y2ptki1q2po3b.png" alt="c# code" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This converter also appears to have a problem with lambda functions (output lines 8/9).&lt;br&gt;&lt;br&gt;
Also, any variable declaration with an initializer of an inferred type (&lt;code&gt;Dim VariableName =...&lt;/code&gt;) is converted to &lt;code&gt;object VariableName =...&lt;/code&gt; (output line 13). The other converters output &lt;code&gt;var VariableName= ...&lt;/code&gt; which works a lot better.&lt;br&gt;&lt;br&gt;
Also, the VB ternary operator &lt;code&gt;if(...,...,...)&lt;/code&gt; was not converted at all (output line 14).&lt;br&gt;&lt;br&gt;
Also, it does not handle VB multi-line statements correctly (output lines 14-15).&lt;/p&gt;

&lt;h2&gt;
  
  
  Instant C# .
&lt;/h2&gt;

&lt;p&gt;Windows desktop application from &lt;a href="https://www.tangiblesoftwaresolutions.com/product_details/vb-to-csharp-converter.html" rel="noopener noreferrer"&gt;https://www.tangiblesoftwaresolutions.com/product_details/vb-to-csharp-converter.html&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Testing with version 23.12.21 (paid $159 for a "One-Year Subscription License") using the"Code Snippets" function.&lt;/p&gt;

&lt;p&gt;Output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk8scuoxpfmu3agw12lj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnk8scuoxpfmu3agw12lj.png" alt="c# code" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compiled and ran correctly on the first attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Looks like I will be using Instant C#&lt;/p&gt;

</description>
      <category>vb</category>
      <category>vbnet</category>
      <category>visualbasic</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Revisiting site search + SQLite as a search engine</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Thu, 06 Apr 2023 11:31:00 +0000</pubDate>
      <link>https://forem.com/jesperhoy/revisiting-site-search-sqlite-as-a-search-engine-14km</link>
      <guid>https://forem.com/jesperhoy/revisiting-site-search-sqlite-as-a-search-engine-14km</guid>
      <description>&lt;p&gt;Back in 2017, I researched adding search functionality to our websites (&lt;a href="https://jesperhoy.dev/p38/adding-search-to-our-websites" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;) and ended up going with &lt;a href="https://www.zoomsearchengine.com" rel="noopener noreferrer"&gt;Zoom Search Engine&lt;/a&gt;.&lt;br&gt;
This has worked OK, but not without issues...&lt;/p&gt;

&lt;p&gt;So, as I was re-structuring our web-sites, I decided to have another look at the options for this.&lt;/p&gt;
&lt;h3&gt;
  
  
  Full "site search engine as a service"
&lt;/h3&gt;

&lt;p&gt;(= crawler + index hosting + search UI / widget + management / statistics)&lt;/p&gt;

&lt;p&gt;I had a quick second look at the services from my 2017 round (prices for 400 documents):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AddSearch - &lt;a href="https://www.addsearch.com" rel="noopener noreferrer"&gt;https://www.addsearch.com&lt;/a&gt; - $29/month&lt;/li&gt;
&lt;li&gt;Site Search 360 - &lt;a href="https://sitesearch360.com" rel="noopener noreferrer"&gt;https://sitesearch360.com&lt;/a&gt; - $9/month&lt;/li&gt;
&lt;li&gt;Cludo &lt;a href="https://www.cludo.com" rel="noopener noreferrer"&gt;https://www.cludo.com&lt;/a&gt; - $5000/month&lt;/li&gt;
&lt;li&gt;Swiftype - &lt;a href="https://swiftype.com" rel="noopener noreferrer"&gt;https://swiftype.com&lt;/a&gt; - $79/month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also found a couple of new ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Findberry - &lt;a href="https://www.findberry.com" rel="noopener noreferrer"&gt;https://www.findberry.com&lt;/a&gt; - $10/month.&lt;/li&gt;
&lt;li&gt;Expertrec - &lt;a href="https://www.expertrec.com" rel="noopener noreferrer"&gt;https://www.expertrec.com&lt;/a&gt; - $49/month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few aspects (including price) of SiteSearch360 seemed more interesting than the others, so I decided to give this a try.&lt;/p&gt;

&lt;p&gt;I got this fully implemented and running on our websites.&lt;/p&gt;

&lt;p&gt;One thing I really liked was the way you tell SiteSearch360 which parts of web-pages to index using CSS query selectors (or XPath). I eventually ended up using this concept in my solution (see below).&lt;/p&gt;

&lt;p&gt;The crawling and management/statistics were also nice, but I wasn't real happy with the search UI integration.&lt;/p&gt;
&lt;h3&gt;
  
  
  Full text search as a service
&lt;/h3&gt;

&lt;p&gt;(= index hosting + management / statistics - but no crawler)&lt;/p&gt;

&lt;p&gt;I looked at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Algolia - &lt;a href="https://www.algolia.com" rel="noopener noreferrer"&gt;https://www.algolia.com&lt;/a&gt; - 10,000 free searches/month, then $0.50/1,000 searches.&lt;/li&gt;
&lt;li&gt;Meilisearch - &lt;a href="https://www.meilisearch.com" rel="noopener noreferrer"&gt;https://www.meilisearch.com&lt;/a&gt; - 10,000 free searches/month, then $0.25/1,000 searches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are much cheaper than the "full site search engine as a service" services above, but also require more programming (crawler + search UI) to get up and running.&lt;/p&gt;

&lt;p&gt;In recent years I've noticed Algolia search on many programming related web-sites (possibly because it is free for open source projects).&lt;/p&gt;

&lt;p&gt;I was curious enough that I programmed my own simple crawler (including the CSS query selector trick I learned from SiteSearch360) and tried uploading the data to Algolia - just to get a feel for how all this works.&lt;/p&gt;

&lt;p&gt;Once I had extracted the text data (with my crawler) and converted it into JSON, the upload process was simple enough and searching worked very well within the Algolia management interface. &lt;/p&gt;

&lt;p&gt;I didn't take this any further, because at this point I realized that the output from my crawler could also easily be fed into other full text search solutions...&lt;/p&gt;
&lt;h3&gt;
  
  
  Dedicated full text search server software
&lt;/h3&gt;

&lt;p&gt;I had a cursory look at &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Solr - &lt;a href="https://solr.apache.org" rel="noopener noreferrer"&gt;https://solr.apache.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sphinx - &lt;a href="https://sphinxsearch.com" rel="noopener noreferrer"&gt;https://sphinxsearch.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are open source / free, advanced dedicated full text search server software programs (running on your own server) designed for enterprise searching with thousands of documents.&lt;/p&gt;

&lt;p&gt;But I really don't want to run and maintain yet another application on our server (one of my issues with Zoom Search Engine), and they seem like overkill as my largest website only has around 400 documents.&lt;/p&gt;
&lt;h3&gt;
  
  
  Database server full text search
&lt;/h3&gt;

&lt;p&gt;All the major database servers (Microsoft SQL Server, MySQL, PostgreSQL, MongoDB) include some type of full text search functionality which could potentially be used for site search.&lt;/p&gt;

&lt;p&gt;Of course as with the "Full text search as a service" providers, I would need to program my own crawler and search UI.&lt;/p&gt;

&lt;p&gt;I already run Microsoft SQL server, so I had a look at the full text search features in this. Unfortunately I couldn't find any easy way to get out "snippets" of the indexed text with highlighting of query terms - which is pretty essential for displaying search results.&lt;/p&gt;

&lt;p&gt;As for the other database servers listed above - again - I really don't want to run and maintain yet another application on our server, so I did not consider these further.&lt;/p&gt;
&lt;h3&gt;
  
  
  SQLite
&lt;/h3&gt;

&lt;p&gt;In recent years, I have been reaching for &lt;a href="https://www.sqlite.org" rel="noopener noreferrer"&gt;SQLite&lt;/a&gt; in more and more situations where I would have used Microsoft SQL server in the past. SQLite is often both faster and easier to work with.&lt;/p&gt;

&lt;p&gt;Turns out that SQLite also does &lt;a href="https://www.sqlite.org/fts5.html" rel="noopener noreferrer"&gt;full text search&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And unlike Microsoft SQL Server, it has built in &lt;a href="https://www.sqlite.org/fts5.html#the_highlight_function" rel="noopener noreferrer"&gt;"highlight"&lt;/a&gt; and &lt;a href="https://www.sqlite.org/fts5.html#the_snippet_function" rel="noopener noreferrer"&gt;"snippet"&lt;/a&gt; functions for generating text for the search result listing.&lt;/p&gt;

&lt;p&gt;So combining my own crawler (incorporating the "CSS query selector" trick from SiteSearch360), SQLite full text search, and some existing search UI template/code from my Zoom Search Engine integration, I was quickly able to put together a full site search system - without introducing any new paid services, or any new applications running on our server (SQLite is just a library / .dll file already in use on our web-sites).&lt;/p&gt;

&lt;p&gt;Indeed compared to the previous Zoom Search Engine setup, the complexity has been reduced, and I have much better control over the crawling process.&lt;/p&gt;

&lt;p&gt;And performance is amazing - searches execute in 1-2 milliseconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes for using SQLite full text search with .Net&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I use the official SQLite NuGet package &lt;a href="https://www.nuget.org/packages/System.Data.SQLite.Core" rel="noopener noreferrer"&gt;System.Data.SQLite.Core&lt;/a&gt; which includes the full text search functionality (FTS5).&lt;br&gt;
I have read that full text search is NOT included in Microsoft's fork "Microsoft.Data.Sqlite".&lt;/p&gt;

&lt;p&gt;Also the full text search functionality is an "extension", which needs to be enabled and loaded (after opening the database connection) by calling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connection.EnableExtensions(True)
connection.LoadExtension("SQLite.Interop.dll", "sqlite3_fts5_init")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>sqlite</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>My thoughts on Mithril.js</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Tue, 03 May 2022 13:21:33 +0000</pubDate>
      <link>https://forem.com/jesperhoy/my-thoughts-on-mithriljs-319f</link>
      <guid>https://forem.com/jesperhoy/my-thoughts-on-mithriljs-319f</guid>
      <description>&lt;p&gt;&lt;a href="https://mithril.js.org" rel="noopener noreferrer"&gt;Mitril.js&lt;/a&gt; is a client-side JavaScript framework (similar to React, Vue, Svelte, etc.)&lt;/p&gt;

&lt;p&gt;My current go-to client-side framework is &lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; - which is amazing. The only real downside is that it cannot be used without build-tools.&lt;/p&gt;

&lt;p&gt;So when I noticed that Mithril.js can be used without any build-tools (plain vanilla JavaScript) AND it has a tiny runtime (10 kB) AND it has real components - I was intrigued.&lt;/p&gt;

&lt;p&gt;Having (real) components, sets it apart from other no-build / small runtime frameworks (like &lt;a href="https://alpinejs.dev" rel="noopener noreferrer"&gt;Alpine.js&lt;/a&gt; and &lt;a href="https://github.com/vuejs/petite-vue" rel="noopener noreferrer"&gt;Vue-Petite&lt;/a&gt;), because this allows for the creation of advanced full-size SPAs.&lt;/p&gt;

&lt;p&gt;Basically Mithril.js covers both scenarios - "progressive enhancement" (like Alpine.js and Vue-Petite) and full-size SPA (like React, Vue, Svelte).&lt;/p&gt;

&lt;p&gt;Could this be the one?&lt;br&gt;
The single tool for everything?&lt;br&gt;
The holy grail?&lt;/p&gt;

&lt;p&gt;So I played around with Mithil.js over the weekend, to see what it was like.&lt;/p&gt;

&lt;p&gt;With Mithril.js, you generate HTML using a &lt;a href="https://github.com/hyperhype/hyperscript" rel="noopener noreferrer"&gt;hyperscript&lt;/a&gt; dialect 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;m("div", {style:"color:red"},
  m("a", {href:"/page2"}, "click here"))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which generates&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color:red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/page2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;click here&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can use &lt;a href="https://facebook.github.io/jsx" rel="noopener noreferrer"&gt;JSX&lt;/a&gt; syntax (like with React), but then you need build-tools.&lt;/p&gt;

&lt;p&gt;Another option is using the &lt;a href="https://github.com/developit/htm" rel="noopener noreferrer"&gt;htm&lt;/a&gt; library to generate the hyperscript - which would allow the use of JavaScript &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" rel="noopener noreferrer"&gt;template literals&lt;/a&gt; for a development style closer to JSX or the HTML templates used with Vue / Svelte.&lt;br&gt;
The problem with this is that the templates would be re-compiled (by htm) on every single interaction with the app - which I imagine would seriously slow things down.&lt;/p&gt;

&lt;p&gt;So the way I see it, the optimal way to use Mithril.js (without build tools) is to write the hyperscript style code by hand - which actually isn't too difficult once you get used to it.&lt;br&gt;
The biggest problem is keeping track of closing parentheses/brackets with deeply nested elements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reactivity / UI updates
&lt;/h3&gt;

&lt;p&gt;Unlike the major frameworks (Reach, Vue, Svelte, etc.) Mithril.js does not update the UI in response to changes in application state / variables.&lt;/p&gt;

&lt;p&gt;Instead, it updates the UI after each UI event has been handled (or whenever you tell it to by calling &lt;code&gt;m.redraw()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;On each update it re-renders everything to a new virtual DOM, then diffs this to the previous version, and finally updates the real DOM with the differences.&lt;/p&gt;

&lt;p&gt;This works surprisingly well. Dynamic and automatic UI updates - without any kind of state tracking. Very nice!&lt;/p&gt;

&lt;h3&gt;
  
  
  Test project
&lt;/h3&gt;

&lt;p&gt;I recently created a small SPA with Svelte here: &lt;a href="https://simpledns.plus/dmarc-wizard" rel="noopener noreferrer"&gt;https://simpledns.plus/dmarc-wizard&lt;/a&gt; (an in-browser tool to generate DMARC-records - an e-mail security thing).&lt;/p&gt;

&lt;p&gt;So to try out Mithril.js, I re-created the same SPA using Mithril.js - see &lt;a href="https://simpledns.plus/dmarc-wizard-mithril" rel="noopener noreferrer"&gt;https://simpledns.plus/dmarc-wizard-mithril&lt;/a&gt; (should look and work exactly the same).&lt;/p&gt;

&lt;p&gt;The JavaScript source code (which is also the production code) is available at &lt;a href="https://simpledns.plus/scripts/dmarc-wizard-mithril.js" rel="noopener noreferrer"&gt;https://simpledns.plus/scripts/dmarc-wizard-mithril.js&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Things that I really like
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ability to cover both "progressive enhancement" and full-size SPA scenarios (see above).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That no state management system is needed / included (see above).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Everything, even HTML, is expressed in plain JavaScript. No "magic". This makes debugging in the browser much easier, makes code refactoring easier, and reduces the need for VSCode extensions etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The possibility of using tiny render functions, for things that would require a separate a component (and file) in Svelte. This encourages and makes it a lot easier to be adhere to the DRY principle (don't repeat yourself).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Things that really should be removed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mihtril.js (v. 2.0.4) includes a wrapper for ajax calls based on XHR. They even make a selling point out of this in the very first sentence on the web-site.&lt;br&gt;&lt;br&gt;
This is 2022 - all browsers now have &lt;code&gt;fetch&lt;/code&gt;, and support async/await making &lt;code&gt;fetch&lt;/code&gt; easy to use.&lt;br&gt;
Promoting XHR as a feature makes it sound rather outdated.&lt;br&gt;
And I would rather have an even leaner base framework.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mithril.js also includes a routing feature based on "hash-bang URLs" - also a selling point in the first sentence on the web-site.&lt;br&gt;&lt;br&gt;
This is of course nice to have - but routing is not needed for progressive enhancements scenarios (where I see Mithril.js having potential), and developers might prefer other routing schemes (like non-hash history-push/pop).&lt;br&gt;
So please remove this / make it optional (making the base framework even leaner).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mithril.js includes a polyfill for Promise.&lt;br&gt;&lt;br&gt;
This is 2022 - all browsers now support promise.&lt;br&gt;
So please remove this / make it optional (making the base framework even leaner).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Mithril.js has some really nice and unique features.&lt;/p&gt;

&lt;p&gt;But, while the hyperscript syntax is doable for small widgets, I wouldn't want to use it in larger projects.&lt;/p&gt;

&lt;p&gt;I just don't think that the hyperscript syntax is as readable as for example Vue / Svelte templates (standard HTML with as few additions).&lt;/p&gt;

&lt;p&gt;And keeping track of those closing parentheses/brackets with deeply nested elements, just drove me nuts.&lt;/p&gt;

&lt;p&gt;As the JSX docs put it (about &lt;a href="https://facebook.github.io/jsx/#sec-why-not-JXON" rel="noopener noreferrer"&gt;something else&lt;/a&gt;, but with the same issue):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unfortunately, the balanced braces do not give great syntactic hints for where an element starts and ends in large trees. Balanced named tags is a critical syntactic feature of the XML-style notation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So to use Mithril.js, I would probably need JSX, which requires build tools. And if I have to use build tools anyway, then I still prefer Svelte...&lt;/p&gt;

</description>
      <category>mithriljs</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Tasty Vanilla JS - 4 tips</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Thu, 21 Apr 2022 09:20:31 +0000</pubDate>
      <link>https://forem.com/jesperhoy/tasty-vanilla-js-4-tips-1afd</link>
      <guid>https://forem.com/jesperhoy/tasty-vanilla-js-4-tips-1afd</guid>
      <description>&lt;p&gt;[Definitions: "Vanilla JS" = plain old JavaScript with no framework, and no build tools etc., "Big-framework-X" = React, VueJS, Angular, Svelte, etc.]&lt;/p&gt;

&lt;p&gt;When building light weight interactivity on a webpage, I often find myself in this space between doing it in Vanilla JS or pulling out my big-framework-X of choice.&lt;/p&gt;

&lt;p&gt;Mini-frameworks like &lt;a href="https://alpinejs.dev" rel="noopener noreferrer"&gt;AlpineJS&lt;/a&gt;, &lt;a href="https://github.com/vuejs/petite-vue" rel="noopener noreferrer"&gt;Vue-Petite&lt;/a&gt;, and &lt;a href="https://stimulus.hotwired.dev" rel="noopener noreferrer"&gt;Stimulus&lt;/a&gt; exist exactly to fill this space.&lt;br&gt;
But whenever I consider reaching for one of these, I usually find that the job can just as easily be done in Vanilla JS. And if it can't, it typically requires the big-framework-X anyway.&lt;/p&gt;

&lt;p&gt;Vanilla JS is of course much simpler, ships less bytes to users, and consumes fewer CPU cycles from user devices. And with Vanilla JS my development setup is much simpler too (no npm, bundlers, build-tools, etc. needed).&lt;/p&gt;

&lt;p&gt;Here are 4 small tips that might make you choose Vanilla JS more often: &lt;/p&gt;
&lt;h3&gt;
  
  
  Tip 1 - Use element IDs directly in JS code
&lt;/h3&gt;

&lt;p&gt;When big-framework-X code is compared to vanilla JS, you will often see the vanilla JS sample code littered with &lt;code&gt;document.getElementById(...)&lt;/code&gt; or &lt;code&gt;document.querySelector(...)&lt;/code&gt; statements (making the Vanilla JS version look bloated). This is pure propaganda. There is no need for this. For simple stuff (where you would typically use vanilla JS), you can just reference HTML elements using their IDs directly in JS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;checkbox&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;MyChk1&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;MyChk1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tip 2 - Use "hidden" for conditionals
&lt;/h3&gt;

&lt;p&gt;One reason that you might use big-framework-X is conditionals - some syntax which renders a section of HTML to the DOM - or not - depending on some condition. A simple Vanilla JS alternative to this is the "hidden" attribute / property. To show/hide a div (and its content), you can simply do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;MyDiv1&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;MyDiv2&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// swap which divs are visible&lt;/span&gt;
  &lt;span class="nx"&gt;MyDiv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;MyDiv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tip 3 - Template Literals
&lt;/h3&gt;

&lt;p&gt;Another reason that you might use big-framework-X is templating (like React's JSX or Vue's SFC). Vanilla JS actually comes with a powerful templating system called&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals" rel="noopener noreferrer"&gt;template literals&lt;/a&gt;, which can be utilized in much the same way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;MyDiv1&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Shopping list&lt;/span&gt;&lt;span class="dl"&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;Items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Apples&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bananas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dog food&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nx"&gt;MyDiv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/h1&amp;gt;
                    &amp;lt;ul&amp;gt;
                      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itm&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;itm&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
                    &amp;lt;ul&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tip 4 - Inline event handlers
&lt;/h3&gt;

&lt;p&gt;You may have been taught that inline event handlers are bad, and that you need to use &lt;code&gt;element.addEventListener(...)&lt;/code&gt; instead. You will often see this when big-framework-X code is compared to vanilla JS (making the Vanilla JS version look bloated).&lt;br&gt;
Once again, this is pure propaganda. For simple stuff (where you would typically use vanilla JS), inline event handlers are perfectly fine, much simpler, terse and readable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;MyDiv1&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;checkbox&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"MyDiv1.hidden=this.checked"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Combining all 4
&lt;/h3&gt;

&lt;p&gt;I re-created the obligatory (for big JS frameworks) To Do List application in Vanilla JS combining above techniques:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jesperhoy/embed/bGaOgWz?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Fully functional, yet simple and short, and it can easily compete with big-framework-X.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Deep reactivity in Svelte</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Thu, 17 Feb 2022 15:35:01 +0000</pubDate>
      <link>https://forem.com/jesperhoy/deep-reactivity-in-svelte-4h4c</link>
      <guid>https://forem.com/jesperhoy/deep-reactivity-in-svelte-4h4c</guid>
      <description>&lt;p&gt;What is "deep reactivity"? you ask.&lt;/p&gt;

&lt;p&gt;From the &lt;a href="https://vuejs.org/guide/essentials/reactivity-fundamentals.html#deep-reactivity" rel="noopener noreferrer"&gt;Vue.js documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Vue, state is deeply reactive by default. This means you can expect changes to be detected even when you mutate nested objects or arrays&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Vue.js, when using the &lt;a href="https://vuejs.org/api/options-state.html#data" rel="noopener noreferrer"&gt;data option&lt;/a&gt; or the &lt;a href="https://vuejs.org/api/reactivity-core.html#reactive" rel="noopener noreferrer"&gt;reactive() function&lt;/a&gt;, a JavaScript object is transformed into an object where each individual property (including those on nested objects) is reactive. Each property in effect becomes its own "store".&lt;/p&gt;

&lt;p&gt;In Svelte, there is no way to make object properties reactive like that. Reactivity is only available for local variables declared at the root level of each component.&lt;br&gt;&lt;br&gt;
A reactive "store" from outside the component, must first be assigned to a local variable, and then the store value can be accessed/assigned using a "$" prefix on the local variable.&lt;/p&gt;

&lt;p&gt;Most of the time, the Svelte's reactivity model is entirely sufficient and very easy to use.&lt;br&gt;&lt;br&gt;
However, if you need to synchronize a large/complex JavaScript object between multiple components, views, etc. the Vue model is much more convenient.&lt;/p&gt;

&lt;p&gt;To "fix" this, I came up with a tiny helper library &lt;a href="https://github.com/jesperhoy/Svelte-ReactivePojo" rel="noopener noreferrer"&gt;"ReactivePojo"&lt;/a&gt;, which brings "deeper" reactivity to Svelte - similar to Vue.&lt;/p&gt;

&lt;p&gt;ReactivePojo lets you map a local variable in a Svelte component, to a property on any &lt;a href="https://en.wikipedia.org/wiki/Plain_old_Java_object" rel="noopener noreferrer"&gt;POJO&lt;/a&gt; (Plain Old JavaScript object) - via a custom store (honoring the &lt;a href="https://svelte.dev/docs#component-format-script-4-prefix-stores-with-$-to-access-their-values-store-contract" rel="noopener noreferrer"&gt;Svelte store contract&lt;/a&gt;) -  like this:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let v = RPStore(object, propertyName);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The property value can then be accessed/assigned using the Svelte "$" prefix syntax:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log($v);
$v = "New value";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Calling &lt;code&gt;RPStore&lt;/code&gt; will create a store for the specified object/property - unless one already exists, in which case the existing store is returned.  In other words - any call to &lt;code&gt;RPStore&lt;/code&gt; for the same object and property name, from anywhere, will always return the same store.&lt;br&gt;&lt;br&gt;
This ensures that two separate Svelte components accessing the same object/property will get the same store and thus the property value will automatically be synchronized between the components (and the underlying object).&lt;/p&gt;

&lt;p&gt;The first time &lt;code&gt;RPStore&lt;/code&gt; is called for an object/property, the property will be instrumented with getter/setter methods, so that any subsequent assignments directly to the property will also trigger reactivity - ie. subscribers to the store will be notified - and any UI using the store will be updated:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let v = RPStore(Person, "Name");
$v = "Bob"; // triggers reactive updates where $v is used
Person.Name = "Joe"; // also triggers reactive updates where $v is used
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;This is very similar to the way Vue 2 does reactivity (Vue 3 uses a different technique).&lt;/p&gt;

&lt;p&gt;To use this library in a Svelte component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
import RPStore from "./ReactivePojo.js";
import {Person} from "./MyGlobalData.js"; 
// Note: "Person" object could also come from a property, GetContext(...),  etc.
let Name = RPStore(Person, "Name");
&amp;lt;/script&amp;gt;

Name: &amp;lt;input type="text" bind:value={$Name} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution gives you reactivity at a more granular level (like with Vue) - preventing re-calculations/re-rendering based on the entire object when the value of some leaf node property changes.&lt;/p&gt;

&lt;p&gt;And just like Vue, it kind of magically makes a POJO reactive.&lt;/p&gt;

&lt;p&gt;It is actually more efficient than Vue, because it only adds reactivity to specific properties, rather than traversing and instrumenting every single property in the entire object tree.&lt;/p&gt;

&lt;p&gt;"ReactivePojo" is available at &lt;a href="https://github.com/jesperhoy/Svelte-ReactivePojo" rel="noopener noreferrer"&gt;https://github.com/jesperhoy/Svelte-ReactivePojo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Custom validity helpers for Vue.js and Svelte</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Thu, 22 Apr 2021 15:32:15 +0000</pubDate>
      <link>https://forem.com/jesperhoy/custom-validity-helpers-for-vue-js-and-svelte-3k2m</link>
      <guid>https://forem.com/jesperhoy/custom-validity-helpers-for-vue-js-and-svelte-3k2m</guid>
      <description>&lt;p&gt;HTML5 comes with a nifty feature to show custom form validation messages on form submission (see cover image).&lt;br&gt;
This looks different in each browser/OS - but it is a standard HTML5 feature, fully supported in all modern browsers.&lt;/p&gt;

&lt;p&gt;This is easy enough to use through javascript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCustomValidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;That is the wrong name!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But when using a front end framework like Vue.js or Svelte, this means that we first need to get a reference to the DOM element and make sure that the element is mounted before executing above javascript.&lt;/p&gt;

&lt;p&gt;Wouldn't it be nice if this could just be set as an attribute directly on each input element?&lt;br&gt;
Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;validity=&lt;/span&gt;&lt;span class="s"&gt;"That is the wrong name!"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how you can do just that:&lt;/p&gt;

&lt;h2&gt;
  
  
  Vue.js
&lt;/h2&gt;

&lt;p&gt;Run before loading your app, to make this globally available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;directive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;validity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setCustomValidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a Vue component (.vue file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
         &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; 
         &lt;span class="na"&gt;v-validity=&lt;/span&gt;&lt;span class="s"&gt;"name!=='joe'?'That is the wrong name!':''"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;         

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &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="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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Svelte
&lt;/h2&gt;

&lt;p&gt;In a "shared.js" file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Validity&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;val&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="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;val&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="nf"&gt;setCustomValidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&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="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newVal&lt;/span&gt;&lt;span class="p"&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="nf"&gt;setCustomValidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newVal&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;newVal&lt;/span&gt;&lt;span class="p"&gt;:&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="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;In a ".svelte" component file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
       &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="s"&gt;{name}&lt;/span&gt;
       &lt;span class="na"&gt;use:Validity=&lt;/span&gt;&lt;span class="s"&gt;{name!=='joe'?'That&lt;/span&gt; &lt;span class="na"&gt;is&lt;/span&gt; &lt;span class="na"&gt;the&lt;/span&gt; &lt;span class="na"&gt;wrong&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="err"&gt;!'&lt;/span&gt;&lt;span class="na"&gt;:&lt;/span&gt;&lt;span class="err"&gt;''}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Validity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./shared.js&lt;/span&gt;&lt;span class="dl"&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;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>vue</category>
      <category>svelte</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Svelte vs. Vue.js</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Thu, 22 Apr 2021 11:15:37 +0000</pubDate>
      <link>https://forem.com/jesperhoy/svelte-vs-vue-js-2an5</link>
      <guid>https://forem.com/jesperhoy/svelte-vs-vue-js-2an5</guid>
      <description>&lt;p&gt;&lt;em&gt;Updated February 18th 2022 to reflect new features of Vue 3 and stuff I've learned from experience in the meantime.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I have been using &lt;a href="https://vuejs.org" rel="noopener noreferrer"&gt;Vue.js&lt;/a&gt; for client side browser stuff for a few years and I am very happy with it. However, I have been curious about &lt;a href="https://svelte.dev" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; for a while, and as I had an idea for a new project where Svelte might be a better fit than Vue, I decided to take a closer look.&lt;/p&gt;

&lt;p&gt;Also, I have a number of ongoing projects in Vue 2, and with the significant &lt;a href="https://v3-migration.vuejs.org/breaking-changes/" rel="noopener noreferrer"&gt;breaking changes&lt;/a&gt; in Vue 3, &lt;br&gt;
now might be a good time to explore alternatives, &lt;br&gt;
as moving to a different framework might not be much more work than the Vue upgrades that I am facing anyway.&lt;/p&gt;

&lt;p&gt;The following is based on my own experience with Svelte and Vue:&lt;/p&gt;
&lt;h2&gt;
  
  
  In favor of Svelte
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No runtime&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The Vue runtime is not that big, but still significant for smaller "apps".&lt;br&gt;&lt;br&gt;
Consider, for example, the code to validate a simple contact form. Here the Vue runtime would be disproportional huge for the functionality provided.&lt;br&gt;&lt;br&gt;
Small Svelte apps compile to just a few kBs and need no runtime.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Two-way property binding&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In Svelte this is simple (&lt;code&gt;bind:propname={variable}&lt;/code&gt;) which I found very convenient. In Vue.js it requires emitting events and more code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Terser and more readable attribute value binding&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
With Svelte, you can interpolate dynamic values anywhere in an attribute value using &lt;code&gt;{...}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="/items?type={varType}&amp;amp;page={varPage}"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;With Vue.js, the attribute name must be prefixed with &lt;code&gt;v-bind:&lt;/code&gt; or &lt;code&gt;:&lt;/code&gt; (shorthand), and the entire attribute value is then evaluated as JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a :href="'/items?type=' + varType + '&amp;amp;page=' + varPage"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simpler to declare reactive variables&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
In Svelte you simply declare a variable in the script root (like &lt;code&gt;let x=0&lt;/code&gt;) and it is automatically reactive.&lt;br&gt;&lt;br&gt;&lt;br&gt;
In Vue, for a variable to be reactive, it must either be defined as a property on the &lt;code&gt;data&lt;/code&gt; object (Options API), or be created using the &lt;code&gt;ref()&lt;/code&gt; or &lt;code&gt;reactive()&lt;/code&gt; function (Composition API).&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simpler to declare props&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
In Svelte you simply declare a variable in the script root and prefix it with &lt;code&gt;export&lt;/code&gt; (like &lt;code&gt;export let x=0&lt;/code&gt;).&lt;br&gt;&lt;br&gt;&lt;br&gt;
In Vue, to make a component property, you need to define it as a property on the &lt;code&gt;props&lt;/code&gt; object (Options API), or through the &lt;code&gt;defineProps()&lt;/code&gt; method (Composition API).&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;$:&lt;/code&gt; label&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
The Svelte &lt;code&gt;$:&lt;/code&gt; label makes the following script block re-run anytime a reactive variable used within that block changes.&lt;br&gt;&lt;br&gt;&lt;br&gt;
This is similar to Vue &lt;code&gt;computed&lt;/code&gt; and &lt;code&gt;watch&lt;/code&gt; blocks, but simpler, more convenient, and much terser syntax.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Raw html rendering not tied to an HTML element&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
Svelte: &lt;code&gt;{@html HtmlString}&lt;/code&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
Vue: &lt;code&gt;&amp;lt;div v-html="HtmlString"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;No this. this. this. / .value .value .value&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
Unlike Vue, in Svelte you don't need to prefix everything with &lt;code&gt;this.&lt;/code&gt; in code blocks to get at anything else within the same component.&lt;br&gt;&lt;br&gt;&lt;br&gt;
This is also a constant cause of errors in Vue for me. Template in-line script does not need this, and so whenever you move code between template and code blocks and forget to fix this - boom.&lt;br&gt;&lt;br&gt;&lt;br&gt;
In Vue 3, if you use the Composition API, you can avoid &lt;code&gt;this.&lt;/code&gt; inside the "setup" function. But you still have to qualify access to reactive variable values like Refs - so &lt;code&gt;this.VarName&lt;/code&gt; becomes &lt;code&gt;VarName.value&lt;/code&gt; - not much better.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
Svelte is faster at updating the UI - supposedly because it doesn't use a "virtual DOM" (like Vue, React, Angular, etc.).&lt;br&gt;&lt;br&gt;&lt;br&gt;
Using browser performance tools - the measured difference is significant.&lt;br&gt;&lt;br&gt;&lt;br&gt;
Without such tools - it is hard to tell the difference - Vue is certainly fast enough.&lt;br&gt;&lt;br&gt;&lt;br&gt;
I imagine this being an advantage when coding for low powered devices.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  In favor of Vue
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Deep" reactivity&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In Vue.js, when using the &lt;a href="https://vuejs.org/api/options-state.html#data" rel="noopener noreferrer"&gt;data option&lt;/a&gt; or the &lt;a href="https://vuejs.org/api/reactivity-core.html#reactive" rel="noopener noreferrer"&gt;reactive() function&lt;/a&gt;, a JavaScript object is transformed into an object where each individual property (including those on nested objects) is reactive. Each property in effect becomes its own "store".&lt;br&gt;&lt;br&gt;
This is very convenient and simple to work with.&lt;br&gt;&lt;br&gt;
In Svelte, variables declared at a script block root are reactive (based on assignment) and so are explicitly defined "stores". But it is not "deep", meaning that assigning a value to a leaf node on a larger object, will trigger re-calculation/re-rendering based on an assumption that the whole object changed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client side template compilation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Smaller Vue "apps" can be included as source on a web-page directly without any pre-processing / build tools.&lt;br&gt;&lt;br&gt;
For web-pages (not "apps") where you need just a little bit of reactivity (like order forms), this is perfect.&lt;br&gt;
No need to run a compiler/bundler etc.&lt;br&gt;&lt;br&gt;
Another cool thing about this is, that it allows you to  put dynamically server side rendered html/data directly inside a Vue template, mixing server and client side processing very nicely.&lt;br&gt;&lt;br&gt;
I have personally used this quite a lot, and the beauty of this was exactly the thing got me started with Vue in the first place.&lt;br&gt;&lt;br&gt;
There is a special version of Vue optimized for above scenario - &lt;a href="https://github.com/vuejs/petite-vue" rel="noopener noreferrer"&gt;Petite-Vue&lt;/a&gt;. Another similar option for this is &lt;a href="https://alpinejs.dev/" rel="noopener noreferrer"&gt;Alpine.js&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
This is not possible with Svelte. Svelte apps must always be compiled with a build tool.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ecosystem&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Vue is more established and enjoys a much larger selection of component libraries, StackOverflow answers, blogs posts, etc. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tie
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single file components (SFC)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Both have this - which is just awesome.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Great documentation web-sites&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Both have this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero indentation / no curly brace mess&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
In both Svelte and Vue, you can write code at zero indentation enclosed by no curly braces, making code clean and easy to read.&lt;br&gt;&lt;br&gt;
Vue 2 required at least 2-3 levels of indentation before you write any actual program code, but this was "fixed" in Vue 3 with the &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple root elements in components - a.k.a. "fragments"&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Both support this.&lt;br&gt;&lt;br&gt;
In Vue 2, you could only have one root element, but this was "fixed" in Vue 3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Browser DevTools&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Browser (Chrome/Firefox) "DevTools" are available for both Svelte and Vue.js, and with both tools, you can browse the live component hierarchy and see and change component property values.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bundle size matters
&lt;/h2&gt;

&lt;p&gt;Bundle size for small apps in certainly smaller with Svelte because there is no runtime.&lt;/p&gt;

&lt;p&gt;But the bundle size grows faster for Svelte apps than for Vue apps - because the Svelte compiler adds stuff to the code (mainly for reactivity), while with Vue, code is largely left as is.&lt;/p&gt;

&lt;p&gt;It appears that at some point around "medium sized" app, compiled Svelte apps could become bigger than Vue apps including runtime.&lt;/p&gt;

&lt;p&gt;I recently did a small SPA (Danish budget calculator) based on Vue, and figured that it would be a nice test to convert this to Svelte.&lt;br&gt;
I copied the .vue files to a new Svelte project, renamed the files .svelte, and then manually massaged them into the Svelte syntax.&lt;br&gt;
The source code (Vue + Svelte) is available at: &lt;a href="https://github.com/jesperhoy/Mit-Budget.dk" rel="noopener noreferrer"&gt;https://github.com/jesperhoy/Mit-Budget.dk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Minified and gzipped, the javascript for the original Vue version (&lt;a href="https://mit-budget.dk/vue" rel="noopener noreferrer"&gt;https://mit-budget.dk/vue&lt;/a&gt;) is 9.2kb + 23.6kB Vue runtime = 32.8kB total (Note: this was based on Vue 2 - the Vue 3 runtime is significantly larger).&lt;br&gt;&lt;br&gt;
The Svelte version (&lt;a href="https://mit-budget.dk/svelte" rel="noopener noreferrer"&gt;https://mit-budget.dk/svelte&lt;/a&gt;) is 19.2kB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Rich-Harris" rel="noopener noreferrer"&gt;Rich Harris&lt;/a&gt; asked the &lt;a href="https://konmari.com" rel="noopener noreferrer"&gt;Marie Kodo&lt;/a&gt; inspired question "Does this code spark joy?"&lt;/p&gt;

&lt;p&gt;Back when I discovered Vue - this really was a game changer for me - and it did spark a lot of joy :-)&lt;br&gt;&lt;br&gt;
I imagine many programmers feel this way when they first "get" the reactive UI model (be it in Vue, React, Angluar, etc.).&lt;/p&gt;

&lt;p&gt;Vue still sparks joy, but Svelte does so even more :-)&lt;/p&gt;

&lt;p&gt;I especially like the simpler and terser Svelte syntax, and not having to distribute a runtime.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>svelte</category>
      <category>vue</category>
    </item>
    <item>
      <title>Simple Deploy</title>
      <dc:creator>Jesper Høy</dc:creator>
      <pubDate>Mon, 12 Apr 2021 11:33:00 +0000</pubDate>
      <link>https://forem.com/jesperhoy/simple-deploy-497g</link>
      <guid>https://forem.com/jesperhoy/simple-deploy-497g</guid>
      <description>&lt;p&gt;Today I launched a new open source project "Simple Deploy" - an IIS webhook endpoint for GitHub / GitLab / BitBucket for easy web-site publishing.&lt;/p&gt;

&lt;p&gt;I created this in order to simplify our own web-site publishing workflow, as I couldn't find any existing solutions like this - at least not anything very user friendly.&lt;br&gt;
Prior to making Simple Deploy, I used the excellent Bonobo Git Server with a hook to checkout each repository when pushed.&lt;br&gt;
This worked quite nicely, but meant that our web-servers doubled as Git servers, and that our Git repositories were scattered in several different places.&lt;br&gt;
I wanted a setup where all our Git repositories were in a single and separate location (GitHub) - both for simplicity but also as an extra backup.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://simpledeploy.dev" rel="noopener noreferrer"&gt;https://simpledeploy.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>iis</category>
      <category>webhooks</category>
      <category>cd</category>
    </item>
  </channel>
</rss>
