<?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: Saurabh Choudhary</title>
    <description>The latest articles on DEV Community by Saurabh Choudhary (@saurabhzaiswal).</description>
    <link>https://dev.to/saurabhzaiswal</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%2F3965731%2F92f14392-09df-4c4f-8e72-e770c5ed2397.jpg</url>
      <title>DEV Community: Saurabh Choudhary</title>
      <link>https://dev.to/saurabhzaiswal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saurabhzaiswal"/>
    <language>en</language>
    <item>
      <title>Building Better PDF Workflows in Vue 3 with vue-pdf-export</title>
      <dc:creator>Saurabh Choudhary</dc:creator>
      <pubDate>Sat, 12 Sep 2026 16:27:39 +0000</pubDate>
      <link>https://dev.to/saurabhzaiswal/building-better-pdf-workflows-in-vue-3-with-vue-pdf-export-9ed</link>
      <guid>https://dev.to/saurabhzaiswal/building-better-pdf-workflows-in-vue-3-with-vue-pdf-export-9ed</guid>
      <description>&lt;p&gt;Hey Vue developers 👋&lt;/p&gt;

&lt;p&gt;I’ve been working on &lt;code&gt;vue-pdf-export&lt;/code&gt;, a Vue 3 + TypeScript package for generating PDFs directly in the browser.&lt;/p&gt;

&lt;p&gt;I originally created it because I kept rebuilding the same PDF features across different Vue projects - pagination, preview, printing, headers, footers, progress handling, and long-document support.&lt;/p&gt;

&lt;p&gt;Some of the things it supports today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas and selectable/searchable PDF text&lt;/li&gt;
&lt;li&gt;Automatic and manual pagination&lt;/li&gt;
&lt;li&gt;Preview, download, and browser print&lt;/li&gt;
&lt;li&gt;Headers and footers&lt;/li&gt;
&lt;li&gt;Watermarks&lt;/li&gt;
&lt;li&gt;Password protection and PDF permissions&lt;/li&gt;
&lt;li&gt;Clickable links&lt;/li&gt;
&lt;li&gt;Progress reporting and cancellation&lt;/li&gt;
&lt;li&gt;Blob, ArrayBuffer, and Base64 output&lt;/li&gt;
&lt;li&gt;Export-only styling and &lt;code&gt;data-pdf-ignore&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Nuxt 3 and Nuxt 4 SSR-safe imports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;vue-pdf-export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Small example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&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;vue&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;Html2Pdf&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;vue-pdf-export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-pdf-export/style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdfRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InstanceType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Html2Pdf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"pdfRef?.generatePdf()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Generate PDF
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Html2Pdf&lt;/span&gt; &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"pdfRef"&lt;/span&gt; &lt;span class="na"&gt;filename=&lt;/span&gt;&lt;span class="s"&gt;"report.pdf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#pdf-content&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello from Vue&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This content will be exported to PDF.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Html2Pdf&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Playground:&lt;br&gt;&lt;br&gt;
&lt;a href="https://vue-pdf-export-playground.vercel.app/" rel="noopener noreferrer"&gt;https://vue-pdf-export-playground.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documentation:&lt;br&gt;&lt;br&gt;
&lt;a href="https://vue-pdf-export-docs.vercel.app/" rel="noopener noreferrer"&gt;https://vue-pdf-export-docs.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;npm:&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/vue-pdf-export" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/vue-pdf-export&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d really like feedback from Vue developers who have worked with PDFs in production - especially around pagination, large documents, selectable text, and API ergonomics.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>npm</category>
      <category>nuxt</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I built vue-pdf-export - a Vue 3 PDF Export library for real-world browser apps</title>
      <dc:creator>Saurabh Choudhary</dc:creator>
      <pubDate>Sat, 12 Sep 2026 11:24:34 +0000</pubDate>
      <link>https://dev.to/saurabhzaiswal/i-built-vue-pdf-export-a-vue-3-pdf-export-library-for-real-world-browser-apps-27km</link>
      <guid>https://dev.to/saurabhzaiswal/i-built-vue-pdf-export-a-vue-3-pdf-export-library-for-real-world-browser-apps-27km</guid>
      <description>&lt;p&gt;Hey everyone 👋&lt;/p&gt;

&lt;p&gt;I recently released &lt;a href="https://www.npmjs.com/package/vue-pdf-export" rel="noopener noreferrer"&gt;&lt;code&gt;vue-pdf-export&lt;/code&gt;&lt;/a&gt;, a Vue 3 + TypeScript library for generating PDFs directly in the browser.&lt;/p&gt;

&lt;p&gt;I built it after repeatedly needing the same PDF features across different Vue projects and wanting one reusable solution instead of rebuilding the same logic each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it supports
&lt;/h2&gt;

&lt;p&gt;Some of the main features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas and selectable-text PDF rendering&lt;/li&gt;
&lt;li&gt;Automatic and manual pagination&lt;/li&gt;
&lt;li&gt;Preview, download, and browser print&lt;/li&gt;
&lt;li&gt;Headers and footers&lt;/li&gt;
&lt;li&gt;Watermarks and page targeting&lt;/li&gt;
&lt;li&gt;Clickable PDF links&lt;/li&gt;
&lt;li&gt;Password protection and permissions&lt;/li&gt;
&lt;li&gt;PDF metadata and compression&lt;/li&gt;
&lt;li&gt;Progress reporting and cancellation&lt;/li&gt;
&lt;li&gt;Large-document rendering&lt;/li&gt;
&lt;li&gt;Blob, ArrayBuffer, Base64, and PDF data URL outputs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;data-pdf-ignore&lt;/code&gt; for excluding UI elements from exports&lt;/li&gt;
&lt;li&gt;Export-only page and body styling&lt;/li&gt;
&lt;li&gt;Nuxt 3 and Nuxt 4 SSR-safe package imports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The package also has compatibility coverage for npm, pnpm, Yarn, Yarn Plug'n'Play, and Bun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;vue-pdf-export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&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;vue&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;Html2Pdf&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;vue-pdf-export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-pdf-export/style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InstanceType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Html2Pdf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"pdf?.generatePdf()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Generate PDF
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Html2Pdf&lt;/span&gt;
    &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"pdf"&lt;/span&gt;
    &lt;span class="na"&gt;filename=&lt;/span&gt;&lt;span class="s"&gt;"example.pdf"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#pdf-content&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello from Vue&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This content will be exported to PDF.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Html2Pdf&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Canvas or selectable text
&lt;/h2&gt;

