<?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: Ben Gubler</title>
    <description>The latest articles on DEV Community by Ben Gubler (@bgub).</description>
    <link>https://dev.to/bgub</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F364775%2F0a84feb2-2c58-48e5-abae-7a1d214a6f88.png</url>
      <title>DEV Community: Ben Gubler</title>
      <link>https://dev.to/bgub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bgub"/>
    <language>en</language>
    <item>
      <title>Super excited to share! Thoughts?</title>
      <dc:creator>Ben Gubler</dc:creator>
      <pubDate>Wed, 17 Sep 2025 20:15:31 +0000</pubDate>
      <link>https://dev.to/bgub/super-excited-to-share-thoughts-2j7d</link>
      <guid>https://dev.to/bgub/super-excited-to-share-thoughts-2j7d</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bgub/introducing-ts-base-a-modern-typescript-library-template-5g9n" class="crayons-story__hidden-navigation-link"&gt;Introducing ts-base: A Modern TypeScript Library Template&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bgub" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F364775%2F0a84feb2-2c58-48e5-abae-7a1d214a6f88.png" alt="bgub profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bgub" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ben Gubler
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ben Gubler
                
              
              &lt;div id="story-author-preview-content-2852196" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bgub" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F364775%2F0a84feb2-2c58-48e5-abae-7a1d214a6f88.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ben Gubler&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bgub/introducing-ts-base-a-modern-typescript-library-template-5g9n" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 17 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bgub/introducing-ts-base-a-modern-typescript-library-template-5g9n" id="article-link-2852196"&gt;
          Introducing ts-base: A Modern TypeScript Library Template
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bgub/introducing-ts-base-a-modern-typescript-library-template-5g9n#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>typescript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Introducing ts-base: A Modern TypeScript Library Template</title>
      <dc:creator>Ben Gubler</dc:creator>
      <pubDate>Wed, 17 Sep 2025 20:09:07 +0000</pubDate>
      <link>https://dev.to/bgub/introducing-ts-base-a-modern-typescript-library-template-5g9n</link>
      <guid>https://dev.to/bgub/introducing-ts-base-a-modern-typescript-library-template-5g9n</guid>
      <description>&lt;p&gt;Eight years ago, I released my first open-source TypeScript library — &lt;a href="https://github.com/squirrellyjs/squirrelly" rel="noopener noreferrer"&gt;Squirrelly&lt;/a&gt; — which contained two files, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;index.js&lt;/code&gt;. Five years ago, I released &lt;a href="https://github.com/bgub/eta" rel="noopener noreferrer"&gt;Eta&lt;/a&gt; with many more features including testing, linting, bundling, and CI/CD.&lt;/p&gt;

&lt;p&gt;I thought was a pretty solid development setup, but times change and the JavaScript ecosystem moves fast. New tools have emerged, best practices have evolved, and the complexity of properly publishing an npm package has somehow gotten both easier &lt;em&gt;and&lt;/em&gt; more overwhelming at the same time.&lt;/p&gt;

&lt;p&gt;Just look at the &lt;code&gt;package.json&lt;/code&gt; "exports" field evolution if you want a headache. Or try figuring out the right combination of TypeScript configs, bundlers, and CI workflows to publish a library that works seamlessly across Node, Deno, Bun, and browsers. It's surprisingly tricky to get right.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;a href="https://github.com/bgub/ts-base" rel="noopener noreferrer"&gt;&lt;strong&gt;ts-base&lt;/strong&gt;&lt;/a&gt; — a modern TypeScript library starter template that handles all of this complexity for you. It's opinionated, battle-tested, and designed to work out-of-the-box with every major JavaScript runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is ts-base?
&lt;/h2&gt;

&lt;p&gt;ts-base is a TypeScript library template that embraces modern tooling and automated workflows. Instead of starting from scratch or copying outdated boilerplate, you get a complete development environment that includes linting, testing, building, releasing, and publishing — all pre-configured and ready to go.&lt;/p&gt;

&lt;p&gt;The template is built around three core principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-runtime first&lt;/strong&gt;: Works seamlessly across Node, Deno, Bun, and browsers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation over configuration&lt;/strong&gt;: Minimal setup, maximum automation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern tooling&lt;/strong&gt;: ESM-only, latest TypeScript, and carefully chosen dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Multi-Runtime Architecture
&lt;/h2&gt;

&lt;p&gt;The heart of ts-base is its runtime-agnostic design. Instead of trying to make one file work everywhere (and dealing with compatibility headaches), the template uses a clean separation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/internal.ts - Core logic, no runtime-specific APIs&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}):&lt;/span&gt; &lt;span class="kr"&gt;string&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;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shout&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/index.ts - Node/Bun adapter&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./internal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomBytes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSecureRandomId&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&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;timePart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&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;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timePart&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/browser.ts - Browser adapter&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./internal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getSecureRandomId&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&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;timePart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&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;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&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;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&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="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timePart&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you clean imports for every runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node/Bun&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getSecureRandomId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@your-package/ts-base&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Browser (via bundler)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getSecureRandomId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@your-package/ts-base/browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Deno (direct TypeScript imports)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsr.io/@bgub/ts-base/&amp;lt;version&amp;gt;/src/index.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build system uses &lt;a href="https://tsdown.dev/" rel="noopener noreferrer"&gt;tsdown&lt;/a&gt; to create two optimized bundles: one for Node environments and a separate minified bundle for browsers, both with sourcemaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;ts-base consolidates your tooling around a few excellent choices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Biome&lt;/strong&gt; replaces both ESLint and Prettier with a single, fast tool. No more configuration conflicts or plugin incompatibilities — just consistent formatting and linting that works out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vitest&lt;/strong&gt; provides lightning-fast testing with built-in coverage reporting and customizable thresholds. Tests run in parallel, support TypeScript natively, and include helpful features like mocking and snapshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Size Limit&lt;/strong&gt; monitors your bundle size automatically. It runs in CI and comments on pull requests when your changes would increase the bundle size, helping you catch bloat before it ships.&lt;/p&gt;

