<?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>DEV Community: stereobooster</title>
    <description>The latest articles on DEV Community by stereobooster (@stereobooster).</description>
    <link>https://dev.to/stereobooster</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F94664%2Fdd5f88a0-3b21-4a84-8dac-95a1f8d1fbb5.png</url>
      <title>DEV Community: stereobooster</title>
      <link>https://dev.to/stereobooster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stereobooster"/>
    <language>en</language>
    <item>
      <title>The test pyramid is a fiction until you define 'unit'</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Wed, 04 Nov 2026 21:19:14 +0000</pubDate>
      <link>https://dev.to/stereobooster/the-test-pyramid-is-a-fiction-until-you-define-unit-1g8p</link>
      <guid>https://dev.to/stereobooster/the-test-pyramid-is-a-fiction-until-you-define-unit-1g8p</guid>
      <description>&lt;p&gt;The test pyramid says: write mostly unit tests, fewer integration tests, a few end-to-end tests. That instruction means something only if "unit test" does. It has at least five definitions in circulation, and they disagree. Count one suite under two of them and you can get a pyramid and a diamond from the same tests.&lt;/p&gt;

&lt;p&gt;So "do we follow the pyramid?" has no answer until a team picks a definition. And the problem the pyramid was meant to solve, a slow and flaky suite, doesn't need the pyramid at all. You can see it directly, and fix it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five definitions of "unit test"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A test of one small piece of the design.&lt;/strong&gt; IEEE's glossary defines unit testing as "Testing of individual hardware or software units or groups of related units", and admits in a note that the relationship between "module", "component" and "unit" "is not yet standardized"&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. &lt;em&gt;Software Engineering at Google&lt;/em&gt; says "Units can be as small as an individual function or as broad as a set of several related packages/modules"&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A test with every collaborator replaced by a double.&lt;/strong&gt; Martin Fowler calls these &lt;em&gt;solitary&lt;/em&gt; tests: "mockists insist upon solitary unit tests, while classicists prefer sociable tests"&lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A test that touches nothing outside its process.&lt;/strong&gt; Michael Feathers: "A test is not a unit test if: It talks to the database / It communicates across the network / It touches the file system / It can't run at the same time as any of your other unit tests / You have to do special things to your environment (such as editing config files) to run it"&lt;sup id="fnref4"&gt;4&lt;/sup&gt;. Google's testing blog: "A Small test equates neatly to a unit test"&lt;sup id="fnref5"&gt;5&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A test that no other test can affect.&lt;/strong&gt; Kent Beck's draft of &lt;em&gt;Test-Driven Development: By Example&lt;/em&gt; says "tests should be able to ignore each other completely"&lt;sup id="fnref6"&gt;6&lt;/sup&gt;. Fowler recalls that "when xunit testing began in the 90's we made no attempt to go solitary unless communicating with the collaborators was awkward"&lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. A test of one behaviour through a public interface.&lt;/strong&gt; Roy Osherove's unit is "a single logical functional use case in the system that can be invoked by some public interface (in most cases)"&lt;sup id="fnref7"&gt;7&lt;/sup&gt;. Ian Cooper: "the unit of isolation is the test not the thing under test"&lt;sup id="fnref8"&gt;8&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Even ISTQB, the body behind the most common tester certification, hasn't settled it. Its syllabus says "Component testing (also known as unit testing)"&lt;sup id="fnref9"&gt;9&lt;/sup&gt;. Its glossary dropped "unit testing" as a synonym in August 2025, then brought it back in April 2026 as a separate term: "the smallest part of code testable in isolation"&lt;sup id="fnref10"&gt;10&lt;/sup&gt;, without saying what isolation means.&lt;/p&gt;

&lt;p&gt;Fowler's verdict is that the term is "very ill-defined", and "really it's a situational thing - the team decides what makes sense to be a unit"&lt;sup id="fnref3"&gt;3&lt;/sup&gt;. That's reasonable advice. The trouble starts when the team never decides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same test, different layers
&lt;/h2&gt;

&lt;p&gt;Take a few ordinary tests and file them under each definition:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Small piece&lt;/th&gt;
&lt;th&gt;All doubles&lt;/th&gt;
&lt;th&gt;No I/O&lt;/th&gt;
&lt;th&gt;Independent&lt;/th&gt;
&lt;th&gt;One behaviour&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A pure function, called directly&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A class with its real in-memory collaborators&lt;/td&gt;
&lt;td&gt;depends&lt;/td&gt;
&lt;td&gt;integration&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A class with every collaborator mocked&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;arguably not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An HTTP handler, only the database faked&lt;/td&gt;
&lt;td&gt;integration&lt;/td&gt;
&lt;td&gt;integration&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A function that writes a temp file&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;integration&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;td&gt;unit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Only the pure function is a unit test under every definition. Everything else moves between layers depending on who files it, and nothing in the code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  One suite, two shapes
&lt;/h2&gt;

&lt;p&gt;Fabian Trautsch and Jens Grabowski took 10 open-source Python projects and classified their tests against the IEEE and ISTQB definitions. Their conclusion: "developers believe that they are developing more unit tests than they actually do"&lt;sup id="fnref11"&gt;11&lt;/sup&gt;. Depending on the project and the definition, the share of real unit tests ranged from&lt;br&gt;
0% to 68.30%.&lt;/p&gt;

&lt;p&gt;aws-cli shows how much the definition matters. Its developers labelled about 100 tests per revision as unit tests. Under IEEE's definition about 60% of those qualified; under ISTQB's, about 3%&lt;sup id="fnref11"&gt;11&lt;/sup&gt;. The tests are the same in both counts; only the definition changes.&lt;/p&gt;

&lt;p&gt;The tests that don't qualify don't disappear. The study only says they aren't unit tests, but tests written as unit tests rarely drive the whole system end to end, so they belong in the middle layer. Under IEEE's definition, aws-cli keeps a wide base. Under ISTQB's, it has a thin base and a wide middle. Add any end-to-end tests on top and the same suite is a pyramid under one standard and a diamond under the other.&lt;/p&gt;

&lt;p&gt;Neither drawing is wrong. The pyramid simply has no shape until someone says what goes in the bottom layer, and most teams never do.&lt;/p&gt;

&lt;h2&gt;
  
  
  You don't need the shape to find slow tests
&lt;/h2&gt;

&lt;p&gt;The pyramid's case was economic: unit tests are fast and reliable, so have many of them. But speed is not something you infer from a diagram. Every test runner reports how long each test takes. Sort by duration and the slow tests are at the top, whatever folder they live in and whichever definition they would satisfy.&lt;/p&gt;

&lt;p&gt;Look at why they're slow and the answer is almost never "it covers too much code". It's what the test touches: a real database, the network, the disk, a sleep. And tests are flaky for a related reason: they share something, a database, a file, a global, the clock, so the result depends on what ran before or alongside them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix it directly
&lt;/h2&gt;

&lt;p&gt;Two of the five definitions are really engineering rules, and they're the two that matter for speed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Make every test independent&lt;/strong&gt; (definition 4). A test that sets up its own state and shares nothing can run in any order and in parallel. That alone removes a whole class of flakiness and lets you add cores instead of waiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stub the slow parts&lt;/strong&gt; (definition 3). Replace the database, the network and the clock with in-process fakes where the test isn't about them. Fake the expensive edge, not every collaborator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither rule says how much code a test may cover. An HTTP handler test with the database faked runs in one process, in milliseconds, in parallel with everything else. Under two of the five definitions it's an integration test; for speed it doesn't matter. Cooper makes the same point about TDD: "it's the isolation of the test that is driving&lt;br&gt;
that not the isolation of the code under test"&lt;sup id="fnref8"&gt;8&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The other argument for small tests is that a failure points at the bug. Trautsch's thesis found that in the 27 projects it studied: "unit tests are better able to pinpoint the source of a defect"&lt;sup id="fnref12"&gt;12&lt;/sup&gt;. How much that matters depends on the application, not on the test. If an error surfaces with a clear stack trace, nothing swallows it, there isn't much indirection between the call and the fault, and you can run the test locally under a debugger, a failing integration test is quick to trace. If errors get caught and re-raised as something generic, small tests do most of the pointing. Either way it's a question of debuggability, not of counting layers. The follow-up study found no difference in what the two kinds of test catch: "We could not find any evidence that one test type is more capable of detecting certain defect&lt;br&gt;
types than the other one"&lt;sup id="fnref13"&gt;13&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;So keep the pyramid if your team has defined "unit" and finds the picture useful. If it hasn't, skip the argument about the shape. Measure which tests are slow and flaky, make them independent, and stub what makes them slow.&lt;/p&gt;