&lt;p&gt;Use Canvas rendering when HTML/CSS fidelity is the priority:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Html2Pdf&lt;/span&gt; &lt;span class="na"&gt;render-mode=&lt;/span&gt;&lt;span class="s"&gt;"canvas"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use selectable-text rendering when users need to select, search, and copy text from the generated PDF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Html2Pdf&lt;/span&gt; &lt;span class="na"&gt;render-mode=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the package useful for both highly visual layouts and text-heavy documents such as reports, invoices, statements, and documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Programmatic output
&lt;/h2&gt;

&lt;p&gt;The generated PDF can also be used directly in your application:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;generatePdf&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;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;generatePdfArrayBuffer&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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;generatePdfBase64&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when you want to upload, store, preview, or pass the PDF to another API instead of immediately downloading it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nuxt support
&lt;/h2&gt;

&lt;p&gt;The package is designed so importing it does not crash Nuxt SSR.&lt;/p&gt;

&lt;p&gt;Actual PDF generation still happens in the browser, so a typical Nuxt integration can use &lt;code&gt;ClientOnly&lt;/code&gt; or lazy client-side rendering when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;🧪 &lt;strong&gt;Live Playground&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://vue-pdf-export-playground.vercel.app/" rel="noopener noreferrer"&gt;https://vue-pdf-export-playground.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📖 &lt;strong&gt;Documentation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://vue-pdf-export-docs.vercel.app/" rel="noopener noreferrer"&gt;https://vue-pdf-export-docs.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📦 &lt;strong&gt;npm&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.npmjs.com/package/vue-pdf-export" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/vue-pdf-export&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💬 &lt;strong&gt;Feature requests &amp;amp; feedback&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://vue3-pdf-export.canny.io/" rel="noopener noreferrer"&gt;https://vue3-pdf-export.canny.io/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’d really appreciate feedback from Vue and Nuxt developers, especially around real-world PDF edge cases, API ergonomics, pagination, large documents, or features you would expect from a Vue PDF library.&lt;/p&gt;

&lt;p&gt;The project is still evolving, and real-world feedback is one of the most useful inputs for deciding what to improve next.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>nuxt</category>
      <category>typescript</category>
      <category>npm</category>
    </item>
    <item>
      <title>I Kept Rebuilding PDF Features in Vue 3, So I Built vue-pdf-export</title>
      <dc:creator>Saurabh Choudhary</dc:creator>
      <pubDate>Thu, 03 Sep 2026 23:41:35 +0000</pubDate>
      <link>https://dev.to/saurabhzaiswal/generating-pdfs-in-vue-3-was-messy-so-i-built-vue3-pdf-export-3idb</link>
      <guid>https://dev.to/saurabhzaiswal/generating-pdfs-in-vue-3-was-messy-so-i-built-vue3-pdf-export-3idb</guid>
      <description>&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fledtyo9ulq60rb3lozk1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fledtyo9ulq60rb3lozk1.png" alt="vue3-pdf-export logo" width="799" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I Kept Rebuilding PDF Features in Vue 3, So I Built &lt;code&gt;vue-pdf-export&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I recently published &lt;strong&gt;&lt;code&gt;vue-pdf-export&lt;/code&gt;&lt;/strong&gt;, a Vue + TypeScript package for generating PDFs directly from HTML in browser applications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue + TypeScript browser PDF generation with Canvas and selectable-text
rendering, pagination, preview, large-document support, cancellation,
progress reporting, headers, footers, watermarks, security, metadata,
compression, links, and more.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Before publishing &lt;code&gt;vue-pdf-export&lt;/code&gt;, I had already worked on PDF generation in different projects, including Google projects, CRM applications, and other Vue-based applications.&lt;/p&gt;

&lt;p&gt;I tried different HTML-to-PDF approaches and existing libraries, but in real projects I often ended up building my own custom Vue PDF component around them to handle the specific requirements I needed.&lt;/p&gt;

&lt;p&gt;Whenever a new project needed PDF generation, I found myself solving many of the same problems again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pagination&lt;/li&gt;
&lt;li&gt;page breaks&lt;/li&gt;
&lt;li&gt;preview&lt;/li&gt;
&lt;li&gt;download&lt;/li&gt;
&lt;li&gt;printing&lt;/li&gt;
&lt;li&gt;headers and footers&lt;/li&gt;
&lt;li&gt;image handling&lt;/li&gt;
&lt;li&gt;different PDF options&lt;/li&gt;
&lt;li&gt;loading states&lt;/li&gt;
&lt;li&gt;long-running generation&lt;/li&gt;
&lt;li&gt;browser memory usage&lt;/li&gt;
&lt;li&gt;security and metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This time, instead of creating another project-specific component, I decided to build a cleaner and more complete solution that I could reuse across Vue projects.&lt;/p&gt;

&lt;p&gt;That became &lt;code&gt;vue-pdf-export&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main reason was simple: I did not want to keep rebuilding the same PDF functionality again and again.&lt;/p&gt;

&lt;p&gt;And if it can also save time for other Vue developers who need similar PDF features, even better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;vue-pdf-export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&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;vue&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;Html2Pdf&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;vue3-pdf-export&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue3-pdf-export/style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;InstanceType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;Html2Pdf&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;generatePdf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"generate"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Generate PDF
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Html2Pdf&lt;/span&gt;
    &lt;span class="na"&gt;ref=&lt;/span&gt;&lt;span class="s"&gt;"pdf"&lt;/span&gt;
    &lt;span class="na"&gt;filename=&lt;/span&gt;&lt;span class="s"&gt;"example.pdf"&lt;/span&gt;
    &lt;span class="na"&gt;:preview-modal=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#pdf-content&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello from Vue&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This content will be exported to PDF.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Html2Pdf&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Two rendering modes
&lt;/h2&gt;

&lt;p&gt;One of the biggest additions is support for two different PDF rendering strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Canvas mode
&lt;/h3&gt;

&lt;p&gt;Canvas mode is the traditional HTML-to-PDF path and is best when browser CSS fidelity is the main priority.&lt;/p&gt;

&lt;p&gt;It uses the familiar html2pdf.js / html2canvas / jsPDF pipeline underneath.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Html2Pdf&lt;/span&gt; &lt;span class="na"&gt;render-mode=&lt;/span&gt;&lt;span class="s"&gt;"canvas"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This remains the package default for backward compatibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selectable text mode
&lt;/h3&gt;