&lt;p&gt;The TypeScript configuration is optimized for modern bundlers with settings like &lt;code&gt;moduleResolution: "bundler"&lt;/code&gt; and &lt;code&gt;allowImportingTsExtensions: true&lt;/code&gt; that work great with tools like Vite, Rollup, and esbuild.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated CI/CD Pipeline
&lt;/h2&gt;

&lt;p&gt;One of ts-base's biggest strengths is its complete CI/CD setup. Every aspect of code quality and publishing is automated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality Gates&lt;/strong&gt;: Every pull request triggers linting, type checking, testing, and coverage reporting. The CI uploads coverage to Codecov and comments on PRs with size impact reports.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Release Management&lt;/strong&gt;: Instead of complex semantic-release configurations, ts-base uses Google's Release Please. When commits land on main, Release Please automatically opens a "Release PR" that updates version numbers, generates changelogs, and creates release tags.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated Publishing&lt;/strong&gt;: When you merge the Release PR, GitHub Actions automatically builds and publishes your package to both npm and JSR with full OIDC provenance and security attestation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conventional Commits&lt;/strong&gt;: PR titles are automatically linted to follow conventional commit format, ensuring consistent changelog generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Approach Works Better
&lt;/h2&gt;

&lt;p&gt;Most TypeScript library templates I've seen are either too minimal (leaving you to figure out CI, publishing, and multi-runtime support) or overcomplicated with dozens of dependencies. I've seen templates with packages like &lt;code&gt;@commitlint/cli&lt;/code&gt;, &lt;code&gt;@commitlint/config-conventional&lt;/code&gt;, &lt;code&gt;@semantic-release/changelog&lt;/code&gt;, &lt;code&gt;@semantic-release/git&lt;/code&gt;, &lt;code&gt;@semantic-release/github&lt;/code&gt;, &lt;code&gt;@semantic-release/npm&lt;/code&gt;, and more just for CI publishing!&lt;/p&gt;

&lt;p&gt;ts-base takes a different approach with just 8 total dev dependencies. By choosing Release Please over semantic-release, Biome over ESLint+Prettier, and Vitest over Jest, you get a simpler dependency graph that's easier to maintain and less likely to break.&lt;/p&gt;

&lt;p&gt;The automation philosophy means less configuration and fewer places for things to go wrong. Release Please handles version bumping, changelog generation, and release creation in one tool. The GitHub Actions workflows handle everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic of Release Please
&lt;/h2&gt;

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

&lt;p&gt;Release Please deserves special attention because it transforms how you think about releases. Instead of manually bumping versions or configuring complex semantic-release pipelines, Release Please works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You merge commits to &lt;code&gt;main&lt;/code&gt; using conventional commit messages&lt;/li&gt;
&lt;li&gt;Release Please automatically opens/updates a "Release PR" with version bumps and changelog entries&lt;/li&gt;
&lt;li&gt;When you're ready to release, simply merge the Release PR&lt;/li&gt;
&lt;li&gt;GitHub Actions automatically publishes to npm and JSR&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The system supports pre-releases too. If you release an alpha or beta version, it automatically publishes under the "next" tag on npm. You can override version bumps using &lt;code&gt;Release-As: 2.0.0&lt;/code&gt; in commit messages, and you can maintain multiple release branches (like &lt;code&gt;2.x&lt;/code&gt; and &lt;code&gt;3.x&lt;/code&gt;) that each get their own Release PRs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Setting up ts-base is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clone and customize&lt;/strong&gt;: Clone the repository, remove the &lt;code&gt;.git&lt;/code&gt; folder, and update &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;jsr.json&lt;/code&gt;, and &lt;code&gt;.release-please-manifest.json&lt;/code&gt; with your package details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Claim your package&lt;/strong&gt;: Set the version to &lt;code&gt;0.0.0&lt;/code&gt; in all config files, then run &lt;code&gt;npm publish&lt;/code&gt; locally to claim your package name on npm.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure publishing&lt;/strong&gt;: In npm, set your package to require 2FA for authorization only (not publishing), then add your GitHub workflow as a trusted publisher. On JSR, create your package and add the repository as a trusted source.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up GitHub&lt;/strong&gt;: Push to GitHub, add &lt;code&gt;CODECOV_TOKEN&lt;/code&gt; as a repository secret, and configure branch protection rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start developing&lt;/strong&gt;: Add your code to &lt;code&gt;src/&lt;/code&gt;, write tests, and push commits. Release Please will handle the rest.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I recommend configuring GitHub to only allow squash merging and using "pull request title and commit details" as the default commit message. This keeps your commit history clean and ensures conventional commit compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices &amp;amp; Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repository Settings&lt;/strong&gt;: Enable branch protection on &lt;code&gt;main&lt;/code&gt; with required status checks. Disable merge commits to keep history linear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entry Points&lt;/strong&gt;: Use the main export (&lt;code&gt;@your-package&lt;/code&gt;) for Node/Bun, the browser export (&lt;code&gt;@your-package/browser&lt;/code&gt;) for bundled browser code, and direct TypeScript imports for Deno.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customization&lt;/strong&gt;: If you don't need separate Node/browser builds, delete the unused configuration. The template is designed to be trimmed down to your specific needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing Strategy&lt;/strong&gt;: The template includes examples of testing both shared and platform-specific code, including mocking browser APIs in the Node test environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Publishing a TypeScript library shouldn't require a PhD in tooling configuration. ts-base gives you a modern, opinionated foundation that handles the complexity so you can focus on building great software.&lt;/p&gt;