&lt;p&gt;I keep a &lt;strong&gt;knowledge base on software quality&lt;/strong&gt; where every claim has a source. &lt;a href="https://quality.stereobooster.com/terminology/" rel="noopener noreferrer"&gt;Terminology&lt;/a&gt; maps unit, integration and end-to-end onto Google's scope and size. &lt;a href="https://quality.stereobooster.com/axes/" rel="noopener noreferrer"&gt;The axes&lt;/a&gt; covers what the pyramid doesn't ask: where the inputs come from and what decides pass&lt;br&gt;
or fail.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;em&gt;IEEE Standard Glossary of Software Engineering Terminology&lt;/em&gt;, &lt;a href="https://www.informatik.htw-dresden.de/~hauptman/SEI/IEEE_Standard_Glossary_of_Software_Engineering_Terminology%20.pdf" rel="noopener noreferrer"&gt;IEEE Std 610.12-1990&lt;/a&gt;, entries "unit testing" and "unit".&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Titus Winters, Tom Manshreck, Hyrum Wright (eds.), &lt;em&gt;Software Engineering at Google&lt;/em&gt;, O'Reilly, 2020, &lt;a href="https://abseil.io/resources/swe-book/html/ch12.html" rel="noopener noreferrer"&gt;chapter 12, "Unit Testing"&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Martin Fowler, &lt;a href="https://martinfowler.com/bliki/UnitTest.html" rel="noopener noreferrer"&gt;"Unit Test"&lt;/a&gt;, bliki, 2014 (revised 2017).&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Michael Feathers, &lt;a href="https://www.artima.com/weblogs/viewpost.jsp?thread=126923" rel="noopener noreferrer"&gt;"A Set of Unit Testing Rules"&lt;/a&gt;, Artima, 2005.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Simon Stewart, &lt;a href="https://testing.googleblog.com/2010/12/test-sizes.html" rel="noopener noreferrer"&gt;"Test Sizes"&lt;/a&gt;, Google Testing Blog, 2010.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;Kent Beck, &lt;em&gt;Test-Driven Development: By Example&lt;/em&gt;, "Isolated Test" (pre-publication draft, 2002; the printed edition may word it differently).&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;Roy Osherove, &lt;a href="https://www.artofunittesting.com/definition-of-a-unit-test" rel="noopener noreferrer"&gt;"Unit test: a definition"&lt;/a&gt;, &lt;em&gt;The Art of Unit Testing&lt;/em&gt; companion site.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;Ian Cooper, &lt;a href="https://www.youtube.com/watch?v=EZ05e7EMOLM" rel="noopener noreferrer"&gt;TDD, Where Did It All Go Wrong?&lt;/a&gt;, talk, DevTernity 2017 (first given 2013); quoted from the recording.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn9"&gt;
&lt;p&gt;ISTQB, &lt;a href="https://istqb.org/wp-content/uploads/2024/11/ISTQB_CTFL_Syllabus_v4.0.1.pdf" rel="noopener noreferrer"&gt;Certified Tester Foundation Level Syllabus v4.0.1&lt;/a&gt;, 2024, §2.2.1.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn10"&gt;
&lt;p&gt;ISTQB Glossary, &lt;a href="https://glossary.istqb.org/en_US/term/unit-testing" rel="noopener noreferrer"&gt;"unit testing"&lt;/a&gt;, read November 2026.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn11"&gt;
&lt;p&gt;Fabian Trautsch, Jens Grabowski, &lt;a href="https://www.swe.informatik.uni-goettingen.de/sites/default/files/publications/paper_2.pdf" rel="noopener noreferrer"&gt;"Are there any Unit Tests? An Empirical Study on Unit Testing in Open Source Python Projects"&lt;/a&gt;, ICST 2017, &lt;a href="https://doi.org/10.1109/ICST.2017.26" rel="noopener noreferrer"&gt;doi:10.1109/ICST.2017.26&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn12"&gt;
&lt;p&gt;Fabian Trautsch, "An Analysis of the Differences between Unit and Integration Tests", PhD thesis, University of Göttingen, 2019, &lt;a href="https://doi.org/10.53846/goediss-7393" rel="noopener noreferrer"&gt;doi:10.53846/goediss-7393&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn13"&gt;
&lt;p&gt;Fabian Trautsch, Steffen Herbold, Jens Grabowski, &lt;a href="https://dl.gi.de/server/api/core/bitstreams/44c02778-e1fa-4254-b439-e115bfe08aa0/content" rel="noopener noreferrer"&gt;"Are Unit and Integration Test Definitions Still Valid for Modern Java Projects? An Empirical Study on Open-Source Projects"&lt;/a&gt;, Journal of Systems and Software 159, 2020, &lt;a href="https://doi.org/10.1016/j.jss.2019.110421" rel="noopener noreferrer"&gt;doi:10.1016/j.jss.2019.110421&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>testing</category>
      <category>unittesting</category>
      <category>softwarequality</category>
    </item>
    <item>
      <title>Run Cystoscape.js with Node.js</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Thu, 12 Oct 2023 16:33:48 +0000</pubDate>
      <link>https://dev.to/stereobooster/run-cystoscapejs-with-nodejs-4g02</link>
      <guid>https://dev.to/stereobooster/run-cystoscapejs-with-nodejs-4g02</guid>
      <description>&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; If you need to generate an image on the server side.&lt;/p&gt;

&lt;p&gt;At first, I thought it was impossible. There is &lt;a href="https://github.com/cytoscape/cytosnap"&gt;cytosnap&lt;/a&gt; that uses Puppeteer (headless browser). Then, for fun, &lt;a href="https://stereobooster.com/content/posts/tauri-instead-of-puppeteer-or-playwright/index.md"&gt;I rewrote it with Tauri&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But later, I though, what if it would be possible to run it with Node.js. And, indeed, it’s possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Add polyfils for the browser (it also includes canvas):&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;class&lt;/span&gt; &lt;span class="nx"&gt;XMLSerializer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nx"&gt;serializeToString&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="nx"&gt;serialize&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="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;XMLSerializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;XMLSerializer&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;dom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;JSDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;div id="cy"&amp;gt;&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;global&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explicitly set bounding box&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;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boundingBox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;x2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;y2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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;Generate a graph the same way you would in the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#cy&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;cy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cytoscape&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now I was able to render it only as SVG:&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;await&lt;/span&gt; &lt;span class="nx"&gt;loadExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;full&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to not polute global scope with polyfills we can run this script as sub-process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;executablePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`bin/cyto-nodejs.js`&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;bin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="na"&gt;windowsHide&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;Source code is here: &lt;a href="https://github.com/stereobooster/cyto-nodejs"&gt;https://github.com/stereobooster/cyto-nodejs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cytoscape</category>
      <category>webdev</category>
      <category>diagram</category>
      <category>cli</category>
    </item>
    <item>
      <title>Hugo ideal image</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Tue, 10 Oct 2023 23:14:33 +0000</pubDate>
      <link>https://dev.to/stereobooster/hugo-ideal-image-4blj</link>
      <guid>https://dev.to/stereobooster/hugo-ideal-image-4blj</guid>
      <description>&lt;p&gt;I created image components a couple of times before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/stereobooster/responsive-images-for-hugo-dn9"&gt;Responsive images for Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/stereobooster/react-ideal-image"&gt;react-ideal-image&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But browsers keep improving - now almost all modern browsers support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/loading-lazy-attr"&gt;loading=lazy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/srcset"&gt;srcset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/picture"&gt;picture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com/webp"&gt;webp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I checked existing solutions - they either don’t do what I want or are complicated (for my taste):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hugomods.com/en/docs/images/"&gt;Hugo Images Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hugo-mods/lazyimg"&gt;lazyimg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danielfdickinson/image-handling-mod-hugo-dfd"&gt;DFD Hugo image handling module&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;What I expect from an “ideal” image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responsiveness&lt;/li&gt;
&lt;li&gt;Lazy-loading&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/transitive-bullshit/lqip-modern"&gt;Modern LQIP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stereobooster.com/posts/portable-markdown-links/"&gt;Portable markdown links&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dark mode&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;There are different ways to implement image components in Hugo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/templates/partials/"&gt;partial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/templates/render-hooks/"&gt;render hook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/templates/shortcode-templates/"&gt;shortcodes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think the most pragmatic way would be to implement it as “partial” which accepts an image as a resource. This way it can be reused in render hooks and shortcodes, and the resolution of the image would be the responsibility of the caller.&lt;/p&gt;

&lt;p&gt;It can look something 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;{{ partial "picture.html" (dict "img" $img "alt" $.Text) }}

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

&lt;/div&gt;



&lt;p&gt;Partial &lt;code&gt;picture.html&lt;/code&gt; would be responsible for: detecting width and height, resizing the image, and rendering HTML. There are no special requirements for HTML or CSS so it would be a pretty portable solution (copy one file).&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsiveness
&lt;/h3&gt;