&lt;p&gt;Text mode generates real PDF text instead of rasterizing the body into a canvas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Html2Pdf&lt;/span&gt; &lt;span class="na"&gt;render-mode=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting text can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;selected&lt;/li&gt;
&lt;li&gt;searched&lt;/li&gt;
&lt;li&gt;copied&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can be especially useful for reports, invoices, documentation, and other text-heavy PDFs.&lt;/p&gt;

&lt;p&gt;Because this renderer does not behave exactly like a browser layout engine, some advanced CSS may render differently compared with Canvas mode.&lt;/p&gt;

&lt;p&gt;The idea is to let developers choose between maximum browser-style fidelity and real selectable PDF text depending on their use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pagination
&lt;/h2&gt;

&lt;p&gt;The package supports both automatic and manual pagination.&lt;/p&gt;

&lt;p&gt;For an explicit manual page break:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"html2pdf__page-break"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also options for keeping elements together and avoiding unwanted page breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Large-document rendering
&lt;/h2&gt;

&lt;p&gt;Large PDFs were one of the areas I wanted to improve significantly.&lt;/p&gt;

&lt;p&gt;Canvas rendering can become expensive when a browser attempts to create one extremely tall canvas for an entire document.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vue3-pdf-export&lt;/code&gt; can now render paginated Canvas documents incrementally, page by page, instead of depending on one giant full-document capture.&lt;/p&gt;

&lt;p&gt;In one benchmark fixture, the previous long-document capture surface was around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;99.96 million pixels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while incremental rendering kept the maximum capture surface around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.74 million pixels per page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is approximately a 98.26% reduction in peak capture-surface geometry for that fixture.&lt;/p&gt;

&lt;p&gt;This is specifically a rendering-surface measurement, not a claim that total browser memory always drops by the same percentage.&lt;/p&gt;

&lt;p&gt;The current regression suite includes documents across:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 / 25 / 40 / 50 / 60 / 75 / 100 pages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;50-page and 100-page Canvas documents are part of the current verified test range.&lt;/p&gt;

&lt;p&gt;100 pages is not a hard package limit. Practical limits still depend on the document, images, browser memory, rendering mode, security settings, and the device being used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancellation
&lt;/h2&gt;

&lt;p&gt;Long-running PDF generation can now be cancelled.&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="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;cancelGeneration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cancellation is cooperative and works across the generation pipeline.&lt;/p&gt;

&lt;p&gt;The library also tests recovery after cancellation, so a new PDF can be generated after an earlier operation was cancelled.&lt;/p&gt;

&lt;p&gt;Some already-running synchronous browser operations cannot always be stopped immediately, but the pipeline checks for cancellation between major stages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Page-aware progress
&lt;/h2&gt;

&lt;p&gt;Progress reporting now includes more than a simple percentage.&lt;/p&gt;

&lt;p&gt;The package can expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;current progress&lt;/li&gt;
&lt;li&gt;current page&lt;/li&gt;
&lt;li&gt;total pages&lt;/li&gt;
&lt;li&gt;current processing stage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stages include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pagination
images
preparing
rendering
watermark
header
footer
metadata
serializing
complete
error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it possible to build UI such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rendering page 18 of 50...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of showing an indefinite spinner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headers
&lt;/h2&gt;

&lt;p&gt;Headers can contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plain text&lt;/li&gt;
&lt;li&gt;trusted HTML&lt;/li&gt;
&lt;li&gt;optional logos&lt;/li&gt;
&lt;li&gt;custom backgrounds and text colors&lt;/li&gt;
&lt;li&gt;configurable logo dimensions&lt;/li&gt;
&lt;li&gt;selected-page targeting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A header can appear on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all pages&lt;/li&gt;
&lt;li&gt;the first page only&lt;/li&gt;
&lt;li&gt;the last page only&lt;/li&gt;
&lt;li&gt;every page except the first&lt;/li&gt;
&lt;li&gt;explicit page numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Selective header targeting also uses page-aware spacing, so header space is reserved only where the header is actually rendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Footers
&lt;/h2&gt;

&lt;p&gt;Footers support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML mode&lt;/li&gt;
&lt;li&gt;text mode&lt;/li&gt;
&lt;li&gt;trusted HTML content&lt;/li&gt;
&lt;li&gt;optional logos&lt;/li&gt;
&lt;li&gt;configurable logo dimensions&lt;/li&gt;
&lt;li&gt;background and text colors&lt;/li&gt;
&lt;li&gt;page-number templates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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;Page {n} of {total}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Header and footer logos preserve their aspect ratio instead of being stretched into a fixed box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Footer performance improvements
&lt;/h3&gt;

&lt;p&gt;Static HTML footers are now rendered once and reused across pages instead of being rasterized repeatedly.&lt;/p&gt;

&lt;p&gt;In project benchmark fixtures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTML footer without logo
7.82s -&amp;gt; 1.43s

HTML footer with logo
11.81s -&amp;gt; 1.29s

Footer-heavy PDF size
~46.7 MB -&amp;gt; ~1.0 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These numbers are specific to the benchmark fixtures and should not be treated as universal performance guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeated image optimization
&lt;/h2&gt;

&lt;p&gt;Selectable-text rendering now caches repeated images within a single generation.&lt;/p&gt;

&lt;p&gt;If the same image is reused across many pages, the package can avoid repeatedly loading and encoding the same source.&lt;/p&gt;

&lt;p&gt;The cache is scoped to one PDF generation and is cleared afterward.&lt;/p&gt;

&lt;p&gt;This is especially useful for documents containing repeated logos, icons, product images, or other shared assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource cleanup
&lt;/h2&gt;

&lt;p&gt;A lot of work also went into cleaning temporary resources earlier.&lt;/p&gt;

&lt;p&gt;The generation pipeline now handles cleanup of things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;temporary cloned DOM nodes&lt;/li&gt;
&lt;li&gt;temporary canvases&lt;/li&gt;
&lt;li&gt;encoded image data&lt;/li&gt;
&lt;li&gt;per-generation image caches&lt;/li&gt;
&lt;li&gt;rendering state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Canvas backing stores are also released when possible after they are no longer needed.&lt;/p&gt;

&lt;p&gt;Repeated-generation and isolation tests were added to help detect regressions in cleanup behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser responsiveness
&lt;/h2&gt;

&lt;p&gt;Large PDF generation is naturally expensive, especially in Canvas mode.&lt;/p&gt;

&lt;p&gt;The pipeline now yields between selected expensive operations so the browser gets opportunities to process UI work while generation is running.&lt;/p&gt;

&lt;p&gt;Combined with cancellation and page-aware progress, this makes long-running generation easier to integrate into real applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watermarks
&lt;/h2&gt;