&lt;p&gt;The template represents eight years of lessons learned from maintaining open source projects. Ready to try it out? Check out the &lt;a href="https://github.com/bgub/ts-base" rel="noopener noreferrer"&gt;ts-base repository&lt;/a&gt; and start building your next library.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>typescript</category>
      <category>programming</category>
    </item>
    <item>
      <title>One config file to rule them all</title>
      <dc:creator>Ben Gubler</dc:creator>
      <pubDate>Mon, 03 Jul 2023 19:41:30 +0000</pubDate>
      <link>https://dev.to/bgub/one-config-file-to-rule-them-all-5nh</link>
      <guid>https://dev.to/bgub/one-config-file-to-rule-them-all-5nh</guid>
      <description>&lt;p&gt;Modern web development involves working with multiple JS build tools and frameworks, each requiring their own configuration files. Managing these configuration files, such as &lt;code&gt;.eslintrc&lt;/code&gt;, &lt;code&gt;next.config.js&lt;/code&gt;, and &lt;code&gt;tailwind.config.js&lt;/code&gt;, can become cumbersome and time-consuming. In this blog post, I'll explore the idea of combining these configuration files into a single file called &lt;code&gt;global.config.js&lt;/code&gt;, centralizing project configuration and reducing distractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Config files everywhere
&lt;/h2&gt;

&lt;p&gt;As I'm writing this blog post, my project root contains an &lt;code&gt;.eslintrc.json&lt;/code&gt;, &lt;code&gt;next.config.js&lt;/code&gt;, &lt;code&gt;postcss.config.js&lt;/code&gt;, &lt;code&gt;tailwind.config.js&lt;/code&gt;, and &lt;code&gt;tsconfig.json&lt;/code&gt;. Although my configuration is pretty out-of-the-box and each file is less than 30 lines, those files take up valuable space in my VSCode sidebar and distract from what's important: my source code.&lt;/p&gt;

&lt;p&gt;My case is far from exceptional. Some other projects use far more configuration files. Imagine how cluttered a project can get when you add a &lt;code&gt;.babelrc&lt;/code&gt;, &lt;code&gt;prettier.config.js&lt;/code&gt;, &lt;code&gt;jest.config.js&lt;/code&gt;, &lt;code&gt;cypress.json&lt;/code&gt;, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: &lt;code&gt;global.config.js&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I'd like to propose a simple solution: consolidating configuration files in a file called &lt;code&gt;global.config.js&lt;/code&gt;. The configuration for each tool would be stored in the exported object, under a key with the name of the npm package.&lt;/p&gt;

&lt;p&gt;Projects should still allow usage of individual configuration files for cases when configuration is large and complex (e.g. &lt;code&gt;tsconfig.json&lt;/code&gt;), but should first scan to see if a &lt;code&gt;global.config.js&lt;/code&gt; exists and configuration for their tool is present.&lt;/p&gt;

&lt;p&gt;Here's what a simple &lt;code&gt;global.config.js&lt;/code&gt; might look like:&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;eslint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/core-web-vitals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;postcss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="na"&gt;autoprefixer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/pages/**/*.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;backgroundImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gradient-radial&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;radial-gradient(var(--tw-gradient-stops))&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tailwindcss/typography&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  But what about types?
&lt;/h2&gt;