&lt;p&gt;Component would be responsible for resizing image and rendering &lt;code&gt;srcset&lt;/code&gt; attribute. Basic code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;srcset="
{{- (.Resize "330x").RelPermalink }} 330w,
{{- (.Resize "660x").RelPermalink }} 660w,
{{- (.Resize "1024x").RelPermalink }} 1024w,
{{- (.Resize "1320x").RelPermalink }} 2x"
src="{{ (.Resize "660x").RelPermalink }}"

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it suppose to resize only raster images (not SVG)&lt;/li&gt;
&lt;li&gt;it should not upscale image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally we can provide webp version of the image. Basic code:&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;picture&amp;gt;
&amp;lt;source
srcset="
{{- (.Resize "330x webp").RelPermalink }} 330w,
{{- (.Resize "660x webp").RelPermalink }} 660w,
{{- (.Resize "1024x webp").RelPermalink }} 1024w,
{{- (.Resize "1320x webp").RelPermalink }} 2x"
src="{{ (.Resize "660x webp").RelPermalink }}"
/&amp;gt;
&amp;lt;img ... /&amp;gt;
&amp;lt;/picture&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Component only responsible for HTML part. You probably would need to add some CSS, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;img {
 max-width: 100%;
 height: auto;
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lazy-loading
&lt;/h3&gt;

&lt;p&gt;In order to use lazy-load it is enough to add:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;and optionaly:&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it is important to set width and height of the image, to prevent reflow - browser would know ratio of the image upfront and will reserve space for it without downloading it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which is pretty easy to do with Hugo&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;width="{{ .Width }}" height="{{ .Height }}"

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it works only for raster images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For SVG we need to do something 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;{{ if (eq .MediaType.SubType "svg") }}
{{ $width := ""}}
{{ $height := ""}}
{{ $svgContent := .Content }}
{{ range (findRESubmatch `&amp;lt;svg[^&amp;gt;]*width=["']([.0-9]*)["'a-zA-Z]` $svgContent 1) }}
{{ $width = index . 1 }}
{{ end }}
{{ range (findRESubmatch `&amp;lt;svg[^&amp;gt;]*height=["']([.0-9]*)["'a-zA-Z]` $svgContent 1) }}
{{ $height = index . 1 }}
{{ end }}
{{ if (eq "" $width $height) }}
{{ range (findRESubmatch `&amp;lt;svg[^&amp;gt;]*viewBox=["']?([.0-9]*) ([.0-9]*) ([.0-9]*) ([.0-9]*)` $svgContent 1) }}
{{ $width = index . 3 }}
{{ $height = index . 4 }}
{{ end }}
{{ end }}
{{ if (eq "" $width $height) }}
{{ warnf "Can't detect width and height for SVG %s" .RelPermalink }}
{{/* do not use lazy without dimensions */}}
{{ $lazy = false }}
{{ end }}
{{ end }}

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

&lt;/div&gt;



&lt;p&gt;This code reads SVG as text and uses regular expression in order to match &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, and &lt;code&gt;viewBox&lt;/code&gt;. &lt;strong&gt;Important&lt;/strong&gt; : it should not match values with &lt;code&gt;%&lt;/code&gt;, because they are useless in this case. On the other hand - any absolute units can be matched, like &lt;code&gt;px&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  LQIP
&lt;/h3&gt;

&lt;p&gt;There are many LQIP approaches. I like, so called, modern LQIP, which uses 20px, 20% quality webp image as base64.&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;picture
{{ if $lqip }}
{{ $bg := (.Resize "20x webp q20").Content | base64Encode }}
style="background-image:url(data:image/webp;base64,{{ $bg }});background-size:cover"
{{ end }}&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;I already introduced &lt;code&gt;picture&lt;/code&gt; in the markup, so I will reuse it as “holder” for LQIP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for transparent images (png, webp) LQIP may be problematic, so there suppose to be a way to disable it&lt;/li&gt;
&lt;li&gt;I don’t want to generate LQIP for SVG, because I would need to rasterize SVG first, which is too much trouble&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionaly you may want to add CSS in order to make picture of the same size as image, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;picture {
 display: block;
}

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

&lt;/div&gt;



&lt;p&gt;To improve appearance it is recomended to add blur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;picture &amp;gt; img {
 -webkit-backdrop-filter: blur(10px);
 backdrop-filter: blur(10px);
}

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

&lt;/div&gt;



&lt;p&gt;Image is on “top” of picture. While it is loading it will be transparent. So we can use it add blur to the background. &lt;strong&gt;Important&lt;/strong&gt; : this trick doesn’t work in Firefox, see &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1556156"&gt;Bug 1556156&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Portable markdown links
&lt;/h3&gt;

&lt;p&gt;This one is easy. Partial take resource rather than path. So it is responsibility of caller to resolve path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dark mode
&lt;/h3&gt;

&lt;p&gt;I’m not sure yet how to implement “dark mode” for images. From an HTML point of view it is trivial to implement:&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;picture&amp;gt;
 &amp;lt;source media="(prefers-color-scheme: dark)" srcset="..." /&amp;gt;
 &amp;lt;source media="(prefers-color-scheme: light)" srcset="..." /&amp;gt;
 &amp;lt;img src="..." /&amp;gt;
&amp;lt;/picture&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;But how to do it from Markdown’s point of view? Some options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;convention&lt;/strong&gt;. If there is an image &lt;code&gt;img.png&lt;/code&gt; and &lt;code&gt;img_dark.png&lt;/code&gt;, the system can automatically find the second image and use it for dark mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;shortcode&lt;/strong&gt;. Will work, but this is not a markdown solution&lt;/li&gt;
&lt;li&gt;Use some kind of &lt;strong&gt;marker in the url&lt;/strong&gt; to distinguis dark-/light-mode images. For example, &lt;code&gt;#gh-dark-mode-only&lt;/code&gt; or &lt;code&gt;#gh-light-mode-only&lt;/code&gt;. See &lt;a href="https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to"&gt;Specifying the theme an image is shown to&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SVGs don’t need a special solution, they can handle it internally. See &lt;a href="https://stackoverflow.com/questions/67187091/creating-svg-that-appears-black-in-light-mode-and-light-in-dark-mode"&gt;Creating SVG that appears black in light mode and light in dark mode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;for black and white images &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/invert"&gt;&lt;code&gt;filter: invert(1)&lt;/code&gt;&lt;/a&gt; would do the trick&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other
&lt;/h3&gt;

&lt;p&gt;I want to keep number of params minimal. Currently it supports only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;img&lt;/code&gt; - image as resource&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alt&lt;/code&gt; - string, alternative text&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;class&lt;/code&gt; - string, for styling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lazy&lt;/code&gt; - boolean, whether it should use lazy loading or not&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webp&lt;/code&gt; - boolean, whether it should generate webp version or not&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lqip&lt;/code&gt; - boolean, whether it should generate LQIP or not&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;My current implementation is here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/stereobooster/hugo-ideal-image/blob/main/layouts/partials/picture.html"&gt;picture partial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/stereobooster/hugo-ideal-image/blob/main/layouts/_default/_markup/render-image.html"&gt;render hook for image&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hugo</category>
      <category>webdev</category>
      <category>image</category>
    </item>
    <item>
      <title>Tauri instead of Puppeteer or Playwright?</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Tue, 03 Oct 2023 11:14:09 +0000</pubDate>
      <link>https://dev.to/stereobooster/tauri-instead-of-puppeteer-or-playwright-3ic4</link>
      <guid>https://dev.to/stereobooster/tauri-instead-of-puppeteer-or-playwright-3ic4</guid>
      <description>&lt;p&gt;Tauri and Puppeteer/Playwright have different use cases. So it seems nothing to compare. But there is (at least) one use case where you may try to use Tuari instead of a headless browser - “snapshots”. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/stereobooster/react-snap"&gt;react-snap&lt;/a&gt; - HTML snapshot, uses Puppeteer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mermaid-js/mermaid-cli"&gt;mermaid-cli&lt;/a&gt; - SVG/image snapshot, uses Puppeteer&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/cytoscape/cytosnap"&gt;cytosnap&lt;/a&gt; - image snapshot, uses Puppeteer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Because Tauri uses provided by OS browser and produces small binaries (let’s say ~10 MB). While Puppeteer and Playwright need a full browser (let’s say ~200 MB). And Tauri probably can be faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment
&lt;/h2&gt;

&lt;p&gt;Let’s see if this will work in practice. For an experiment, I decided to recreate &lt;code&gt;cytosnap&lt;/code&gt; with Tauri. Functionality is trivial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read input from file or STDIN (&lt;a href="https://js.cytoscape.org/#notation/elements-json"&gt;Elements JSON&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;generate a graph with the help of Cytoscape.js&lt;/li&gt;
&lt;li&gt;write result (image from canvas) to file or STDOUT&lt;/li&gt;
&lt;li&gt;and this is supposed to be CLI, not GUI-app&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;

&lt;p&gt;CLI applications are not a typical use case for Tauri, but it is possible.&lt;/p&gt;

&lt;p&gt;Hide window (&lt;code&gt;tauri.conf.json&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;"windows": [{ "visible": false, ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hide icon from Dock bar (Mac OS only):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[cfg(target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"macos"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ActivationPolicy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.setup&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;#[cfg(target_os&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"macos"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="nf"&gt;.set_activation_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ActivationPolicy&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Accessory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&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;Add CLI arguments (&lt;code&gt;tauri.conf.json&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; "cli": {
      "description": "Render graphs on the server side with Cytoscape.js, getting image file as output",
      "args": [
        {
          "name": "source",
          "short": "s",
          "takesValue": true,
          "multiple": false,
          "multipleOccurrences": false
        },
        {
          "name": "destination",
          "short": "d",
          "takesValue": true,
          "multiple": false,
          "multipleOccurrences": false
        }
      ],
    },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Overcome security fences
&lt;/h3&gt;

&lt;p&gt;Because Tauri’s main use case is GUI, they care about security and limit what files/folders the application can access. Which doesn’t work for the way people use CLI. So I had to write my own Tauri commands to write and read files, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tauri::command]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;write_destination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;print!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ok"&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;general_purpose&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;STANDARD&lt;/span&gt;&lt;span class="nf"&gt;.decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;absolute_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;result&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;result&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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;&lt;strong&gt;Note&lt;/strong&gt; : I need to pass binary data from the front end to the rust, so I use Base64.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distribution
&lt;/h3&gt;

&lt;p&gt;Initially, I wanted to distribute this CLI as &lt;a href="//content/posts/distributing-executable-binaries-in-npm/index.md"&gt;binary inside the npm package&lt;/a&gt;. But then I realized that &lt;a href="https://github.com/tauri-apps/tauri/discussions/3048"&gt;Tauri can’t really produce portable binaries&lt;/a&gt;. Tauri relies on the OS’s browser - which is a neat trick to shrink down the size of the binary, but this is what makes it less portable. Trade-offs as always.&lt;/p&gt;

&lt;p&gt;Not to stop an experiment I decided to distribute at least binary for Mac OS in npm. And it works (Mac OS only):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx @stereobooster/cyto-snap -s g2.json -d g2.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Size of the npm package
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm notice package size: 2.9 MB
npm notice unpacked size: 7.4 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn’t do any Tauri optimizations, so it probably can be smaller. Also, this is Mac OS-only binary, it will be bigger if we pack binaries for all platforms in one npm package.&lt;/p&gt;

&lt;p&gt;And it has 0 dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source code
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt; : because this is an experiment, I didn’t try to make it perfect. Just made it work.&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/stereobooster/cyto-snap"&gt;https://github.com/stereobooster/cyto-snap&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Homebrew
&lt;/h3&gt;

&lt;p&gt;The idea of distributing binary in npm failed, so I think to try to distribute it via Homebrew. Homebrew works on Mac OS, Windows (WSL 2), and Linux (though they have their own package managers).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Produce standard desktop installers for Tauri (&lt;code&gt;tauri-apps/tauri-action&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Upload installers to GitHub releases&lt;/li&gt;
&lt;li&gt;Run installers in silent mode with the Homebrew formula&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Full automation with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;It has a lot of steps to produce the final package. I started automation but didn’t finish it. Ideally, it should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build application&lt;/li&gt;
&lt;li&gt;Run tests (I do integration tests with &lt;a href="https://github.com/dmtrKovalenko/odiff"&gt;odiff&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Create a tag and push it&lt;/li&gt;
&lt;li&gt;Create release and upload all binaries&lt;/li&gt;
&lt;li&gt;Update the Homebrew formula and publish it&lt;/li&gt;
&lt;li&gt;Publish the npm package (but it is not very useful without portable binaries)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;This approach may work with Homebrew distribution

&lt;ul&gt;
&lt;li&gt;It is sad that you can’t publish binaries to npm, which would make JS-developer use other means to install it (Homebrew, apt-get, cURL, etc)&lt;/li&gt;
&lt;li&gt;On the other hand, it is fully independent of npm (and Node.js ecosystem in general), so can be used by none-JS developers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;This was my first time using Tauri, and it is awesome&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tauri</category>
      <category>rust</category>
      <category>cli</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Distributing executable binaries in npm package</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Sun, 01 Oct 2023 09:28:23 +0000</pubDate>
      <link>https://dev.to/stereobooster/distributing-executable-binaries-in-npm-package-1d4l</link>
      <guid>https://dev.to/stereobooster/distributing-executable-binaries-in-npm-package-1d4l</guid>
      <description>&lt;p&gt;Let’s say you need to distribute binary in npm. I’m not talking about node extensions (node-gyp, napi, etc.). I’m talking about standalone executables, like CLI applications.&lt;/p&gt;

&lt;p&gt;We have options. &lt;strong&gt;How to distribute binary&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;host executables on some server, for example, GitHub releases, and download appropriate (e.g. for given OS and architecture) binary on installation&lt;/li&gt;
&lt;li&gt;put all executables (for all OSs and architectures) in one npm package&lt;/li&gt;
&lt;li&gt;create sub-packages with binary for each OS and architecture and one “root” npm package which would list all sub-packages as optional dependencies&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How to run the binary&lt;/strong&gt; :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use JS wrapper which would call actual executable with &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;spawn&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;On installation figure out where Node puts binaries and overwrite it with your binary from the package&lt;/li&gt;
&lt;li&gt;Use JS script to call node binding (&lt;code&gt;*.node&lt;/code&gt;) or WASM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tools and blog posts&lt;/strong&gt; :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;distribution&lt;/th&gt;
&lt;th&gt;execution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/EverlastingBugstopper/binary-install"&gt;binary-install&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;spawnSync&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/avh4/binwrap"&gt;binwrap&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;spawn&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/mole-inc/bin-wrapper"&gt;bin-wrapper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;spawn&lt;/code&gt;, …)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/prebuild/prebuild-install"&gt;prebuild-install&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://blog.xendit.engineer/how-we-repurposed-npm-to-publish-and-distribute-our-go-binaries-for-internal-cli-23981b80911b"&gt;blog.xendit.enginee&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://hspak.dev/post/publish-npm/"&gt;hspak.dev&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/prebuild/prebuildify"&gt;prebuildify&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://blog.orhun.dev/packaging-rust-for-npm/"&gt;blog.orhun.dev&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;spawnSync&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/napi-rs/package-template"&gt;napi-rs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Example projects&lt;/strong&gt; :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;distribution&lt;/th&gt;
&lt;th&gt;execution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/dmtrKovalenko/odiff"&gt;odiff&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/withastro/compiler"&gt;@astrojs/compiler&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2 (WASM)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/evanw/esbuild"&gt;esbuild&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;execFileSync&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/biomejs/biome"&gt;biome&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;spawnSync&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/vercel/turbo/tree/main/packages/turbo"&gt;Turborepo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 (&lt;code&gt;execFileSync&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/parcel-bundler/lightningcss"&gt;lightningcss&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>npm</category>
    </item>
    <item>
      <title>Useful modern tools for static websites</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Tue, 26 Sep 2023 15:50:02 +0000</pubDate>
      <link>https://dev.to/stereobooster/useful-modern-tools-for-static-websites-i9p</link>
      <guid>https://dev.to/stereobooster/useful-modern-tools-for-static-websites-i9p</guid>
      <description>&lt;p&gt;This list is about tools beyond &lt;a href="https://www.staticwebsitehosting.org/"&gt;static hosting&lt;/a&gt; and &lt;a href="https://jamstack.org/generators/"&gt;static website generators&lt;/a&gt;. There are already enough list about that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics
&lt;/h2&gt;

&lt;p&gt;In old times there was basically one default choice: Google analytics. What is bad about GA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JS size (about 49kb). There alternative clients though, like this &lt;a href="https://github.com/lukeed/ganalytics"&gt;one&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Blocked by default by some browsers and adblockers&lt;/li&gt;
&lt;li&gt;no GDPR compliance (e.g. you always need to provide cookies banner)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there are a lot of better alternatives (just to name few):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://tinyanalytics.io/"&gt;https://tinyanalytics.io/&lt;/a&gt; - 1 site for free&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://umami.is/"&gt;https://umami.is/&lt;/a&gt; - 3 sites for free&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://swetrix.com/"&gt;https://swetrix.com/&lt;/a&gt; - open source, starts from $5/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See more alternatives here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://european-alternatives.eu/category/web-analytics-services"&gt;European web analytics services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tinyanalytics.io/google-analytics-alternatives"&gt;19 Google Analytics Alternatives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.goatcounter.com/"&gt;goatcounter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also some “static hostings” provide analytics, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.cloudflare.com/web-analytics/"&gt;Cloudflare Pages&lt;/a&gt; there is a free tier&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.netlify.com/products/analytics/"&gt;Netlify&lt;/a&gt; $9/month&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Search
&lt;/h2&gt;

&lt;p&gt;In old times people would use “Google custom search engine” (I can’t find it anymore, I guess, they discounted it). There is as well Algolia. But who needs third party paid solution, when you can do it for free.&lt;/p&gt;

&lt;p&gt;Recent trend is to generate search index during site generation and then use client side library to do the search. It will probably won’t scale for a huge website, but for personal blog more than enough.&lt;/p&gt;

&lt;p&gt;Some options (not a full list):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/oramasearch/orama"&gt;orama&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cloudcannon/pagefind"&gt;pagefind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/krisk/fuse"&gt;fuse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/olivernn/lunr.js"&gt;lunr.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honorable mentions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/typesense/typesense"&gt;typesense&lt;/a&gt; - open-source, but needs server&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docsearch.algolia.com/"&gt;docsearch&lt;/a&gt; - free Algolia search for developer docs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or you can do something crazy, like hosting &lt;a href="https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/"&gt;SQLite database over HTTP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comments
&lt;/h2&gt;

&lt;p&gt;In old times people would use Disqus. But again, it is not GDPR compliant out of the box and there is huge JS payload (at least it was, when I used it last time).&lt;/p&gt;

&lt;p&gt;One of potential solution for developers is to reuse github comments or discussions as comments for your website. Most developers have github account anyway (I know there are people, who are strongly opposed to Github and prefer Gitlab or self-hosted solutions, but this is discussion for another time). For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://giscus.app/"&gt;https://giscus.app/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://utteranc.es/"&gt;https://utteranc.es/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other option is not to host comments directly on your website, but put a link to a place where people can discuss (especially if you crosspost), for example: hacker news, dev.to, reddit, twitter etc.&lt;/p&gt;

</description>
      <category>hugo</category>
      <category>blog</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Runtime type validators</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Sun, 24 Sep 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/stereobooster/runtime-type-validators-1o60</link>
      <guid>https://dev.to/stereobooster/runtime-type-validators-1o60</guid>
      <description>&lt;h2&gt;
  
  
  Where can be used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;validate http request from client (at server side)&lt;/li&gt;
&lt;li&gt;validate http response from server (at client side)&lt;/li&gt;
&lt;li&gt;validate data from untyped storage, for example localStorage, JSON columns in DB, etc.&lt;/li&gt;
&lt;li&gt;validate env variables&lt;/li&gt;
&lt;li&gt;validate CLI arguments&lt;/li&gt;
&lt;li&gt;validate configuration files&lt;/li&gt;
&lt;li&gt;validate data from URL (query params)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Basic validation - can validate static types at runtime (can be restricted to JSON type)&lt;/li&gt;
&lt;li&gt;Serialisation / deserialisation - for example, TS/JS supports &lt;code&gt;Date&lt;/code&gt;, but JSON doesn’t. So you need to do conversion in order to support it&lt;/li&gt;
&lt;li&gt;Compatibility with other schemas - there are tools which can convert from- or to- another schemas, for example JSONSchema, OpenAPI etc&lt;/li&gt;
&lt;li&gt;Strict or permissive mode - would object be accepted if it contains extra fields&lt;/li&gt;
&lt;li&gt;Helpful error messages - if in case of failed validation it points what exactly went wrong or simply dismisses the input&lt;/li&gt;
&lt;li&gt;AOT (ahead of time) - can it use type information in order to produce validation libraries or faster serialiser, deserialiser&lt;/li&gt;
&lt;li&gt;Custom types - can user create custom types, for example type for email, instead of string&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Libraries
&lt;/h2&gt;

&lt;p&gt;Not a full list, but rather a high level overview&lt;/p&gt;

&lt;h3&gt;
  
  
  Fast
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/samchon/typia"&gt;typia&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Doesn’t need extra schema definition. Uses AOT to generate validators, serialisers and deserialisers directly from TypeScript types&lt;/li&gt;
&lt;li&gt;Supports protocol buffers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sinclairzx81/typebox"&gt;typebox&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Supports all JSON, JS, JSONSchema, but for TS discriminated unions you need to use &lt;a href="https://github.com/jtlapp/typebox-validators"&gt;typebox-validators&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Can use JIT (and AOT?)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/GoogleFeud/ts-runtime-checks"&gt;ts-runtime-checks&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Uses AOT&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See: &lt;a href="https://moltar.github.io/typescript-runtime-type-benchmarks/"&gt;Runtype Benchmarks&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Popular
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/colinhacks/zod"&gt;zod&lt;/a&gt;. For example, integrates with &lt;a href="https://docs.astro.build/en/guides/content-collections/#defining-datatypes-with-zod"&gt;Astro&lt;/a&gt;, &lt;a href="https://orm.drizzle.team/docs/zod"&gt;Drizzle&lt;/a&gt;, &lt;a href="https://trpc.io/docs/server/validators#with-zod"&gt;trpc&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jquense/yup"&gt;yup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ajv-validator/ajv"&gt;ajv&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://typeschema.com/"&gt;typeschema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabian-hiller/valibot"&gt;valibot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/adonisjs/validator"&gt;adonisjs/validator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/gcanti/io-ts"&gt;io-ts&lt;/a&gt; and &lt;a href="https://github.com/gcanti/newtype-ts"&gt;newtype-ts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.deepkit.io/english/runtime-types.html"&gt;deepkit runtime types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pelotom/runtypes"&gt;runtypes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ianstormtaylor/superstruct"&gt;superstruct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/denostack/safen"&gt;safen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/arktypeio/arktype"&gt;arktype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/DZakh/rescript-struct"&gt;rescript-struct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ostrowr/ts-json-validator"&gt;ts-json-validator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/badrap/valita"&gt;valita&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jsoldi/to-typed"&gt;to-typed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/icebob/fastest-validator"&gt;fastest-validator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sant123/runtypes"&gt;runtypes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/neuledge/computed-types"&gt;computed_types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/paritytech/subshape"&gt;subshape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/effect-ts/schema"&gt;effect-ts/schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/marcj/TypeRunner"&gt;TypeRunner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/moodysalem/jointz"&gt;jointz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rhys-vdw/ts-auto-guard"&gt;ts-auto-guard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lukeautry/tserial"&gt;tserial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabiandev/ts-runtime"&gt;ts-runtime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Related
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/stereobooster/type-o-rama"&gt;type-o-rama&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>typesystem</category>
    </item>
    <item>
      <title>Text to diagram</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Sat, 23 Sep 2023 14:00:00 +0000</pubDate>
      <link>https://dev.to/stereobooster/text-to-diagram-2pb3</link>
      <guid>https://dev.to/stereobooster/text-to-diagram-2pb3</guid>
      <description>&lt;p&gt;aka diagrams as code&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stereobooster.com/content/posts/what-i-miss-in-markdown/index.md#diagrams"&gt;I dreamt about it for a long time&lt;/a&gt;. Idea is to allow to express diagrams with a text, this way you can draw diagram while you are writing you markdown file. Without need to switch to another tool. And this diagram becomes part of the document and can be stored in git.&lt;/p&gt;

&lt;p&gt;Now when &lt;a href="https://github.com/gohugoio/hugo/releases/tag/v0.93.0"&gt;Hugo supports&lt;/a&gt; render hooks for &lt;code&gt;code&lt;/code&gt; blocks and in &lt;a href="https://mdxjs.com/"&gt;MDX&lt;/a&gt; you can do this too (to use with Astro, etc.) it’s time to revisit the subject.&lt;/p&gt;

&lt;h2&gt;
  
  
  Categories
&lt;/h2&gt;

&lt;p&gt;Before we move on let’s introduce some kind of categorisation of tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Declarative&lt;/strong&gt; - when you have specific language for the job. You express what you want to get and it’s up to the tool to figure out how to draw it

&lt;ul&gt;
&lt;li&gt;Examples: &lt;a href="https://mermaid.js.org/"&gt;Mermaid&lt;/a&gt;, &lt;a href="https://graphviz.org/"&gt;Graphviz&lt;/a&gt;, &lt;a href="https://d2lang.com/"&gt;d2lang&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Imperative&lt;/strong&gt; - more involved approach, you need to specify how to draw the diagram

&lt;ul&gt;
&lt;li&gt;Examples: ?&lt;/li&gt;
&lt;li&gt;Note: python + Matplotlib, for example. also in this category, but if we go that far we will reinvent &lt;a href="https://jupyter.org/"&gt;Jupyter&lt;/a&gt;. So let’s leave it out&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASCII-art&lt;/strong&gt; - you de-facto drawing, but with text. Tool just responsible for making it look nicer than terminal output

&lt;ul&gt;
&lt;li&gt;Examples: &lt;a href="https://ivanceras.github.io/svgbob-editor/"&gt;svgbob&lt;/a&gt;, &lt;a href="https://google.github.io/typograms/"&gt;typograms&lt;/a&gt;, &lt;a href="https://casual-effects.com/markdeep/"&gt;markdeep&lt;/a&gt;, &lt;a href="https://github.com/bep/goat"&gt;goat&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;output format&lt;/strong&gt; : svg, png (or any other raster image format), ASCII-art, html + css + js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;integration&lt;/strong&gt; : library, cli, web browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;supported diagrams&lt;/strong&gt; : network/graph, flowchart, UML and many more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;theming&lt;/strong&gt; : if you have to hardcode colours in the diagram or you can configure it later, for example to support dark theme&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;other&lt;/strong&gt; : code highlighting plugin for editor, auto-formatter/pretty-printer, support for icons/images, support for embedding html/markdown/LaTeX, etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Names&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;Formats&lt;/th&gt;
&lt;th&gt;Supported diagrams&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://mermaid.js.org/"&gt;mermaid&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Declarative&lt;/td&gt;
&lt;td&gt;browser*&lt;/td&gt;
&lt;td&gt;SVG&lt;/td&gt;
&lt;td&gt;Flowchart, UML, ER, Gant…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://graphviz.org/"&gt;graphviz&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Declarative&lt;/td&gt;
&lt;td&gt;CLI, library&lt;/td&gt;
&lt;td&gt;SVG, raster&lt;/td&gt;
&lt;td&gt;Graphs (flowchart, network, etc)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://d2lang.com/"&gt;d2lang&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Declarative&lt;/td&gt;
&lt;td&gt;CLI (go)&lt;/td&gt;
&lt;td&gt;SVG, raster…&lt;/td&gt;
&lt;td&gt;Flowchart, UML, ER…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://pikchr.org/"&gt;pikchr&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;CLI, library&lt;/td&gt;
&lt;td&gt;SVG&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pikchr.org/home/uv/pic.pdf"&gt;Kind of anything&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arthursonzogni.com/Diagon/"&gt;Diagon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Declarative&lt;/td&gt;
&lt;td&gt;CLI, library&lt;/td&gt;
&lt;td&gt;ASCII-art&lt;/td&gt;
&lt;td&gt;Flowchart, Sequence…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://plantuml.com/"&gt;PlantUML&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Declarative&lt;/td&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;SVG, raster, ASCII-art&lt;/td&gt;
&lt;td&gt;UML…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="http://www.gnuplot.info/"&gt;gnuplot&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;SVG, raster, ASCII-art&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gnuplot.sourceforge.net/demo_5.5/"&gt;plots&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://tikz.net/"&gt;Tikz&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;LaTeX*&lt;/td&gt;
&lt;td&gt;PDF*&lt;/td&gt;
&lt;td&gt;anything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is birdeye overview. There are nuances. I didn’t include some libraries that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;has very narrow use-case (only one type of digram)&lt;/li&gt;
&lt;li&gt;are less popular or not supported (abandonware)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mermaid
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Most popular, because it &lt;a href="https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/"&gt;was adopted by github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Works &lt;a href="https://github.com/mermaid-js/mermaid/issues/3650"&gt;only in the browser&lt;/a&gt;. There is a &lt;a href="https://github.com/mermaid-js/mermaid-cli"&gt;cli&lt;/a&gt;, but it just a hack with headless browser (puppeteer)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theme-able&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bpruitt-goddard/vscode-mermaid-syntax-highlight"&gt;syntax highlightning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Graphviz
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Mature and wide-spread. I think, it was number one for graphs (flowchart, network, DAG) before github integrated mermaid&lt;/li&gt;
&lt;li&gt;Originally CLI tool, but there are as well libraries, for example &lt;a href="https://www.npmjs.com/package/@hpcc-js/wasm"&gt;WASM-based&lt;/a&gt;, which allows to use Graphwiz on the server and in the browser&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=joaompinto.vscode-graphviz"&gt;syntax highlightning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  d2lang
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://text-to-diagram.com/"&gt;Wants to take over the world&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Originally CLI tool, but there is &lt;a href="https://github.com/terrastruct/d2/issues/136"&gt;an attempt to convert to WASM library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It worth to mention that it packs headless browser (playwright)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theme-able&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=terrastruct.d2"&gt;syntax highlightning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Pikchr
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;“&lt;a href="https://en.wikipedia.org/wiki/Pic_language"&gt;PIC&lt;/a&gt;-like markup language for diagrams in technical documentation”&lt;/li&gt;
&lt;li&gt;To me it feels more on declarative side, but you can do some imperative things as well&lt;/li&gt;
&lt;li&gt;There is &lt;a href="https://github.com/fabiospampinato/pikchr-wasm"&gt;WASM library&lt;/a&gt; and &lt;a href="https://github.com/gopikchr/gopikchr"&gt;go port&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/epmoyer/pikchr_monarch"&gt;syntax highlightning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Diagon
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;“Diagon is an interactive interpreter. It transforms markdown-style expression into an ascii-art representation.”&lt;/li&gt;
&lt;li&gt;It is the only one option on the list which outputs (only) ASCII-art. But you can feed it to another tool which will convert it to SVG&lt;/li&gt;
&lt;li&gt;There is WASM library, but it’s not distributed as npm package&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tikz
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The most famous option between people who use LaTeX&lt;/li&gt;
&lt;li&gt;I can’t tell if this more declarative or imperative approach (kind of both)&lt;/li&gt;
&lt;li&gt;There is a project which &lt;a href="https://github.com/kisonecat/web2js"&gt;compiles Tikz to WASM&lt;/a&gt; (&lt;a href="https://tikzjax.com/"&gt;demo&lt;/a&gt;), but it’s not distributed as npm package&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  gnuplot
&lt;/h3&gt;

&lt;p&gt;This is the only one solution on the list which focuses solely on plots. There are a lot of plot libraries and I don’t want to list them all here. The difference from other solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is standalone, while other solutions are DSLs/libraries inside bigger language, for example &lt;code&gt;matplotlib&lt;/code&gt; “requires” python&lt;/li&gt;
&lt;li&gt;it is CLI, where is many other solutions are GUI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One way or another I felt like I need to include at least one of those on the list. Originally written in C, so there is a &lt;a href="https://github.com/chhu/gnuplot-JS/issues/5"&gt;chance of porting it to WASM&lt;/a&gt; (see also &lt;a href="https://mneuroth.github.io/MobileGnuplotViewerQuickWASM/"&gt;MobileGnuplotViewerQuickWASM&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I can’t tell if this more declarative or imperative approach (kind of both)&lt;/p&gt;

&lt;p&gt;Syntax highlightning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=fizzybreezy.gnuplot"&gt;fizzybreezy.gnuplot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=mammothb.gnuplot"&gt;mammothb.gnuplot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=MarioSchwalbe.gnuplot"&gt;MarioSchwalbe.gnuplot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ASCII-art
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ivanceras.github.io/svgbob-editor/"&gt;svgbob&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://google.github.io/typograms/"&gt;typograms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://casual-effects.com/markdeep/"&gt;markdeep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bep/goat"&gt;goat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ditaa.sourceforge.net/"&gt;diita&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/asciitosvg/asciitosvg"&gt;asciitosvg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blockdiag.com/en/"&gt;blockdiag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/christiangoltz/shaape"&gt;shaape&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Editors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://asciiflow.com/"&gt;https://asciiflow.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://textik.com/"&gt;https://textik.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kirilllive.github.io/ASCII_Art_Paint/ascii_paint.html"&gt;https://kirilllive.github.io/ASCII_Art_Paint/ascii_paint.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Special purpose
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wavedrom/wavedrom"&gt;wavedrom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jacquev6.github.io/DrawGrammar/"&gt;DrawGrammar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sverweij/state-machine-cat"&gt;state-machine-cat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://structurizr.com/"&gt;structurizr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kevinpt/symbolator"&gt;symbolator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kevinpt.github.io/syntrax/"&gt;syntrax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://upset.js.org/venn.js/"&gt;venn.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Deep-Symmetry/bytefield-svg"&gt;bytefield-svg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lilypond.org/"&gt;lilypond&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other
&lt;/h3&gt;

&lt;p&gt;Other tools (I’m pretty sure there are much more projects which I’m not aware of):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://penrose.cs.cmu.edu/docs/ref/style/usage"&gt;penrose&lt;/a&gt;. This one is &lt;strong&gt;interesting&lt;/strong&gt; - I need to read more about it&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vega.github.io/vega-lite/"&gt;vega-lite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mcternan.me.uk/mscgen/"&gt;mscgen&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mscgen.js.org/"&gt;mscgen_js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flowchart.js.org/"&gt;flowchart.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bramp.github.io/js-sequence-diagrams/"&gt;js-sequence-diagrams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kroki.io/#support"&gt;kroki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/softwaretechnik-berlin/dbml-renderer"&gt;dbml-renderer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/aplevich/dpic"&gt;dpic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BurntSushi/erd"&gt;erd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blockdiag.com/en/actdiag/index.html"&gt;actdiag&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nomnoml.com/"&gt;nomnoml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.umlet.com/"&gt;UMLet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://xosh.org/text-to-diagram/"&gt;https://xosh.org/text-to-diagram/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.asciidoctor.org/diagram-extension/latest/"&gt;https://docs.asciidoctor.org/diagram-extension/latest/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Instead of conclusion
&lt;/h2&gt;

&lt;p&gt;I’m looking for solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;to embed diagrams in markdown file (inside fenced code block)&lt;/li&gt;
&lt;li&gt;ideally it should be possible to generate on the server (during conversion of markdown to HTML)&lt;/li&gt;
&lt;li&gt;ideally it should generate SVG, If we inline SVG (instead of using &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;) it can support

&lt;ul&gt;
&lt;li&gt;dark mode via classes or css variables&lt;/li&gt;
&lt;li&gt;it can be animated&lt;/li&gt;
&lt;li&gt;it can contain html links (&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;you can use Cmd + F to search text in it&lt;/li&gt;
&lt;li&gt;we can add zoom/pan to it as in &lt;a href="https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams#creating-mermaid-diagrams"&gt;github&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;sometime you need to have standalone file as well, which you can include as image for crossposting&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;it should be lightweight (so no headless browsers)&lt;/li&gt;
&lt;li&gt;In some cases it make sense to generate visualisation on the client side (in the browser), for example:

&lt;ul&gt;
&lt;li&gt;fractals with infinite zoom&lt;/li&gt;
&lt;li&gt;interactive visualisations, like in &lt;a href="https://github.com/bijection/g9"&gt;g9&lt;/a&gt; or &lt;a href="https://reality.mentat.org/essays/reality/introduction"&gt;Road to Reality Essays&lt;/a&gt;. In this case they should use high performance solutions (canvas or WebGL)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Diagrams that I’m interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;graphs (aka network, flowchart, DAG, etc.)

&lt;ul&gt;
&lt;li&gt;Hasse diagrams&lt;/li&gt;
&lt;li&gt;subway-scheme like diagrams. For example see: &lt;a href="https://mermaid.js.org/syntax/gitgraph.html"&gt;gitgraph&lt;/a&gt;, &lt;a href="https://metrosets.ac.tuwien.ac.at/"&gt;metrosets&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Euler diagrams (which are often confused with Venn)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jakevdp.github.io/blog/2012/10/07/xkcd-style-plots-in-matplotlib/"&gt;xkcd-style&lt;/a&gt; plots&lt;/li&gt;
&lt;li&gt;sankey diagrams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more twist to this story. Because those diagrams can be represented as text you can use &lt;strong&gt;GhatGPT&lt;/strong&gt; to generate diagrams.&lt;/p&gt;

</description>
      <category>diagram</category>
      <category>markdown</category>
      <category>mermaid</category>
      <category>graphviz</category>
    </item>
    <item>
      <title>Portable markdown links</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Fri, 22 Sep 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/stereobooster/portable-markdown-links-2bn0</link>
      <guid>https://dev.to/stereobooster/portable-markdown-links-2bn0</guid>
      <description>&lt;p&gt;&lt;strong&gt;aka&lt;/strong&gt; relative links, file path links. Software supports &lt;strong&gt;portable markdown links&lt;/strong&gt; if for content 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;[test](folder/test.md)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;editor&lt;/strong&gt; : user can Cmd + Click the link and editor will jump to that file&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;static website generator&lt;/strong&gt; : file pahts are replaced by URLs according generation scheme, for example: &lt;code&gt;[test](folder/test.md)&lt;/code&gt; → &lt;code&gt;&amp;lt;a href="/folder/test/"&amp;gt;test&amp;lt;/a&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bep/portable-hugo-links"&gt;portable-hugo-links&lt;/a&gt; shows how same links work in Github and website generated by Hugo.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Links: &lt;code&gt;[readme](/readme.md)&lt;/code&gt;, which also includes resolution of relative and absolute paths:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[readme](../readme.md)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[readme](./readme.md)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[readme](readme.md)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Anchors: &lt;code&gt;&lt;a href="///readme.md#heading"&gt;readme&lt;/a&gt;&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;for editor: Cmd + Click should navigate to specified section&lt;/li&gt;
&lt;li&gt;for static website generator: anchors should be preserved in html links&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Images: &lt;code&gt;![alt]_(img.jpg)&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;for editor’s preview: images should be displayed&lt;/li&gt;
&lt;li&gt;for static website generator: images should be displayed&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who supports it?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Software&lt;/th&gt;
&lt;th&gt;Does it support PML&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Github&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VSCode&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hugo&lt;/td&gt;
&lt;td&gt;Yes, &lt;a href="https://github.com/bep/portable-hugo-links"&gt;with configuration&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Obsidian&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docusaurus&lt;/td&gt;
&lt;td&gt;Yes, see &lt;a href="https://docusaurus.io/docs/markdown-features/links"&gt;documentation&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/microsoft/vscode/tree/main/extensions/markdown-language-features/server"&gt;Markdown Language Server&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/tchayen/markdown-links"&gt;markdown-links&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jekyll&lt;/td&gt;
&lt;td&gt;Yes, with &lt;a href="https://github.com/benbalter/jekyll-relative-links"&gt;plugin&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Astro&lt;/td&gt;
&lt;td&gt;No, see &lt;a href="https://github.com/withastro/roadmap/discussions/424"&gt;discussion&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;td&gt;No, AFAIK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gatsby&lt;/td&gt;
&lt;td&gt;No, AFAIK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Naming
&lt;/h2&gt;

&lt;p&gt;Why do I call it &lt;strong&gt;portable&lt;/strong&gt;? Because this simple convention allows to use same markdown files with different software wihtout modifications. In the same vein, we can think of portable markdown frontmatter, etc.&lt;/p&gt;

&lt;p&gt;A bit of convention over configuration goes long way.&lt;/p&gt;

</description>
      <category>markdown</category>
    </item>
    <item>
      <title>Speed up navigation between pages</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Tue, 19 Sep 2023 08:00:00 +0000</pubDate>
      <link>https://dev.to/stereobooster/speed-up-navigation-between-pages-5bki</link>
      <guid>https://dev.to/stereobooster/speed-up-navigation-between-pages-5bki</guid>
      <description>&lt;p&gt;Basic idea - navigating between pages is slow, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser waits for network response&lt;/li&gt;
&lt;li&gt;it is expensive to redraw the whole page from scratch. Much faster would be to replace &lt;code&gt;innerHTML&lt;/code&gt; of the document. At least this is common belief, but there are &lt;a href="https://jakearchibald.com/2016/fun-hacks-faster-content/"&gt;nuances to that&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  History
&lt;/h2&gt;

&lt;p&gt;First solution appeared in 2010. See &lt;a href="https://github.com/defunkt/jquery-pjax"&gt;defunkt/jquery-pjax&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Around 2015 "olympic torch" was taken over by &lt;a href="https://github.com/turbolinks/turbolinks"&gt;turbolinks&lt;/a&gt;. Which was part of Rails stack and later replaced by &lt;a href="https://turbo.hotwired.dev/handbook/drive"&gt;Turbo Drive&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Around the same time there were also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MoOx/pjax"&gt;MoOx/pjax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/falsandtru/pjax-api#features"&gt;falsandtru/pjax-api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://barba.js.org/"&gt;barba&lt;/a&gt;, 2016&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/swup/swup"&gt;swup&lt;/a&gt;, 2017&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In 2018 Google jump in on that boat with &lt;a href="https://github.com/GoogleChromeLabs/quicklink"&gt;quicklink&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: later doesn't mean better, because development didn't stop at the release date. For example, &lt;code&gt;swup&lt;/code&gt; recently released v4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it still alive?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One would think that with SPA trend (started around 2015) nobody would care about this kind of solutions, but static website still popular. For example Hugo, Astro and &lt;a href="https://jamstack.org/generators/"&gt;others&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The devil is in the details
&lt;/h2&gt;

&lt;p&gt;I put all those libraries in one category but they are actually different and use different assumptions. For example,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PJAX uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API"&gt;History API&lt;/a&gt; and AJAX (or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch"&gt;fetch&lt;/a&gt;). It takes over browser navigation mechanism&lt;/li&gt;
&lt;li&gt;quicklink on the other side bothers itself only with &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Prefetch"&gt;prefetching&lt;/a&gt;/&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/prerender"&gt;prerendering&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Browser history
&lt;/h3&gt;

&lt;p&gt;So we have &lt;strong&gt;two main approaches&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;don't take over browser navigation

&lt;ul&gt;
&lt;li&gt;pros:&lt;/li&gt;
&lt;li&gt;easy to integrate&lt;/li&gt;
&lt;li&gt;cons:&lt;/li&gt;
&lt;li&gt;animations are not possible&lt;/li&gt;
&lt;li&gt;state of the page reseted, like selected tabs, collapsed menu, scroll position in sidebar&lt;/li&gt;
&lt;li&gt;examples: &lt;code&gt;quicklink&lt;/code&gt;, &lt;a href="https://docs.astro.build/en/guides/integrations-guide/prefetch/"&gt;@astrojs/prefetch&lt;/a&gt;, &lt;a href="https://code.juliancataldo.com/component/astro-hover-prefetch/"&gt;astro-hover-prefetch&lt;/a&gt;, &lt;a href="https://swup.js.org/plugins/preload-plugin/"&gt;swup preload&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;take over browser navigation

&lt;ul&gt;
&lt;li&gt;pros:&lt;/li&gt;
&lt;li&gt;hard to integrate&lt;/li&gt;
&lt;li&gt;cons:&lt;/li&gt;
&lt;li&gt;animations are possible&lt;/li&gt;
&lt;li&gt;state of the page can be preserved but needs additional work&lt;/li&gt;
&lt;li&gt;examples: &lt;code&gt;pjax&lt;/code&gt;, &lt;code&gt;barba&lt;/code&gt;, &lt;code&gt;swup&lt;/code&gt;, &lt;a href="https://github.com/RafidMuhymin/astro-spa"&gt;astro-spa&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adaptability
&lt;/h3&gt;

&lt;p&gt;Besides browser navigation we need to take into account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we don't want to prefetch all the links

&lt;ul&gt;
&lt;li&gt;quicklink uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API"&gt;Intersection Observer&lt;/a&gt; to detect links within the viewport&lt;/li&gt;
&lt;li&gt;Other approach is to do this on hover, focus, tap&lt;/li&gt;
&lt;li&gt;Some libraries allows to specify which links to prefetch&lt;/li&gt;
&lt;li&gt;Or we can statistically &lt;a href="https://github.com/guess-js/guess"&gt;guess&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;we don't want it to do while browser is busy doing something else. You don't want to degrade user experience

&lt;ul&gt;
&lt;li&gt;quicklink uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback"&gt;requestIdleCallback&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Making additional requests on mobile devices may cost user additional money

&lt;ul&gt;
&lt;li&gt;quicklink uses &lt;code&gt;navigator.connection.effectiveType&lt;/code&gt; to detect slow networks, and &lt;code&gt;navigator.connection.saveData&lt;/code&gt; to detect data-saver mode&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related functionality
&lt;/h3&gt;

&lt;p&gt;Main subject is to speed up navigation between pages, but there are related functionalities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;animate&lt;/strong&gt; page navigations, like in &lt;code&gt;swup&lt;/code&gt; and &lt;code&gt;barba&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;replace only part of the page&lt;/strong&gt;, for example to filter list or table, like in &lt;a href="https://turbo.hotwired.dev/handbook/frames"&gt;Turbo Frames&lt;/a&gt;, &lt;a href="https://swup.js.org/plugins/fragment-plugin/"&gt;swup fragment&lt;/a&gt;, &lt;a href="https://docs.astro.build/en/guides/view-transitions/"&gt;astro view transitions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;show &lt;strong&gt;page preview&lt;/strong&gt; on hover, like in &lt;a href="https://help.obsidian.md/Plugins/Page+preview"&gt;Obsidian&lt;/a&gt;. See also &lt;a href="https://github.com/Fischer-L/previewbox"&gt;previewbox&lt;/a&gt; and &lt;a href="https://github.com/cijiugechu/astro-link-preview"&gt;astro-link-preview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;highlight section if navigated with anchor, like in StakOverflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I mainly concern myself with static web sites, but in the context of dynamic websites there is so called "HTML over wire" approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Elixir &lt;a href="https://github.com/phoenixframework/phoenix_live_view"&gt;Phoenix LiveView&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ruby &lt;a href="https://turbo.hotwired.dev/"&gt;Turbo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PHP &lt;a href="https://laravel-livewire.com/"&gt;Laravel livewire&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Complexity with browser history
&lt;/h3&gt;

&lt;p&gt;Basic implementation can look like this&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add global click event listener&lt;/li&gt;
&lt;li&gt;on click you fetch remote content&lt;/li&gt;
&lt;li&gt;extract element that needs to be replaced (for example &lt;code&gt;main&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;replace content with &lt;code&gt;target.innerHTML = response&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Push new item to history with &lt;code&gt;history.pushState(state, "", url);&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt; you need to take care of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;removing event handlers from old HTML&lt;/li&gt;
&lt;li&gt;attaching event handlers to new HTML&lt;/li&gt;
&lt;li&gt;replacing title, maybe announce it to screen reader&lt;/li&gt;
&lt;li&gt;replacing styles&lt;/li&gt;
&lt;li&gt;replacing scripts&lt;/li&gt;
&lt;li&gt;maybe provide a hook so that it would be possible to integrate analytics&lt;/li&gt;
&lt;li&gt;scrolling to top of the page&lt;/li&gt;
&lt;li&gt;listen to history for back navigations&lt;/li&gt;
&lt;li&gt;maybe resolve relative links&lt;/li&gt;
&lt;li&gt;it doesn't preserve state of scroll or tabs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is an error-prone approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  To be continued...
&lt;/h2&gt;

&lt;p&gt;While I was doing the research on the subject I stumbled upon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/web-platform/view-transitions/"&gt;View transitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API"&gt;Navigation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wicg.github.io/nav-speculation/prerendering.html"&gt;"Prerender 2"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/bfcache/"&gt;Back/forward cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/cache-api-quick-guide/"&gt;Cache API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will need more time to finish the research&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Markdown parsers</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Sun, 17 Sep 2023 10:00:09 +0000</pubDate>
      <link>https://dev.to/stereobooster/markdown-parsers-171g</link>
      <guid>https://dev.to/stereobooster/markdown-parsers-171g</guid>
      <description>&lt;h2&gt;
  
  
  JS
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/micromark/micromark"&gt;micromark&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/syntax-tree/mdast-util-from-markdown"&gt;mdast-util-from-markdown&lt;/a&gt; used by&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/remarkjs/remark/tree/main/packages/remark-parse"&gt;remark-parse&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://prettier.io/blog/2017/11/07/1.8.0.html"&gt;prettier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mdx-js/mdx/tree/main/packages/mdx"&gt;mdx&lt;/a&gt;, which is used by&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gatsbyjs.com/"&gt;Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/remark-mdc"&gt;mdc&lt;/a&gt;, which is used by&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuxt.com/"&gt;Nuxt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/markdown-it/markdown-it"&gt;markdown-it&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.11ty.dev/docs/languages/markdown/"&gt;11ty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://v2.vuepress.vuejs.org/"&gt;vuepress&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vitepress.dev/"&gt;vitepress&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/showdownjs/showdown"&gt;showdown&lt;/a&gt; used by (based on what they say)

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform"&gt;GoogleCloudPlatform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.meteor.com/"&gt;Meteor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Stackexchange - forked as &lt;a href="https://code.google.com/archive/p/pagedown/"&gt;PageDown&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jonschlinkert/remarkable"&gt;remarkable&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;used by &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/markedjs/marked"&gt;marked&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hexo.io/index.html"&gt;Hexo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/docsifyjs/docsify"&gt;docsify&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/markup-it"&gt;markup-it&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gitbook.com/"&gt;gitbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/commonmark/commonmark.js/"&gt;commonmark.js&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;is the JS &lt;strong&gt;reference implementation of CommonMark&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  WASM
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/rsms/markdown-wasm"&gt;markdown-wasm&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;uses &lt;strong&gt;md4c&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://markdown.fastlylabs.com/"&gt;Wasm Markdown&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;uses &lt;strong&gt;pulldown-cmark&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/rodneylab/parsedown"&gt;parsedown&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;uses &lt;strong&gt;pulldown-cmark&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  See also
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://umemotoctrl.github.io/mdpjs/"&gt;Online playground&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://npmtrends.com/commonmark-vs-markdown-it-vs-marked-vs-markup-it-vs-micromark-vs-remark-parse-vs-remarkable-vs-showdown"&gt;npmtrends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://biomejs.dev/internals/language-support/"&gt;biome&lt;/a&gt; promises to add markdown eventually&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rust
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/raphlinus/pulldown-cmark"&gt;pulldown-cmark&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rust-lang/mdBook"&gt;mdBook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cobalt-org.github.io/"&gt;cobalt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/getzola/zola/tree/master/components/markdown"&gt;zola-markdown&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;uses &lt;a href="https://pest.rs/"&gt;pest.rs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;used by &lt;a href="https://github.com/getzola/zola"&gt;zola&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/wooorm/markdown-rs"&gt;markdown-rs&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Sibling to &lt;strong&gt;micromark&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kivikakk/comrak"&gt;comrak&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Rust port of &lt;strong&gt;cmark-gfm&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/markdown-it-rust/markdown-it"&gt;markdown-it-rust&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Rust port of &lt;strong&gt;markdown-it&lt;/strong&gt; library.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Trivernis/snekdown"&gt;Snekdown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Golang
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/yuin/goldmark"&gt;goldmark&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/getting-started/configuration-markup/"&gt;Hugo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gomarkdown/markdown"&gt;gomarkdown/markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/golang-commonmark/markdown"&gt;golang-commonmark/markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/russross/blackfriday"&gt;blackfriday&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mmarkdown/mmark"&gt;mmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/src.elv.sh/pkg/md"&gt;md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/cmark-gfm"&gt;cmark-gfm&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;used by Github&lt;/li&gt;
&lt;li&gt;is an extended version of the &lt;strong&gt;cmark&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/commonmark/cmark"&gt;cmark&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;is the C &lt;strong&gt;reference implementation of CommonMark&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mity/md4c"&gt;md4c&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Zig
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/kivikakk/koino"&gt;koino&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Zig port of &lt;strong&gt;comrak&lt;/strong&gt;, which is Rust port of &lt;strong&gt;cmark-gfm&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/demizer/markzig"&gt;markzig&lt;/a&gt; archived&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tree-sitter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tree-sitter.github.io/tree-sitter/"&gt;Tree-sitter&lt;/a&gt; is a parser generator tool and an incremental parsing library. There are bindings for many languages (including Rust and JS)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MDeiml/tree-sitter-markdown"&gt;MDeiml/tree-sitter-markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ikatyang/tree-sitter-markdown"&gt;ikatyang/tree-sitter-markdown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ruby
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/jekyll/jekyll-commonmark"&gt;jekyll-commonmark&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;used by &lt;a href="https://jekyllrb.com/docs/configuration/markdown/"&gt;jekyll&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;uses &lt;strong&gt;cmark-gfm&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/vmg/redcarpet"&gt;redcarpet&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/slatedocs/slate"&gt;slate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/docs/configuration/markdown/"&gt;jekyll&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://python-markdown.github.io/"&gt;python-markdown&lt;/a&gt; used by

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mkdocs.org/"&gt;MkDocs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://getpelican.com/"&gt;Pelican&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/frostming/marko"&gt;marko&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Clojure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/askonomm/clarktown"&gt;clarktown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Elixir
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/am-kantox/md"&gt;md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Other
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/BubuAnabelas/awesome-markdown#libraries"&gt;"awesome-markdown" libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lezer-parser/markdown"&gt;lezer-parser/markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://babelmark.github.io/"&gt;babelmark&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Crticism
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jgm/djot#rationale"&gt;djot rationale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.swyx.io/markdown-mistakes"&gt;markdown mistakes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>markdown</category>
      <category>parser</category>
      <category>javascript</category>
      <category>go</category>
    </item>
    <item>
      <title>Component libraries trends</title>
      <dc:creator>stereobooster</dc:creator>
      <pubDate>Sat, 16 Sep 2023 13:00:00 +0000</pubDate>
      <link>https://dev.to/stereobooster/component-libraries-trends-12fn</link>
      <guid>https://dev.to/stereobooster/component-libraries-trends-12fn</guid>
      <description>&lt;h2&gt;
  
  
  Headless and accessible
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;aka&lt;/strong&gt; unstyled, renderless&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main idea&lt;/strong&gt; is to provide all the logic for component (often accessibility) without forcing any visual appearance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/reach/reach-ui/issues/972"&gt;Reach UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.radix-ui.com/primitives/docs/overview/introduction"&gt;Radix primitives&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://headlessui.com/"&gt;headlessui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ariakit.org/components"&gt;ariakit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;not a component library, but worth to mention &lt;a href="https://react-spectrum.adobe.com/"&gt;React Spectrum&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Based on a design system
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Main idea&lt;/strong&gt; it is based on thoroughly documented design-system, so it gives more than just components. There can be a lot of implementations based on the same design system. For example, for &lt;a href="https://m3.material.io/"&gt;material design&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mui.com/"&gt;mui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://v2.vuetifyjs.com/en/"&gt;vuetify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://suid.io/"&gt;SUID&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other design systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://designsystemsrepo.com/design-systems/"&gt;https://designsystemsrepo.com/design-systems/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For mobile development
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Main idea&lt;/strong&gt; is to support mobile devices, for example, &lt;a href="https://daisyui.com/components/bottom-navigation/"&gt;bottom navigation bar&lt;/a&gt;, &lt;a href="https://github.com/bmcmahen/react-gesture-stack"&gt;gestures&lt;/a&gt;, &lt;a href="https://konstaui.com/react/safe-areas"&gt;safe-areas&lt;/a&gt;, &lt;a href="https://tamagui.dev/docs/components/sheet/1.59.0"&gt;action-sheet&lt;/a&gt; etc. Can be used for developing native apps with WebView (PhoneGap/Cordova) or for PWAs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://konstaui.com/"&gt;Konsta UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://daisyui.com/components/"&gt;daisyUI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sancho-ui.com/"&gt;Sancho&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://framework7.io/kitchen-sink/core/"&gt;Framework7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vant-ui.github.io/vant/mobile.html#/en-US"&gt;Vant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://onsen.io/"&gt;Onsen UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mobileui.github.io/#getting-started"&gt;mobileui&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All react-native-web component libraries are also in this category:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tamagui.dev/"&gt;tamagui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactnativepaper.com/"&gt;paper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wix.github.io/react-native-ui-lib/"&gt;RNUI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://magnus-ui.com/"&gt;magnus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://akveo.github.io/react-native-ui-kitten/docs/guides/running-on-the-web#existing-expo-applications"&gt;React Native UI Kitten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ui.gluestack.io/"&gt;gluestack-ui&lt;/a&gt;, successor for &lt;a href="https://nativebase.io/"&gt;NativeBase&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Based on Tailwind
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Main idea&lt;/strong&gt;: it is based on Tailwind. While CSS-in-JS in very unstable situation and they keep inventing zero-runtime solutions, Tailwind just works. So there is whole new wave of component libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://daisyui.com/components/"&gt;daisyUI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://konstaui.com/"&gt;Konsta UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextui.org/"&gt;NextUI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.flowbite-react.com/"&gt;Flowbite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/creativetimofficial/material-tailwind"&gt;Material Tailwind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://preline.co/examples.html"&gt;preline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ripple-ui.com/"&gt;ripple-ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailwind-elements.com/docs/react/"&gt;tailwind-elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sailboatui.com/docs/components/tooltip/"&gt;sailboatui&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Not a component library
&lt;/h2&gt;

&lt;p&gt;This is new interesting approach. We already have enough good component libraries, so instead of creating another one, this tool allows to re-use existing. It is more like collection of snippets.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://ui.shadcn.com/docs"&gt;shadcn/ui&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>css</category>
      <category>components</category>
    </item>
  </channel>
</rss>