&lt;p&gt;Watermarks can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text or image&lt;/li&gt;
&lt;li&gt;foreground or background&lt;/li&gt;
&lt;li&gt;single or repeated&lt;/li&gt;
&lt;li&gt;rotated&lt;/li&gt;
&lt;li&gt;positioned&lt;/li&gt;
&lt;li&gt;configured with opacity&lt;/li&gt;
&lt;li&gt;targeted to selected PDF pages&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PDF security
&lt;/h2&gt;

&lt;p&gt;You can optionally generate password-protected PDFs with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user/open password&lt;/li&gt;
&lt;li&gt;owner password&lt;/li&gt;
&lt;li&gt;configurable permissions&lt;/li&gt;
&lt;li&gt;password construction from multiple password parts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supported permission controls include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print
modify
copy
annot-forms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Metadata and compression
&lt;/h2&gt;

&lt;p&gt;PDF metadata can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;author&lt;/li&gt;
&lt;li&gt;subject&lt;/li&gt;
&lt;li&gt;keywords&lt;/li&gt;
&lt;li&gt;creator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;jsPDF stream compression can also be enabled when needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clickable links
&lt;/h2&gt;

&lt;p&gt;PDF link annotations are also supported, allowing links in generated documents to remain interactive instead of becoming plain visual text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safer filenames
&lt;/h2&gt;

&lt;p&gt;Generated filenames are normalized before download.&lt;/p&gt;

&lt;p&gt;This includes handling invalid filename characters, whitespace, reserved Windows names, trailing dots or spaces, and overly long filenames.&lt;/p&gt;

&lt;p&gt;Unicode filenames are still preserved.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note about encrypted raster PDFs
&lt;/h2&gt;

&lt;p&gt;While profiling larger secured documents, I found that encrypted raster PDFs could retain significantly more browser memory than equivalent unencrypted PDFs.&lt;/p&gt;

&lt;p&gt;I reproduced the behavior directly with jsPDF outside the main &lt;code&gt;vue3-pdf-export&lt;/code&gt; rendering pipeline.&lt;/p&gt;

&lt;p&gt;That means a substantial part of this behavior appears to come from the underlying PDF library rather than from the Vue component itself.&lt;/p&gt;

&lt;p&gt;I opened an upstream jsPDF issue with the reproduction and benchmark details:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/parallax/jsPDF/issues/4017" rel="noopener noreferrer"&gt;https://github.com/parallax/jsPDF/issues/4017&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For large secured documents, selectable-text mode can sometimes be a useful alternative because text-based PDFs are generally much smaller than equivalent rasterized documents.&lt;/p&gt;

&lt;p&gt;This is still workload-dependent, so applications generating large encrypted PDFs should test their own representative content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and performance work
&lt;/h2&gt;

&lt;p&gt;The project now includes automated testing around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unit behavior&lt;/li&gt;
&lt;li&gt;Chromium&lt;/li&gt;
&lt;li&gt;Firefox&lt;/li&gt;
&lt;li&gt;WebKit&lt;/li&gt;
&lt;li&gt;large documents&lt;/li&gt;
&lt;li&gt;Canvas rendering&lt;/li&gt;
&lt;li&gt;selectable-text rendering&lt;/li&gt;
&lt;li&gt;cancellation and recovery&lt;/li&gt;
&lt;li&gt;image-heavy documents&lt;/li&gt;
&lt;li&gt;repeated images&lt;/li&gt;
&lt;li&gt;footer optimization&lt;/li&gt;
&lt;li&gt;browser cleanup&lt;/li&gt;
&lt;li&gt;security behavior&lt;/li&gt;
&lt;li&gt;memory measurements&lt;/li&gt;
&lt;li&gt;repeated generation&lt;/li&gt;
&lt;li&gt;stress testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current unit suite contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;19 test files
106 tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also dedicated benchmark and Playwright workflows for performance-heavy scenarios.&lt;/p&gt;

&lt;p&gt;For heavy PDF tests, I intentionally run browser work sequentially where appropriate so multiple workers do not distort memory measurements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built a Vue layer instead of replacing the underlying libraries
&lt;/h2&gt;

&lt;p&gt;The goal was never to pretend that &lt;code&gt;html2pdf.js&lt;/code&gt;, &lt;code&gt;html2canvas&lt;/code&gt;, or &lt;code&gt;jsPDF&lt;/code&gt; do not exist.&lt;/p&gt;

&lt;p&gt;Those libraries still provide important pieces of the Canvas rendering pipeline.&lt;/p&gt;

&lt;p&gt;The goal of &lt;code&gt;vue3-pdf-export&lt;/code&gt; is to provide a Vue-oriented layer around PDF generation so common production requirements do not need to be rebuilt for every application.&lt;/p&gt;

&lt;p&gt;At the same time, developers can still pass advanced html2pdf.js and jsPDF options when the higher-level API is not enough.&lt;/p&gt;

&lt;p&gt;The selectable-text renderer provides another option for cases where real PDF text matters more than exact Canvas-style browser rendering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current release
&lt;/h2&gt;

&lt;p&gt;The current release is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vue-pdf-export v1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project has grown significantly since the original release, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Canvas and selectable-text rendering modes&lt;/li&gt;
&lt;li&gt;incremental page-by-page Canvas generation&lt;/li&gt;
&lt;li&gt;large-document regression coverage&lt;/li&gt;
&lt;li&gt;cancellation and recovery&lt;/li&gt;
&lt;li&gt;current-page / total-page progress&lt;/li&gt;
&lt;li&gt;repeated-image caching&lt;/li&gt;
&lt;li&gt;footer rendering optimization&lt;/li&gt;
&lt;li&gt;earlier DOM and Canvas cleanup&lt;/li&gt;
&lt;li&gt;browser responsiveness improvements&lt;/li&gt;
&lt;li&gt;PDF security&lt;/li&gt;
&lt;li&gt;metadata and compression&lt;/li&gt;
&lt;li&gt;clickable links&lt;/li&gt;
&lt;li&gt;trusted HTML headers and footers&lt;/li&gt;
&lt;li&gt;logo support&lt;/li&gt;
&lt;li&gt;watermarks&lt;/li&gt;
&lt;li&gt;filename normalization&lt;/li&gt;
&lt;li&gt;expanded unit, browser, stress, and performance testing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qfvaw4w1z14k3m03c8t.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qfvaw4w1z14k3m03c8t.png" alt="vue-pdf-export Docs" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📄 &lt;strong&gt;Explore the documentation&lt;/strong&gt; : &lt;a href="https://vue-pdf-export-docs.vercel.app/" rel="noopener noreferrer"&gt;vue-pdf-export-docs.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌐 &lt;strong&gt;Live Playground&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Explore the features and generate PDFs directly in the browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vue-pdf-export-playground.vercel.app/" rel="noopener noreferrer"&gt;vue3-pdf-export-playground&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📦 &lt;strong&gt;npm Package&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install and use the package in your Vue 3 project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/vue-pdf-export" rel="noopener noreferrer"&gt;npmjs.com/package/vue-pdf-export&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The package is available for Vue 3 applications and continues to evolve around real-world PDF generation requirements.&lt;/p&gt;