&lt;p&gt;Type completion can be easily enabled for &lt;code&gt;global.config.js&lt;/code&gt; by adding type definitions in comments, like Tailwind and NextJS already do in their config files.&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="cm"&gt;/** @type {import('tailwindcss').Config} */&lt;/span&gt;
  &lt;span class="na"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/pages/**/*.{js,ts,jsx,tsx,mdx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;backgroundImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gradient-radial&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;radial-gradient(var(--tw-gradient-stops))&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tailwindcss/typography&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;If you like this idea, submit a PR to your favorite build tool! If you don't, let me know why on Twitter. Or don't and just keep going about your life.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you liked the article, don't forget to share it and follow me at &lt;a href="https://twitter.com/nebrelbug" rel="noopener noreferrer"&gt;@nebrelbug&lt;/a&gt; on Twitter.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Introducing Eta v3</title>
      <dc:creator>Ben Gubler</dc:creator>
      <pubDate>Fri, 23 Jun 2023 03:52:39 +0000</pubDate>
      <link>https://dev.to/bgub/introducing-eta-v3-8m9</link>
      <guid>https://dev.to/bgub/introducing-eta-v3-8m9</guid>
      <description>&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;Today, Eta is my most popular and widely used open-source project. But when I first published it 3 years ago, it wasn't one of my primary focuses. In fact, I created Eta as a slimmed-down variation of &lt;a href="https://squirrelly.js.org" rel="noopener noreferrer"&gt;Squirrelly&lt;/a&gt;, a more complex template engine with features like helpers and filters.&lt;/p&gt;

&lt;p&gt;As time passed, I realized that for most projects, an embedded template engine was actually a better fit than something more complex. Projects which needed complex or client-side HTML processing typically used a framework like React or Vue. Eta's performance and low bundle size, meanwhile, made it a great fit for projects which needed fast processing, low memory usage, or to handle non-XML languages.&lt;/p&gt;

&lt;p&gt;At the same time, Eta had become increasingly popular thanks to its speed, Deno support, and syntactic advantages over EJS. Given those factors, I decided to make Eta my main focus. I spent time writing tutorials, fixing issues, and polishing documentation.&lt;/p&gt;

&lt;p&gt;After several years and some time spent away from programming as a missionary, I finally had time to work on Eta again. I decided to make some big changes to the project, including the build system, API, and documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build System Updates
&lt;/h2&gt;

&lt;p&gt;Despite Eta's advantages and features, it had some big problems. One such problem was the build system. Complex and unwieldy, it was difficult to maintain and update. I dealt with complex configuration files and the necessity of transpiling a version specifically for Deno.&lt;/p&gt;

&lt;p&gt;Changes in version 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;a href="https://github.com/developit/microbundle" rel="noopener noreferrer"&gt;microbundle&lt;/a&gt; to bundle the library helped me avoid the need for complex configuration files.&lt;/li&gt;
&lt;li&gt;Using GitHub Actions to run tests and collect coverage allowed me to consolidate the services I used.&lt;/li&gt;
&lt;li&gt;By setting &lt;code&gt;allowImportingTsExtensions: true&lt;/code&gt; in &lt;code&gt;tsconfig.json&lt;/code&gt;, I was able to avoid using &lt;a href="https://github.com/garronej/denoify" rel="noopener noreferrer"&gt;Denoify&lt;/a&gt; for a separate Deno build.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  API Changes
&lt;/h2&gt;

&lt;p&gt;Another problem was the API. Simple methods like &lt;code&gt;eta.render()&lt;/code&gt; had many function overloads, making types difficult to infer and usage unintuitive. A custom configuration object could be passed in when calling user-exposed functions like &lt;code&gt;render&lt;/code&gt;, &lt;code&gt;parse&lt;/code&gt;, and &lt;code&gt;compile&lt;/code&gt;. In practice, that meant the user-provided configuration had to be merged with the default configuration every time any of those functions was called.&lt;/p&gt;

&lt;p&gt;Changes in version 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's only one export, a named class called &lt;code&gt;Eta&lt;/code&gt;. This class has a single constructor, which processes a configuration object and generates template caches at instantiation time.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;render()&lt;/code&gt; and &lt;code&gt;renderAsync()&lt;/code&gt; functions now have a single function signature.

&lt;ul&gt;
&lt;li&gt;In Eta v2, &lt;code&gt;render()&lt;/code&gt; and &lt;code&gt;renderAsync()&lt;/code&gt; could be used to render either named templates or template strings. Eta v3 introduces two new functions to render template strings: &lt;code&gt;renderString()&lt;/code&gt; and &lt;code&gt;renderStringAsync()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The &lt;code&gt;readFile()&lt;/code&gt; and &lt;code&gt;resolvePath()&lt;/code&gt; functions, which Eta uses internally, can be overridden as class methods by the user.&lt;/li&gt;

&lt;li&gt;Internal variables and methods inside each compiled template are stored in the &lt;code&gt;&lt;strong&gt;eta&lt;/strong&gt;&lt;/code&gt; object, rather than across several variables including &lt;code&gt;res&lt;/code&gt;.&lt;/li&gt;

&lt;li&gt;Rather than allowing users to specify one &lt;code&gt;root&lt;/code&gt; directory and multiple &lt;code&gt;views&lt;/code&gt; directories, users may just specify a single &lt;code&gt;views&lt;/code&gt; directory. This directory is used as the root directory for all template resolution. All template files must be inside this directory or a subdirectory of it, improving template security and reducing expensive file-lookup operations.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Developer Experience Changes
&lt;/h2&gt;

&lt;p&gt;One of the biggest changes in Eta v3 was the addition of detailed runtime errors (inspired by EJS). Consider a template like the following, which will throw because of an undefined variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Template header
&amp;lt;%= undefinedVariable %&amp;gt;
Lorem Ipsum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eta v2 would throw an error with some generic info, but it wasn't incredibly helpful. In contrast, Eta v3 throws a detailed error with the template name, line number, and error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EtaError [ReferenceError]: .../my-dir/templates/runtime-error.eta:2
    1| Template header
 &amp;gt;&amp;gt; 2| &amp;lt;%= undefinedVariable %&amp;gt;
    3| Lorem Ipsum

undefinedVariable is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Documentation Changes
&lt;/h2&gt;

&lt;p&gt;The documentation for Eta v2 was extensive but very difficult to navigate. Information about the project was split over 40+ (!) documentation pages, found in multiple folders spread across 3 different website sections.&lt;/p&gt;

&lt;p&gt;The documentation for Eta v3 takes up 9 pages, all found in the same part of the website (&lt;a href="https://eta.js.org" rel="noopener noreferrer"&gt;eta.js.org&lt;/a&gt;). Topics like template syntax and API overview are covered in a single page, rather than being split across multiple pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Eta
&lt;/h2&gt;

&lt;p&gt;I'm proud of the changes included in Eta v3, and of the project as a whole. Much thanks to those who contributed to the project through PRs, issues, and suggestions. Additional thanks to projects like &lt;a href="https://github.com/mde/ejs" rel="noopener noreferrer"&gt;ejs&lt;/a&gt;, from which Eta continues to draw inspiration.&lt;/p&gt;

&lt;p&gt;I see Eta as mostly feature-complete at this point, though I'll continue to fix bugs and add some small features. I'd encourage current users of the library to upgrade to v3, and I hope new users will find Eta to be a great fit for their projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/eta-dev/eta" rel="noopener noreferrer"&gt;Eta on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/eta" rel="noopener noreferrer"&gt;Eta on npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eta.js.org" rel="noopener noreferrer"&gt;Eta website and docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If you liked the article, don't forget to share it and follow me at &lt;a href="https://twitter.com/nebrelbug" rel="noopener noreferrer"&gt;@nebrelbug&lt;/a&gt; on Twitter.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>npm</category>
      <category>library</category>
    </item>
    <item>
      <title>Looking for a new lead maintainer for the Eta template engine</title>
      <dc:creator>Ben Gubler</dc:creator>
      <pubDate>Fri, 09 Oct 2020 13:24:43 +0000</pubDate>
      <link>https://dev.to/bgub/looking-for-a-new-lead-maintainer-for-the-eta-template-engine-4bge</link>
      <guid>https://dev.to/bgub/looking-for-a-new-lead-maintainer-for-the-eta-template-engine-4bge</guid>
      <description>&lt;p&gt;Hey everyone, this is &lt;a href="https://github.com/nebrelbug" rel="noopener noreferrer"&gt;@nebrelbug&lt;/a&gt; -- the lead maintainer of the &lt;a href="https://eta.js.org" rel="noopener noreferrer"&gt;Eta template engine&lt;/a&gt; and its companion project, &lt;a href="https://squirrelly.js.org" rel="noopener noreferrer"&gt;Squirrelly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On October 28, 2020, I will leave on a 2-year service opportunity. During that time, I will be unable to work on any of my open-source projects.&lt;/p&gt;

&lt;p&gt;Since my date of departure is drawing closer, I've decided it is time to begin &lt;strong&gt;looking for a new lead maintainer for Eta&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintainer Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Proficiency in TypeScript&lt;/li&gt;
&lt;li&gt;Open-source experience. Preferably the maintainer/developer of an open-source JS/TS project with 50+ stars&lt;/li&gt;
&lt;li&gt;Willing to test changes &amp;amp; not break anything important 😉&lt;/li&gt;
&lt;li&gt;Willing to follow the Project Goals (see below)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Maintainer Jobs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix bugs&lt;/li&gt;
&lt;li&gt;Answer questions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A maintainer could also update documentation, write tutorials, write plugins, write integrations (ex. for Fastify, Eleventy, Koa) etc. In case this sounds overwhelming, remember that Eta is only 2.5KB minzipped 😂&lt;/p&gt;

&lt;p&gt;Ideally a maintainer would be willing to maintain Squirrelly as well (they share much of the same codebase) but that's definitely not a requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Goals
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follow SemVer versioning guidelines

&lt;ul&gt;
&lt;li&gt;This means Eta's public API should remain backwards-compatible (at least within v1.x.x)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Remain lightweight

&lt;ul&gt;
&lt;li&gt;Browser build should never exceed 3KB minzipped&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Remain stable&lt;/li&gt;

&lt;li&gt;Keep parsing engine

&lt;ul&gt;
&lt;li&gt;Eta's parser has been optimized quite a bit for performance and reliability, and any significant changes run too high a risk of impacting either&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fallback Plan
&lt;/h2&gt;

&lt;p&gt;There's always the possibility I will be unable to find a new lead maintainer before my departure. This is one of the reasons I've accelerated development so much. It's also the reason Eta and Squirrelly are so extensively tested.&lt;/p&gt;

&lt;p&gt;I feel confident that Eta is stable enough it could continue to function, if needs be, without maintenance for several years. It has a stable API, is well tested, and has decent documentation. Many features can be added as 3rd-party plugins rather than core parts of the library. Moreover, Eta's small size reduces the likelihood that important bugs exist inside the code.&lt;/p&gt;

&lt;p&gt;That scenario, though, would be far from ideal, so I've made several backup plans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give repository + npm access to a friend I trust to act in my behalf. This friend would not modify Eta's code, but could give access if, while I was gone, someone asked to become a maintainer&lt;/li&gt;
&lt;li&gt;Add the project to &lt;a href="https://www.codeshelter.co/" rel="noopener noreferrer"&gt;https://www.codeshelter.co/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I'm going to be away for 2 years and unable to maintain Eta.&lt;/p&gt;

&lt;p&gt;In case I am unable to find a maintainer before I leave, I will give repository access to a trusted friend and add the repository to &lt;a href="https://www.codeshelter.co/" rel="noopener noreferrer"&gt;https://www.codeshelter.co/&lt;/a&gt; (allowing anyone to apply for maintainership).&lt;/p&gt;

&lt;p&gt;If you want to become one of Eta's maintainers and have experience with open-source and TypeScript, message me on &lt;a href="https://gitter.im/nebrelbug" rel="noopener noreferrer"&gt;Gitter&lt;/a&gt;, email me at nebrelbug [at] gmail [dot] com, or contact me some other way!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>deno</category>
      <category>typescript</category>
      <category>node</category>
    </item>
    <item>
      <title>Adding Deno support to the Eta template engine</title>
      <dc:creator>Ben Gubler</dc:creator>
      <pubDate>Mon, 14 Sep 2020 14:43:19 +0000</pubDate>
      <link>https://dev.to/bgub/adding-deno-support-to-the-eta-template-engine-28n7</link>
      <guid>https://dev.to/bgub/adding-deno-support-to-the-eta-template-engine-28n7</guid>
      <description>&lt;p&gt;&lt;em&gt;How I added Deno support to my module, Eta, using Denoify&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;A few months ago I wrote about my creation of &lt;a href="https://eta.js.org" rel="noopener noreferrer"&gt;Eta&lt;/a&gt;, an embedded JavaScript template engine.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;div class="ltag__link__content"&gt;
    &lt;div class="missing"&gt;
      &lt;h2&gt;Article No Longer Available&lt;/h2&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Since then, Eta has met with a fair bit of success (it's now used by Facebook's popular &lt;a href="https://v2.docusaurus.io" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt; library to generate SSR pages) and has seen quite a few updates. &lt;/p&gt;

&lt;p&gt;I was particularly excited to add &lt;strong&gt;Deno support&lt;/strong&gt;.  Since Deno is relatively new, not many template engines are compatible with it, and none of them are as full-featured as Eta. Additionally, Eta is written in TypeScript, which is ideal for Deno modules because Deno has built-in support for TypeScript.&lt;/p&gt;

&lt;p&gt;Libraries like EJS and lodash.template are still far more popular than Eta for Node.js users, despite being less lightweight, less reliable, and slower. I hoped that adding Deno support to Eta would increase its popularity where older libraries weren't an option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I knew actually porting the module to use Deno's syntax would be quite easy. All I needed to do was add the &lt;code&gt;.ts&lt;/code&gt; ending to imports and use Deno's &lt;a href="https://deno.land/std@0.68.0" rel="noopener noreferrer"&gt;standard library&lt;/a&gt; instead of Node built-ins like &lt;code&gt;fs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I could see a few challenges, though. First of all, Eta needed to continue working with Node.js. Using Deno, you import modules using URLs:&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;import&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt;  &lt;span class="k"&gt;as&lt;/span&gt;  &lt;span class="nx"&gt;fs&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt;  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.66.0/fs/mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This definitely does not work in Node.js and will cause errors.&lt;/p&gt;

&lt;p&gt;Second, Deno resolves file paths differently than Node.js with TypeScript. Using the file extension &lt;code&gt;.ts&lt;/code&gt; in imports – like &lt;code&gt;import someMod from './some-mod.ts'&lt;/code&gt; – causes Node to throw an error, but not specifying the extension causes Deno to throw an error.&lt;/p&gt;

&lt;p&gt;Finally, I planned to host Eta on &lt;a href="https://deno.land/x" rel="noopener noreferrer"&gt;https://deno.land/x&lt;/a&gt;, Deno's 3rd-party module registry. I wanted users to be able to import the module using a short URL, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;eta&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/x/eta/mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than having to specify a long nested directory path, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;eta&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/x/eta/dist/deno/mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;After a bit of internet research, I found a library called &lt;a href="https://github.com/garronej/denoify" rel="noopener noreferrer"&gt;Denoify&lt;/a&gt;. Denoify is a build tool that takes TypeScript source files and outputs files built for Deno.&lt;/p&gt;

&lt;p&gt;Denoify automatically converts import paths to work with Deno, converting statements like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;myFunc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-func&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;myFunc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./my-func.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main advantage of Denoify, though, is that it lets you provide a Deno-specific implementation of your files. &lt;/p&gt;

&lt;p&gt;Say you have a file, &lt;code&gt;file-handlers.ts&lt;/code&gt;, that requires the Node &lt;code&gt;fs&lt;/code&gt; module. With Denoify, you can create a &lt;code&gt;file-handlers.deno.ts&lt;/code&gt; file that uses Deno's standard library &lt;code&gt;fs&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Denoify will automatically swap out the override file when you build (this will sound familiar to users of React Native, which has a &lt;a href="https://reactnative.dev/docs/platform-specific-code#platform-specific-extensions" rel="noopener noreferrer"&gt;feature this was based on&lt;/a&gt;). It turns out this is a &lt;em&gt;super&lt;/em&gt; helpful feature.&lt;/p&gt;

&lt;p&gt;In my case, I was able to extract all my file handling logic into one file called &lt;code&gt;file-methods.ts&lt;/code&gt;, and created a Deno-specific implementation at &lt;code&gt;file-methods.deno.ts&lt;/code&gt;. Other scripts could &lt;code&gt;import ... from './file-methods'&lt;/code&gt; just like normal, but &lt;code&gt;file-methods.ts&lt;/code&gt; itself was a different file inside the Deno build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;

&lt;p&gt;The last thing I had to do before release was add testing for the Deno build. Luckily, Deno has a &lt;a href="https://deno.land/manual/testing/assertions" rel="noopener noreferrer"&gt;built-in assertions module&lt;/a&gt;. Its syntax is fairly similar to other assertion libraries I'd used – as an example, here's a simple test I wrote.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assertEquals&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://deno.land/std@0.67.0/testing/asserts.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../deno_dist/mod.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;simple render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Hi &amp;lt;%=it.name%&amp;gt;`&lt;/span&gt;
  &lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ben&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hi Ben&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ended up creating a small subdirectory named &lt;code&gt;deno/&lt;/code&gt; inside my main tests folder. There I put several tests that focused mainly on general functionality (just in case somehow the build went wrong and everything broke) and file handling (Eta has, as described above, unique file handling code for Deno).&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Steps
&lt;/h3&gt;

&lt;p&gt;It turns out that linters, test files, and documentation generators try to operate on every single file they see within their input directory, even if it isn't directly in their test path.&lt;/p&gt;

&lt;p&gt;I spent a lot of time figuring out how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make ESLint ignore &lt;code&gt;*.deno.ts&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;Make Prettier not try to format Deno files&lt;/li&gt;
&lt;li&gt;Make Jest ignore the &lt;code&gt;test/deno&lt;/code&gt; subdirectory&lt;/li&gt;
&lt;li&gt;Make Coveralls ignore the &lt;code&gt;test/deno&lt;/code&gt; subdirectory&lt;/li&gt;
&lt;li&gt;Make TypeDoc ignore Deno files&lt;/li&gt;
&lt;li&gt;Etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, though, I got all of my Deno files correctly ignored. I added the &lt;code&gt;denoify&lt;/code&gt; command to my build script, and ... voila! Eta supported Deno! &lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing
&lt;/h3&gt;

&lt;p&gt;One really nice feature of &lt;a href="https://deno.land/x" rel="noopener noreferrer"&gt;https://deno.land/x&lt;/a&gt; is that it supports adding a module that lives in the subdirectory of a Git repository. In my case, I configured &lt;code&gt;denoify&lt;/code&gt; to create an output folder called &lt;code&gt;deno_dist&lt;/code&gt;. This folder contains all of Eta's source files, as well as &lt;code&gt;README.md&lt;/code&gt; and &lt;code&gt;LICENSE&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I added Eta to &lt;a href="https://deno.land/x" rel="noopener noreferrer"&gt;https://deno.land/x&lt;/a&gt;, so users can view and import it from &lt;a href="https://deno.land/x/eta" rel="noopener noreferrer"&gt;https://deno.land/x/eta&lt;/a&gt;. The registry automatically updates the module, thanks to webhooks, every time I create a new tagged release.&lt;/p&gt;

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

&lt;p&gt;So there we have it, an account of how I gave my npm package Deno support! I hope this helps any who are working to bring Deno support to their packages and libraries. Don't hesitate to ask in the comments if you have any questions!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;⚡ Obligatory shameless plug ⚡&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you're looking for a template engine for your next Deno project, try &lt;a href="https://eta.js.org" rel="noopener noreferrer"&gt;Eta&lt;/a&gt;! It's lightweight, has great performance, and is super configurable.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://eta.js.org/docs/learn/install" rel="noopener noreferrer"&gt;Eta's documentation&lt;/a&gt;, or see examples of Eta being used with &lt;a href="https://github.com/asos-craigmorten/opine/tree/main/examples/eta" rel="noopener noreferrer"&gt;Opine&lt;/a&gt; and &lt;a href="https://github.com/alosaur/alosaur/tree/master/examples/eta" rel="noopener noreferrer"&gt;Alosaur&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>deno</category>
      <category>typescript</category>
      <category>javascript</category>
      <category>denoify</category>
    </item>
    <item>
      <title>I built a JS template engine 3x faster than EJS</title>
      <dc:creator>Ben Gubler</dc:creator>
      <pubDate>Sat, 11 Apr 2020 15:33:46 +0000</pubDate>
      <link>https://dev.to/bgub/i-built-a-js-template-engine-3x-faster-than-ejs-lj8</link>
      <guid>https://dev.to/bgub/i-built-a-js-template-engine-3x-faster-than-ejs-lj8</guid>
      <description>&lt;p&gt;After hundreds of hours of development, I finally released my open-source project last week. &lt;a href="https://eta.js.org" rel="noopener noreferrer"&gt;Eta&lt;/a&gt; is a pluggable, lightweight, and super-fast JavaScript template engine I created to serve as an alternative to doT and EJS.&lt;/p&gt;

&lt;p&gt;Before I get into discussing my development journey, though, let me outline some differences between Eta and EJS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eta vs EJS&lt;/strong&gt;&lt;br&gt;
Eta's syntax is very similar to EJS' (most templates should work with either engine), Eta has a similar API, and Eta and EJS share the same file-handling logic. Here are the differences between Eta and EJS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eta is more lightweight. Eta weighs around &lt;strong&gt;2KB gzipped&lt;/strong&gt;, while EJS is &lt;strong&gt;4.4KB gzipped&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Eta compiles and renders templates &lt;strong&gt;&lt;em&gt;much&lt;/em&gt; faster than EJS&lt;/strong&gt;. Check out these benchmarks: &lt;a href="https://ghcdn.rawgit.org/eta-dev/eta/master/browser-tests/benchmark.html" rel="noopener noreferrer"&gt;https://ghcdn.rawgit.org/eta-dev/eta/master/browser-tests/benchmark.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Eta allows left whitespace control (with &lt;code&gt;-&lt;/code&gt;), something that doesn't work in EJS because EJS uses &lt;code&gt;-&lt;/code&gt; on the left side to indicate that the value shouldn't be escaped. Instead, Eta uses &lt;code&gt;~&lt;/code&gt; to output a raw value&lt;/li&gt;
&lt;li&gt;Eta gives you more flexibility with delimeters -- you could set them to &lt;code&gt;{{&lt;/code&gt; and &lt;code&gt;}}&lt;/code&gt;, for example, while with EJS this isn't possible&lt;/li&gt;
&lt;li&gt;Eta adds plugin support&lt;/li&gt;
&lt;li&gt;Comments in Eta use &lt;code&gt;/* ... */&lt;/code&gt; which allows commenting around template tags and is more consistent&lt;/li&gt;
&lt;li&gt;Eta parses strings correctly. &lt;em&gt;Example: &lt;code&gt;&amp;lt;%= "%&amp;gt;" %&amp;gt;&lt;/code&gt; works in Eta, while it breaks in EJS&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Eta exposes Typescript types and distributes a UMD build&lt;/li&gt;
&lt;li&gt;Custom tag-type prefixes. &lt;em&gt;Example: you could change &lt;code&gt;&amp;lt;%=&lt;/code&gt; to &lt;code&gt;&amp;lt;%*&lt;/code&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Features Eta and EJS share&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Async support&lt;/li&gt;
&lt;li&gt;Partials support&lt;/li&gt;
&lt;li&gt;File-handling support

&lt;ul&gt;
&lt;li&gt;Eta borrows its file-handling code from EJS, which has over 1.5 million downloads / week. It's pretty reliable 😉&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Express.js support&lt;/li&gt;

&lt;li&gt;EJS syntax-highlighting tools work to some extent with Eta&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why did I build Eta?
&lt;/h2&gt;

&lt;p&gt;My journey started around 2 years ago, when I first created a template engine called &lt;a href="https://squirrelly.js.org" rel="noopener noreferrer"&gt;Squirrelly&lt;/a&gt;. After hundreds of hours of performance benchmarking and trying dozens of different parsing methods, I was finally able to break the speed of all other popular template engines.&lt;/p&gt;

&lt;p&gt;Over the last year, I've been working on the next version of Squirrelly, which adds some important features like template inheritance and better string parsing. While benchmarking Squirrelly, I realized that it was significantly faster than other template engines like EJS -- even though it supported helpers, filters, template inheritance, etc.&lt;/p&gt;

&lt;p&gt;I decided to take the framework behind Squirrelly (all of the behind-the-scenes code) which I had already optimized and tested extensively, and create a simple embedded template engine on top of it. The new template engine would be more configurable than most other template engines, extremely lightweight, very fast, and more reliable than most other template engines.&lt;/p&gt;

&lt;p&gt;I named the new template engine "eta" for a few reasons.&lt;br&gt;
1) Eta means "tiny" in Esperanto, and Eta is a tiny template engine&lt;br&gt;
2) Eta is the name of a letter in the Greek alphabet, which I use as a cool logo&lt;br&gt;
3) "Eta" is 3 letters long, which makes it easy to write template files (ex. &lt;code&gt;footer.eta&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Within several weeks, I was able to create Eta. Because I didn't have to worry about Squirrelly's advanced features, Eta took significantly less time to create and test. Using Docusaurus v2, I was able to throw up a documentation site and write &lt;a href="https://eta.js.org/playground" rel="noopener noreferrer"&gt;a playground&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few lessons learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;In JavaScript (at least the v8 engine) regular expressions are super optimized and are significantly faster than even looping through every character in a string and doing something with it&lt;/li&gt;
&lt;li&gt;TypeScript catches a lot of stupid errors&lt;/li&gt;
&lt;li&gt;Running code coverage tests helps find unnecessary (dead) code&lt;/li&gt;
&lt;li&gt;Performance can be improved significantly by lots of trial-and-error testing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;with () {}&lt;/code&gt; in JavaScript slows down execution and can cause confusing bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Eta's Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Written in TypeScript&lt;/li&gt;
&lt;li&gt;Compiled with Rollup (which, for libraries, creates &lt;strong&gt;much&lt;/strong&gt; smaller builds than Webpack)&lt;/li&gt;
&lt;li&gt;Minified with rollup-plugin-terser&lt;/li&gt;
&lt;li&gt;Tests with Jest&lt;/li&gt;
&lt;li&gt;Code-coverage with Coveralls&lt;/li&gt;
&lt;li&gt;Travis CI for Continuous Integration&lt;/li&gt;
&lt;li&gt;Prettier for formatting&lt;/li&gt;
&lt;li&gt;ESLint with StandardJS compatibility for linting&lt;/li&gt;
&lt;li&gt;TypeDoc for automatic API Doc Generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you enjoyed reading about my journey to create Eta! If you need an embedded JavaScript template engine for a future project, consider using Eta instead of a library like EJS or doT.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>showdev</category>
      <category>node</category>
    </item>
  </channel>
</rss>