&lt;p&gt;If you are building a Vue application that needs client-side PDF generation, I would love to hear about the kinds of documents you are generating and the edge cases you run into.&lt;/p&gt;

&lt;p&gt;If you find a bug, performance cases, or have an idea for an API improvement, feel free to let me know.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>npm</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Built Log Stripper: A VS Code Extension to Remove Debug Logs Across 23+ Languages</title>
      <dc:creator>Saurabh Choudhary</dc:creator>
      <pubDate>Wed, 03 Jun 2026 07:36:42 +0000</pubDate>
      <link>https://dev.to/saurabhzaiswal/built-log-stripper-a-vs-code-extension-to-remove-debug-logs-across-23-languages-g9h</link>
      <guid>https://dev.to/saurabhzaiswal/built-log-stripper-a-vs-code-extension-to-remove-debug-logs-across-23-languages-g9h</guid>
      <description>&lt;p&gt;Every developer has a version of this story.&lt;/p&gt;

&lt;p&gt;You're about to open a pull request. The code works. The tests pass. Everything looks good.&lt;/p&gt;

&lt;p&gt;Then you do one final scan and find this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HERE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;final:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then, before the PR:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Search → Delete. Search → Delete. Search → Delete.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I got tired of it. So I built a tool to automate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Log Stripper?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Log Stripper&lt;/strong&gt; is a VS Code extension that removes debug, log, and print statements from &lt;strong&gt;23+ programming languages&lt;/strong&gt; - with a preview so you always see what will be deleted before anything changes.&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%2Fcboyc43q1b3yqlllnv9i.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%2Fcboyc43q1b3yqlllnv9i.png" alt="Log Stripper Icon" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://marketplace.visualstudio.com/items?itemName=saurabhchoudhary.log-stripper" rel="noopener noreferrer"&gt;VS Code Marketplace&lt;/a&gt;&lt;br&gt;
🔗 &lt;a href="https://github.com/saurabhzaiswal/Log-Stripper-VS-Code" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Features
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🔍 Preview mode&lt;/strong&gt; - &lt;code&gt;Ctrl+Shift+D&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Shows every line that will be removed. You confirm. Only then does anything change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎨 Highlight mode&lt;/strong&gt; - &lt;code&gt;Ctrl+Shift+H&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Marks debug lines in red. No file modification. Review first, strip later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🌍  23+ languages&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JS, TS, Python, Java, Go, Rust, C#, Swift, Kotlin, Dart, Ruby, PHP, C, C++, Shell, Lua, Scala, Elixir, Haskell, R, Vue, Svelte, JSX/TSX&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🏢 Workspace cleanup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Strip debug statements from an entire project in one command.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why not just use grep/sed?
&lt;/h2&gt;

&lt;p&gt;You could. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No preview&lt;/li&gt;
&lt;li&gt;No multiline support&lt;/li&gt;
&lt;li&gt;No VS Code integration&lt;/li&gt;
&lt;li&gt;No safety rules&lt;/li&gt;
&lt;li&gt;Works differently on Windows vs Mac vs Linux
Log Stripper handles all of this inside VS Code with a consistent UX.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Interesting Technical Part: Multiline Removal
&lt;/h2&gt;

&lt;p&gt;Removing a single-line &lt;code&gt;console.log("x")&lt;/code&gt; is trivial. The interesting case is:&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;role:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To remove this correctly, you need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the opening &lt;code&gt;(&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Track paren depth across lines&lt;/li&gt;
&lt;li&gt;Handle parens inside strings (don't count those)&lt;/li&gt;
&lt;li&gt;Find the exact closing &lt;code&gt;)&lt;/code&gt; and optional &lt;code&gt;;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove the entire block without touching surrounding code
Here's the core of &lt;code&gt;skipParenBlock&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;skipParenBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&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;startLine&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openIdx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;startLine&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;indexOf&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startLine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ci&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;openIdx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;inStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ci&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ci&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inStr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&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="nx"&gt;ci&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;inStr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;inStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&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="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&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="nx"&gt;inStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ch&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&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="nx"&gt;bal&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt; &lt;span class="o"&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="nx"&gt;bal&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bal&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;ci&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ci&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="nx"&gt;ci&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;endLine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;endCol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ci&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="nx"&gt;ci&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;ci&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architecture Decision
&lt;/h2&gt;

&lt;p&gt;I separated the core logic from the VS Code layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── extension.ts   ← VS Code commands, UI, workspace
└── stripper.ts    ← Pure logic, zero VS Code deps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because &lt;code&gt;stripper.ts&lt;/code&gt; can be tested without launching a VS Code host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--test&lt;/span&gt; out-test/test/stripper.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;151 tests. Fast. No mocking needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety Rules
&lt;/h2&gt;

&lt;p&gt;The extension will &lt;strong&gt;never&lt;/strong&gt; remove:&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="c1"&gt;// console.log("commented") ← comment line, always kept&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nx"&gt;inline&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;always&lt;/span&gt; &lt;span class="nx"&gt;kept&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;doThing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nx"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kept&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only whole-statement debug calls that are the entire line (after indentation) are removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Languages Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JS/TS&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;console.log&lt;/code&gt;, &lt;code&gt;debugger&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;print()&lt;/code&gt;, &lt;code&gt;logging.debug()&lt;/code&gt;, &lt;code&gt;breakpoint()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;System.out.println&lt;/code&gt;, &lt;code&gt;logger.debug()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fmt.Println&lt;/code&gt;, &lt;code&gt;log.Fatal&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;println!&lt;/code&gt;, &lt;code&gt;dbg!&lt;/code&gt;, &lt;code&gt;eprintln!&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Console.WriteLine&lt;/code&gt;, &lt;code&gt;Debug.Write&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Swift&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;print()&lt;/code&gt;, &lt;code&gt;NSLog()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;puts&lt;/code&gt;, &lt;code&gt;binding.pry&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;var_dump()&lt;/code&gt;, &lt;code&gt;dd()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shell&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;echo&lt;/code&gt;, &lt;code&gt;printf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...13 more&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install from Marketplace&lt;/span&gt;
code &lt;span class="nt"&gt;--install-extension&lt;/span&gt; saurabhchoudhary.log-stripper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or search "Log Stripper" in the VS Code extensions panel.&lt;/p&gt;

&lt;p&gt;Open a file with debug statements → &lt;code&gt;Ctrl+Shift+D&lt;/code&gt; → see the preview → confirm.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Toggle Debug Highlights | Highlight/unhighlight debug lines&lt;/code&gt;&lt;/strong&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%2Fhgrpgwbxhy3us1132fx5.png" alt="Toggle Debug Highlights | Highlight/unhighlight debug lines" width="800" height="410"&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Preview &amp;amp; Strip Current File | Shows preview, then strips on confirm&lt;/code&gt;&lt;/strong&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%2Fvprhyo035tbopzpi6qp2.png" alt="Preview &amp;amp; Strip Current File | Shows preview, then strips on confirm" width="800" height="425"&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Command Palette | Strip Entire Workspace | Clean whole project&lt;/code&gt;&lt;/strong&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%2F5ulbtysszpopdgwx0qn5.png" alt="Command Palette | Strip Entire Workspace | Clean whole project" width="799" height="424"&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Right-click → menu | Strip Current File | Strip without preview&lt;/code&gt;&lt;/strong&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%2Fr3n9kak7vn3m9eo6bogp.png" alt="Right-click → menu | Strip Current File | Strip without preview" width="800" height="450"&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Editor toolbar icons (eye / eye-closed / trash)&lt;/code&gt;&lt;/strong&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%2Fkiai3594p78j7f12qpt9.png" alt="Editor toolbar icons (eye / eye-closed / trash)" width="800" height="425"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built this because I couldn't find a tool that did all of this properly. Turned out to be one of the most satisfying weekend projects I've done.&lt;/p&gt;

&lt;p&gt;If you try it, I'd love feedback in the comments or as a GitHub issue. 🙌&lt;/p&gt;

&lt;p&gt;&lt;em&gt;By Saurabh Choudhary - Software Engineer&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Every developer has a version of this story.&lt;/p&gt;

&lt;p&gt;You're about to open a pull request. The code works. The tests pass. You're ready. Then you do one last scroll through the file and see it:&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HERE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user data:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three debug logs you forgot to remove. You delete them, re-run the linter, and open the PR.&lt;/p&gt;

&lt;p&gt;Two weeks later, someone reports that production logs are noisy with debug output. A developer on your team had left a &lt;code&gt;logger.debug()&lt;/code&gt; in a hot path. Nobody caught it in review.&lt;/p&gt;

&lt;p&gt;This isn't a rare occurrence. It's a pattern that plays out in every team, on every codebase, in every programming language.&lt;/p&gt;

&lt;p&gt;I've experienced it across JavaScript, TypeScript, PHP, Go, Python, Vue.js, NestJS, Laravel, and Angular projects. Different languages, different ecosystems - same repetitive problem.&lt;/p&gt;

&lt;p&gt;Eventually I stopped accepting it as "just part of the workflow" and decided to build a proper solution.&lt;/p&gt;

&lt;p&gt;That solution is &lt;strong&gt;Log Stripper&lt;/strong&gt; - a VS Code extension that removes debug, log, and print statements from 23+ programming languages with a safety-first, preview-driven approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The problem has two dimensions that are easy to underestimate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: it's repetitive.&lt;/strong&gt; Before every commit, before every PR, before every release, developers go through the same manual process of searching for and deleting debug statements. On large codebases with dozens of files touched in a single feature branch, this becomes genuinely time-consuming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: it's error-prone.&lt;/strong&gt; Humans miss things. A &lt;code&gt;console.log&lt;/code&gt; inside a nested callback, a &lt;code&gt;print()&lt;/code&gt; inside a conditional branch, a &lt;code&gt;System.out.println&lt;/code&gt; buried in a Java service - these are easy to overlook when scanning files manually. And when they slip through, they end up in production.&lt;/p&gt;

&lt;p&gt;There's also a subtler issue: developers working under time pressure will rush the cleanup. The more pressure, the more likely something gets missed.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of task that should be automated. It is deterministic, repetitive, and rule-based. A computer should do it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Existing Workflows Were Frustrating
&lt;/h2&gt;

&lt;p&gt;Before building Log Stripper, I searched for existing solutions.&lt;/p&gt;

&lt;p&gt;I found a few extensions that handled &lt;code&gt;console.log&lt;/code&gt; removal for JavaScript. Some worked well for that specific case. But they had significant limitations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Language coverage was narrow.&lt;/strong&gt; Most extensions focused exclusively on JavaScript. If you wrote Python, Go, Java, or Rust, you were on your own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No preview mode.&lt;/strong&gt; Several extensions deleted statements immediately without showing you what would be removed. That's unsafe. A developer who accidentally removes a legitimate logger call in production code will stop trusting the tool immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No highlight mode.&lt;/strong&gt; Sometimes I want to &lt;em&gt;see&lt;/em&gt; the debug statements in a file without removing anything yet. I want to review them, understand them, decide manually. None of the tools I found offered non-destructive inspection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No workspace cleanup.&lt;/strong&gt; File-by-file cleanup doesn't scale. Before a major release, I want to scan an entire project and clean everything in one pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unreliable multiline handling.&lt;/strong&gt; A &lt;code&gt;console.log&lt;/code&gt; that spans multiple lines is common:&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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;Many tools would only remove the first line and leave the rest, creating syntax errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built Log Stripper
&lt;/h2&gt;

&lt;p&gt;The decision to build rather than adapt came down to one realization: the problem deserved a proper solution, not a workaround.&lt;/p&gt;

&lt;p&gt;I wanted an extension that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Worked across every major language I use day-to-day&lt;/li&gt;
&lt;li&gt;Showed a preview before making any changes&lt;/li&gt;
&lt;li&gt;Could highlight debug lines without modifying files&lt;/li&gt;
&lt;li&gt;Could clean an entire workspace safely&lt;/li&gt;
&lt;li&gt;Was smart enough to never break code it shouldn't touch&lt;/li&gt;
&lt;li&gt;Was backed by automated tests
None of those requirements are unreasonable. Together, they're the minimum bar for a tool I'd actually trust.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The weekend I started building it, I intended it to be a one-day project. It grew into something more substantial as I realized the edge cases, the safety requirements, and the value of doing it properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The most important architectural decision I made was separating the core logic from the VS Code integration layer.&lt;/p&gt;

&lt;p&gt;The repository has two source files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── extension.ts    ← VS Code commands, UI, workspace operations
└── stripper.ts     ← Core strip logic (zero VS Code dependencies)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stripper.ts&lt;/code&gt; has no awareness of VS Code. It takes a string of text and a language ID, and returns a new string with debug statements removed plus metadata about what was changed. That's it.&lt;/p&gt;

&lt;p&gt;This separation has two major benefits:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testability.&lt;/strong&gt; I can test the core logic without launching a VS Code Extension Development Host. The 151 automated tests run with a plain &lt;code&gt;node --test&lt;/code&gt; command. Fast, simple, reliable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintainability.&lt;/strong&gt; If VS Code changes its API, or if I want to port the logic to a CLI tool or a git pre-commit hook, the core logic doesn't need to change.&lt;/p&gt;

&lt;p&gt;The VS Code layer handles everything user-facing: commands, keybindings, settings, decorations, progress notifications, and workspace file scanning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Language Support
&lt;/h2&gt;

&lt;p&gt;Supporting 23 languages sounds ambitious. The implementation is actually straightforward once you have the right architecture.&lt;/p&gt;

&lt;p&gt;Each language is a key in the &lt;code&gt;LANGUAGE_PATTERNS&lt;/code&gt; object, and its value is an array of regular expressions:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LANGUAGE_PATTERNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;javascript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;console&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;log|debug|info|warn|error|trace|...&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;debugger&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*;&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&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;python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;print&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;logging&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;debug|info|warning|error|critical&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;breakpoint&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\(\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 21 more languages&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Language aliases handle cases where VS Code's language ID differs from the pattern group:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LANG_ALIASES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;svelte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;javascript&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;vue-html&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;vue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;typescriptreact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;typescript&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;When the extension receives a file, it looks up the language ID, finds the pattern list, and applies each pattern against every line. If no patterns are found for a language, the file is returned unchanged.&lt;/p&gt;

&lt;p&gt;Adding a new language is a matter of adding one entry to &lt;code&gt;LANGUAGE_PATTERNS&lt;/code&gt; and a set of test cases to &lt;code&gt;stripper.test.ts&lt;/code&gt;. The infrastructure handles the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Preview Mode
&lt;/h2&gt;

&lt;p&gt;The preview mode was the feature I was most deliberate about designing.&lt;/p&gt;

&lt;p&gt;When a developer triggers "Preview &amp;amp; Strip" with &lt;code&gt;Ctrl+Shift+D&lt;/code&gt;, the extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Runs the stripping logic internally (without modifying the file)&lt;/li&gt;
&lt;li&gt;Collects the list of lines that would be removed&lt;/li&gt;
&lt;li&gt;Shows a modal dialog listing those lines with their line numbers&lt;/li&gt;
&lt;li&gt;Waits for explicit confirmation before making any changes
This turns a potentially destructive operation into a fully transparent, opt-in action. The developer sees exactly what will happen and retains full control.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The modal includes the first 20 matching lines and appends "...and N more" if there are additional matches. This keeps the dialog readable on files with heavy debug coverage.&lt;/p&gt;

&lt;p&gt;If the developer cancels, nothing changes. Not a single character in the file is modified.&lt;/p&gt;




&lt;h2&gt;
  
  
  Highlight Mode
&lt;/h2&gt;

&lt;p&gt;Highlight mode solves a different need: &lt;em&gt;I want to see the debug statements but I'm not ready to remove them yet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When the developer presses &lt;code&gt;Ctrl+Shift+H&lt;/code&gt;, the extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds all lines matching debug patterns using &lt;code&gt;findDebugLineIndices()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Applies a VS Code text decoration that highlights those lines in red&lt;/li&gt;
&lt;li&gt;Adds an inline annotation "← debug" at the end of each line
The file is not modified. This is purely a visual overlay.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pressing &lt;code&gt;Ctrl+Shift+H&lt;/code&gt; again (or clicking the eye-closed icon in the toolbar) removes the highlights.&lt;/p&gt;

&lt;p&gt;The highlight controller also handles three lifecycle events automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you &lt;strong&gt;strip the file&lt;/strong&gt;, highlights are cleared (no red lines on lines that no longer exist)&lt;/li&gt;
&lt;li&gt;When you &lt;strong&gt;edit the file&lt;/strong&gt; and &lt;code&gt;refreshHighlightsOnEdit&lt;/code&gt; is enabled, highlights recompute after a 120ms debounce&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  - When you &lt;strong&gt;switch tabs&lt;/strong&gt; and &lt;code&gt;clearHighlightsOnTabChange&lt;/code&gt; is enabled, highlights are cleared automatically
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Workspace Cleanup
&lt;/h2&gt;

&lt;p&gt;The workspace strip command scans every file in the project matching the supported language extensions and strips debug statements from each one.&lt;/p&gt;

&lt;p&gt;The implementation uses VS Code's &lt;code&gt;workspace.findFiles()&lt;/code&gt; API with exclusion patterns to skip &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;dist&lt;/code&gt;, &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;vendor&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, and &lt;code&gt;out&lt;/code&gt; by default. All exclusion patterns are configurable in settings.&lt;/p&gt;

&lt;p&gt;The operation runs with a progress notification showing the current file being processed and a cancellation option. After completion, a summary shows how many statements were removed across how many files.&lt;/p&gt;

&lt;p&gt;Because this modifies files on disk, the documentation explicitly recommends using version control. A git diff after the workspace strip is always informative.&lt;/p&gt;




&lt;h2&gt;
  
  
  Safety Design
&lt;/h2&gt;

&lt;p&gt;Safety was a first-class concern throughout the design.&lt;/p&gt;

&lt;p&gt;The extension enforces several rules to avoid breaking code it shouldn't touch:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 1: Whole-line only.&lt;/strong&gt; A debug call is only removed if it is the entire statement on the line (after indentation). This prevents removal of:&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;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// kept - inline with return&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// kept - chained&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule 2: Comments are never touched.&lt;/strong&gt; Any line where the first non-whitespace characters form a comment prefix (&lt;code&gt;//&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;--&lt;/code&gt;, &lt;code&gt;/*&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;) is skipped entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 3: Multiline paren balancing.&lt;/strong&gt; When a debug call spans multiple lines, the extension tracks open and close parentheses (accounting for parens inside strings) to identify the exact end of the statement. The closing &lt;code&gt;)&lt;/code&gt; and optional &lt;code&gt;;&lt;/code&gt; are consumed, and any remaining code on that final line is preserved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 4: No modification on cancel.&lt;/strong&gt; Preview mode never touches the file unless the user explicitly confirms. The stripping logic runs on an in-memory copy of the text, and the result is only written back if the user says yes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing Strategy
&lt;/h2&gt;

&lt;p&gt;I wanted the extension to be trustworthy. That required a proper test suite.&lt;/p&gt;

&lt;p&gt;The tests live in &lt;code&gt;test/stripper.test.ts&lt;/code&gt; and use Node's built-in test runner - no Jest, no Mocha, no additional dependencies. The test command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tsc &lt;span class="nt"&gt;-p&lt;/span&gt; tsconfig.test.json &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; node &lt;span class="nt"&gt;--test&lt;/span&gt; out-test/test/stripper.test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 151 test cases cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every language in &lt;code&gt;LANGUAGE_PATTERNS&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Simple single-line removal&lt;/li&gt;
&lt;li&gt;Multiline call removal with paren balancing&lt;/li&gt;
&lt;li&gt;Comment preservation&lt;/li&gt;
&lt;li&gt;Inline code preservation&lt;/li&gt;
&lt;li&gt;Real-world code samples (NestJS controllers, Python classes, Go functions)&lt;/li&gt;
&lt;li&gt;Language aliases (Vue, Svelte, JSX, TSX)&lt;/li&gt;
&lt;li&gt;Unknown languages (should be a no-op)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;findDebugLineIndices()&lt;/code&gt; function (used by highlight mode)
Each test specifies the input code, the expected output, and the expected removal count. Any test that would cause the extension to modify code it shouldn't - or fail to modify code it should - is a failing test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing tests before polishing features was the right decision. It caught several edge cases during development and gave me confidence when refactoring the paren-balancing logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Publishing to the VS Code Marketplace
&lt;/h2&gt;

&lt;p&gt;The Marketplace publishing process is simpler than I expected.&lt;/p&gt;

&lt;p&gt;The key steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a publisher account at &lt;code&gt;marketplace.visualstudio.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generate a Personal Access Token from Azure DevOps with Marketplace → Manage scope&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;@vscode/vsce&lt;/code&gt; globally: &lt;code&gt;npm install -g @vscode/vsce&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;vsce package&lt;/code&gt; to create the &lt;code&gt;.vsix&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;vsce publish --pat TOKEN&lt;/code&gt; or upload the &lt;code&gt;.vsix&lt;/code&gt; manually via the publisher portal
The &lt;code&gt;.vscodeignore&lt;/code&gt; file controls what goes into the package. I excluded &lt;code&gt;src/&lt;/code&gt;, &lt;code&gt;test/&lt;/code&gt;, and &lt;code&gt;node_modules/&lt;/code&gt; - only the compiled &lt;code&gt;out/&lt;/code&gt; directory ships. This keeps the extension lightweight.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing I'd emphasize: set the &lt;code&gt;repository.url&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt; before publishing. It shows up prominently on the Marketplace listing and signals to users that the extension is open source and maintainable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Separate your core logic from your integration layer.&lt;/strong&gt; This applies far beyond VS Code extensions. Any time you can isolate the testable business logic from the framework or platform code, do it. It makes everything easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Safety features build trust.&lt;/strong&gt; The preview mode and the "never remove inline code" rule weren't technically necessary for the extension to work. But they're what make a developer comfortable trusting the tool with real production code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test the edge cases first.&lt;/strong&gt; Multiline calls, strings containing parens, empty files, unsupported languages - these were the cases most likely to cause silent failures. Writing tests for them before the happy path forced me to build robust logic from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ship before it's perfect.&lt;/strong&gt; The first version didn't have highlight mode. The workspace strip was added later. Shipping v1 with the core feature - preview and strip - got real feedback faster than spending another two weeks on features users might not need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small tools have real value.&lt;/strong&gt; This isn't a SaaS platform. It's not an AI product. It's a focused tool that does one thing well. Developer tools like this get used quietly but consistently. That's enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;p&gt;Several improvements are on the roadmap:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom pattern rules.&lt;/strong&gt; Allow developers to define their own patterns to remove. For teams using custom logging frameworks, this would make Log Stripper work with any codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-commit hook integration.&lt;/strong&gt; A script or CLI wrapper that can run as a git pre-commit hook, catching debug statements before they ever reach a commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team-wide configuration.&lt;/strong&gt; A &lt;code&gt;.logstripper.json&lt;/code&gt; configuration file that defines excluded patterns per project, shareable across a team through version control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignore comments.&lt;/strong&gt; A &lt;code&gt;// log-stripper-ignore&lt;/code&gt; annotation that tells the extension to skip a specific line or block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More language coverage.&lt;/strong&gt; Zig, Nim, OCaml, and Erlang are candidates for future additions.&lt;/p&gt;




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

&lt;p&gt;Log Stripper is a small tool. It solves one problem, and it solves it well.&lt;/p&gt;

&lt;p&gt;But the process of building it taught me something more valuable than any individual feature: the instinct to look at a repetitive problem and ask "why am I doing this manually?" is one of the most productive instincts an engineer can develop.&lt;/p&gt;

&lt;p&gt;We accept small frictions as part of the job. We work around limitations instead of fixing them. We solve the same problem repeatedly instead of automating it once.&lt;/p&gt;

&lt;p&gt;Log Stripper exists because I stopped accepting one of those frictions.&lt;/p&gt;

&lt;p&gt;If you're reading this and you have something similar - a small repetitive task, an annoying workflow step, a gap in your tooling - build the solution. The process will teach you more than you expect, and the tool will serve you for years.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try Log Stripper:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VS Code Marketplace: &lt;a href="https://marketplace.visualstudio.com/items?itemName=saurabhchoudhary.log-stripper" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=saurabhchoudhary.log-stripper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/saurabhzaiswal/Log-Stripper-VS-Code" rel="noopener noreferrer"&gt;https://github.com/saurabhzaiswal/Log-Stripper-VS-Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm actively improving Log Stripper, so real-world feedback is incredibly valuable.&lt;/p&gt;

&lt;p&gt;If Log Stripper helps keep a few forgotten debug statements out of production, then it has already paid for itself.&lt;/p&gt;




</description>
      <category>vscode</category>
      <category>opensource</category>
      <category>typescript</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
