<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Accreditly</title>
    <description>The latest articles on Forem by Accreditly (@accreditly).</description>
    <link>https://forem.com/accreditly</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1045142%2Fa534ea30-7b1e-4199-b7e1-87688d0761db.png</url>
      <title>Forem: Accreditly</title>
      <link>https://forem.com/accreditly</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/accreditly"/>
    <language>en</language>
    <item>
      <title>Chart.js Server-Side Rendering as Images (Python Guide)</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Thu, 07 May 2026 10:47:00 +0000</pubDate>
      <link>https://forem.com/accreditly/chartjs-server-side-rendering-as-images-python-guide-5edn</link>
      <guid>https://forem.com/accreditly/chartjs-server-side-rendering-as-images-python-guide-5edn</guid>
      <description>&lt;p&gt;I needed weekly performance reports emailed to clients. Each report had four charts: revenue trend, conversion rate, top products, traffic sources. The app was Python. The charts on the live dashboard were Chart.js because that's what the frontend team had standardised on, and they looked good.&lt;/p&gt;

&lt;p&gt;First attempt was matplotlib. It produced charts. They looked like matplotlib charts, which is to say fine for a scientific paper and terrible for a client email that's supposed to match the product's visual language. The brand colours were approximate. The fonts were wrong. The legend looked like it was from 2008. Clients politely asked why the emailed reports didn't match the dashboard.&lt;/p&gt;

&lt;p&gt;Second attempt was &lt;code&gt;chartjs-node-canvas&lt;/code&gt;. This works by running Chart.js against a Node canvas polyfill. Close to the real thing, but not identical. Gradients rendered differently. Some plugins didn't work at all. And now I was running a Node service from Python for one specific job, which felt wrong.&lt;/p&gt;

&lt;p&gt;What actually solved it was Chart.js server-side rendering, where "server-side" means a real browser on someone else's server. I generate the HTML for the chart, including the Chart.js script tag and the config, post it to a rendering API, and get back a PNG that matches what the dashboard shows exactly, because it's the same Chart.js code running in the same browser engine. This article is how I set that up from Python, and the small details that make the difference between a chart that looks right and one that doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the obvious Python chart options fall short
&lt;/h2&gt;

&lt;p&gt;Matplotlib and Plotly both generate images natively from Python. They're fine tools. The problem is that if your product uses Chart.js (or D3, or Recharts, or ApexCharts) on the frontend, and you want emailed or PDF-embedded charts to match, you're re-implementing the same visual twice in two different libraries. Colours drift. Fonts drift. Tooltip styling is irrelevant for a static image but the overall look and feel stops matching.&lt;/p&gt;

&lt;p&gt;Plotly can export as a PNG via its &lt;code&gt;kaleido&lt;/code&gt; backend, which is better, but you're still maintaining two chart implementations. Any change to the dashboard's chart styling means a matching change to the Python-side Plotly code, and keeping those in sync is a perennial source of bugs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;chartjs-node-canvas&lt;/code&gt; gets you one library, but at the cost of running Node from Python and accepting that canvas-polyfill rendering isn't pixel-perfect against real browser canvas. Gradients, shadows, and certain plugin behaviours differ. For anything that'll sit next to the "real" chart in a client's inbox, close-but-not-quite is worse than obviously-different.&lt;/p&gt;

&lt;p&gt;The approach that actually gives you matching output is to render the chart in a real Chromium instance. Chart.js draws to a canvas, you screenshot the canvas, done. Running that browser yourself brings all the Puppeteer-on-a-server problems: binary size, version drift, memory. Pointing at an API that does it for you removes the problem entirely.&lt;/p&gt;

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

&lt;p&gt;Here's a working Python function that takes a chart config and returns PNG bytes. The chart config is a plain dict that matches Chart.js's config shape, which means you can either write it by hand in Python or fetch it from wherever your frontend gets its config from and reuse it directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
import json
import requests

HTML2IMG_URL = "https://api.html2img.com/v1/render"
API_KEY = os.environ["HTML2IMG_API_KEY"]

def render_chart(config: dict, width: int = 800, height: int = 450) -&amp;gt; bytes:
    """Render a Chart.js config as PNG bytes."""
    html = f"""
    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
      &amp;lt;meta charset="utf-8"&amp;gt;
      &amp;lt;style&amp;gt;
        html, body {{ margin: 0; padding: 0; background: white; }}
        body {{
          width: {width}px;
          height: {height}px;
          font-family: -apple-system, "Segoe UI", system-ui, sans-serif;
          padding: 24px;
        }}
        #chart {{ width: 100%; height: 100%; }}
      &amp;lt;/style&amp;gt;
      &amp;lt;script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
      &amp;lt;canvas id="chart"&amp;gt;&amp;lt;/canvas&amp;gt;
      &amp;lt;script&amp;gt;
        const ctx = document.getElementById('chart').getContext('2d');
        const config = {json.dumps(config)};
        config.options = config.options || {{}};
        config.options.animation = false;
        config.options.responsive = false;
        config.options.maintainAspectRatio = false;
        const chart = new Chart(ctx, config);
        window.__chartReady = true;
      &amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
    """

    response = requests.post(
        HTML2IMG_URL,
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={
            "html": html,
            "viewport_width": width + 48,
            "viewport_height": height + 48,
            "device_scale_factor": 2,
            "wait_for_selector": "#chart",
            "ms_delay": 400,
        },
        timeout=30,
    )
    response.raise_for_status()
    return response.content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things in that HTML are worth calling out because they're the difference between a chart that renders and one that doesn't.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;animation = false&lt;/code&gt; disables Chart.js's entrance animation. The screenshot fires as soon as the chart is ready, and you don't want to capture it mid-fade-in. &lt;code&gt;responsive = false&lt;/code&gt; and &lt;code&gt;maintainAspectRatio = false&lt;/code&gt; force the chart to use the exact canvas size you set, rather than trying to resize relative to its container. &lt;code&gt;ms_delay: 400&lt;/code&gt; gives Chart.js a moment to finish drawing after the canvas appears in the DOM. You could be more precise by having the page set a sentinel element when the chart is drawn and waiting for that selector, but a small delay is usually sufficient.&lt;/p&gt;

&lt;p&gt;Now using it for a revenue trend chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;revenue_config = {
    "type": "line",
    "data": {
        "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug"],
        "datasets": [{
            "label": "Revenue",
            "data": [42000, 48500, 51200, 55800, 61300, 68900, 72400, 79200],
            "borderColor": "#6366f1",
            "backgroundColor": "rgba(99, 102, 241, 0.12)",
            "fill": True,
            "tension": 0.35,
            "pointBackgroundColor": "#6366f1",
            "pointRadius": 4,
            "borderWidth": 3,
        }],
    },
    "options": {
        "plugins": {
            "legend": {"display": False},
            "title": {
                "display": True,
                "text": "Monthly Revenue",
                "font": {"size": 18, "weight": "700"},
                "color": "#0f172a",
                "align": "start",
                "padding": {"bottom": 20},
            },
        },
        "scales": {
            "y": {
                "beginAtZero": False,
                "grid": {"color": "#f1f5f9"},
                "ticks": {
                    "color": "#64748b",
                    "callback": "function(v) { return '£' + (v/1000) + 'k'; }",
                },
            },
            "x": {
                "grid": {"display": False},
                "ticks": {"color": "#64748b"},
            },
        },
    },
}

png_bytes = render_chart(revenue_config, width=800, height=450)

with open("revenue.png", "wb") as f:
    f.write(png_bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One wrinkle: the &lt;code&gt;callback&lt;/code&gt; function for the y-axis ticks is expressed as a string. JSON can't hold JavaScript functions, so if you need them, you serialise the config, then have a post-processing step that replaces string function placeholders with real function literals. Here's a tiny helper that does that:&lt;br&gt;
&lt;/p&gt;

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

def build_html_config(config: dict) -&amp;gt; str:
    """Serialise a config dict, unwrapping string-encoded JS functions."""
    json_str = json.dumps(config)
    # Match "function(...) { ... }" inside JSON strings and unquote them
    return re.sub(
        r'"(function\s*\([^)]*\)\s*\{[^}]*\})"',
        lambda m: m.group(1).replace('\\"', '"'),
        json_str,
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the HTML template, replace &lt;code&gt;JSON.stringify(config)&lt;/code&gt; with the output of &lt;code&gt;build_html_config(config)&lt;/code&gt;. For simple charts that don't need formatter callbacks, you can skip this and pass config straight through.&lt;/p&gt;

&lt;p&gt;Below is what we're rendering:&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%2Fcaynijujt5jvq7z768cp.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%2Fcaynijujt5jvq7z768cp.png" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas worth knowing
&lt;/h2&gt;

&lt;p&gt;Chart.js versions matter. The config shape changed significantly between v2 and v3, and again in a smaller way for v4. Pin the CDN URL to a specific version so your server-side renders don't suddenly break when the chart library publishes a release. The example above pins to 4.4.0.&lt;/p&gt;

&lt;p&gt;Canvas text rendering is affected by fonts being available at the time of draw. If your chart uses a custom font via &lt;code&gt;font.family&lt;/code&gt;, that font needs to either be a system font, be loaded before Chart.js runs, or you accept a fallback. The cleanest pattern is to include &lt;code&gt;@font-face&lt;/code&gt; in the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block and reference the same font both in CSS and in the Chart.js config. Then bump &lt;code&gt;ms_delay&lt;/code&gt; to 500-700ms to be safe.&lt;/p&gt;

&lt;p&gt;Plugins that need DOM interaction, like the &lt;code&gt;chartjs-plugin-datalabels&lt;/code&gt; click handlers or zoom plugins, are irrelevant for a static image and should be omitted from the server-side config even if your frontend uses them. Anything that only affects hover or click state is wasted computation.&lt;/p&gt;

&lt;p&gt;For reports with multiple charts, render them in parallel. The API calls are independent, so &lt;code&gt;concurrent.futures.ThreadPoolExecutor&lt;/code&gt; with a handful of workers cuts report generation time substantially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from concurrent.futures import ThreadPoolExecutor

configs = [revenue_config, conversion_config, products_config, traffic_config]

with ThreadPoolExecutor(max_workers=4) as pool:
    images = list(pool.map(render_chart, configs))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For truly bulk runs (hundreds of charts per report, or reports generated on a schedule for thousands of accounts), switch to the webhook pattern. You post each render with a &lt;code&gt;webhook_url&lt;/code&gt;, the API calls your endpoint with the finished PNG, and you don't hold threads open waiting. For a weekly-report system, that's usually the right shape.&lt;/p&gt;

&lt;p&gt;One last thing: don't inline base64 PNGs into emails if you can avoid it. Host the PNG somewhere (S3, a signed CDN URL, your own storage) and reference it in the email HTML. Email clients handle external images more reliably than inline attachments, and the email itself stays small.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://html2img.com/docs/usage/python" rel="noopener noreferrer"&gt;Python usage guide&lt;/a&gt; covers the requests setup and async alternatives. For more chart examples beyond Chart.js, including rendering D3 and custom canvas visualisations, the &lt;a href="https://html2img.com/docs/examples/chart-screenshot" rel="noopener noreferrer"&gt;chart screenshot example&lt;/a&gt; has a few variants. And if you're building this into a larger reporting pipeline, the &lt;a href="https://html2img.com/docs/parameters/webhook-url" rel="noopener noreferrer"&gt;webhook documentation&lt;/a&gt; walks through the async flow.&lt;/p&gt;

&lt;p&gt;The trick is just to let the real library do the drawing. Your dashboard's Chart.js code is already the canonical source of truth for what a chart looks like in your product. Reusing it server-side means you stop maintaining a second chart implementation, and your emails finally match your app.&lt;/p&gt;

</description>
      <category>chartjs</category>
      <category>python</category>
      <category>ssr</category>
      <category>images</category>
    </item>
    <item>
      <title>HTML Invoice to Image in Laravel</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Tue, 05 May 2026 09:11:00 +0000</pubDate>
      <link>https://forem.com/accreditly/html-invoice-to-image-in-laravel-40li</link>
      <guid>https://forem.com/accreditly/html-invoice-to-image-in-laravel-40li</guid>
      <description>&lt;p&gt;I've written invoice PDFs with DomPDF. I've written them with wkhtmltopdf via Snappy. Both work. Neither produces output I'd want a customer to see if I cared about how the thing looked. DomPDF's CSS support is stuck somewhere around 2012, and Snappy hands you a wkhtmltopdf binary that renders fonts like it's still running on Windows XP.&lt;/p&gt;

&lt;p&gt;The specific thing that pushed me off both of them was a client who wanted their invoices to match their brand guide. Inter font, a specific hex for the accent colour, rounded corners on the total row, a subtle shadow under the line items, and a small logo top-left. DomPDF rendered the Inter font as Times New Roman. Snappy got the font right but broke the border-radius. Neither supported CSS grid, so the two-column header with billing and shipping addresses side-by-side needed a float hack that I didn't want to write in 2026.&lt;/p&gt;

&lt;p&gt;What I actually wanted was to render a Laravel Blade template the way Chrome renders it, and get a PNG I could email, embed in a dashboard, or drop into a PDF later. This article is how I got there without putting a headless browser on my own server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the usual Laravel invoice libraries fall short
&lt;/h2&gt;

&lt;p&gt;The state of PHP PDF libraries is, to put it politely, historical. DomPDF is actively maintained but its rendering engine is a subset of CSS 2.1 with partial CSS 3 support, which means no flexbox, no grid, unreliable webfonts, and shadow/filter properties that either ignore you or crash the renderer. It works for "here is a table of numbers" output. It doesn't work when your design team has opinions.&lt;/p&gt;

&lt;p&gt;Snappy (via wkhtmltopdf) rendered more faithfully for a while, but wkhtmltopdf itself was archived in 2023. It still runs. It's not getting updates. And even at its peak, its font rendering was noticeably different from Chrome, which matters because your designer previewed the template in Chrome.&lt;/p&gt;

&lt;p&gt;The modern-feeling option is to run Puppeteer or Playwright from PHP via an exec call, or a service like Browsershot. This works, but now you're maintaining a Node.js install alongside your Laravel app, a Chromium binary, and you're exec'ing into it from PHP which is a category of bug I don't love debugging. Browsershot in particular is great software, but it's infrastructure you have to install, update, and keep working on every environment including your CI and your production containers.&lt;/p&gt;

&lt;p&gt;The approach that got me out of this was to stop trying to render the invoice on my own infrastructure. Let the Blade template render to HTML the way it normally would. Post that HTML to an API. Get a PNG back. The invoice design lives entirely in Blade and CSS, same as any other Laravel view, and nothing about my server has to change.&lt;/p&gt;

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

&lt;p&gt;I'll walk through a working implementation for a billing system that generates a PNG invoice when an order is marked as paid. The invoice has a header with company details, a line item table, a totals block, and a footer with payment terms. Everything you'd expect.&lt;/p&gt;

&lt;p&gt;Start with the Blade template at &lt;code&gt;resources/views/invoices/image.blade.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8"&amp;gt;
    &amp;lt;style&amp;gt;
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;800&amp;amp;display=swap');
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body {
            width: 800px;
            font-family: 'Inter', system-ui, sans-serif;
            padding: 64px;
            color: #0f172a;
            background: white;
        }
        header {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 32px;
            padding-bottom: 32px;
            border-bottom: 2px solid #0f172a;
        }
        .brand h1 { font-size: 28px; font-weight: 800; margin-bottom: 4px; }
        .brand .addr { font-size: 13px; color: #64748b; line-height: 1.6; }
        .meta { text-align: right; }
        .meta .label { font-size: 12px; text-transform: uppercase; letter-spacing: 2px; color: #94a3b8; }
        .meta .invoice-no { font-size: 32px; font-weight: 800; margin: 6px 0 16px; }
        .meta .dates { font-size: 13px; color: #475569; line-height: 1.8; }
        .bill-to { margin: 40px 0 24px; }
        .bill-to .label { font-size: 12px; text-transform: uppercase; letter-spacing: 2px; color: #94a3b8; margin-bottom: 8px; }
        .bill-to .name { font-size: 16px; font-weight: 600; }
        .bill-to .addr { font-size: 13px; color: #475569; line-height: 1.6; margin-top: 4px; }
        table { width: 100%; border-collapse: collapse; margin-top: 16px; }
        th { text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: 1.5px; color: #94a3b8; padding: 12px 0; border-bottom: 1px solid #e2e8f0; }
        th.right { text-align: right; }
        td { padding: 16px 0; border-bottom: 1px solid #f1f5f9; font-size: 14px; }
        td.right { text-align: right; }
        .totals { margin-top: 24px; display: flex; justify-content: flex-end; }
        .totals-inner { width: 300px; }
        .totals-row { display: flex; justify-content: space-between; padding: 8px 0; font-size: 14px; color: #475569; }
        .totals-row.total {
            margin-top: 12px; padding: 16px 20px;
            background: #0f172a; color: white;
            border-radius: 10px;
            font-size: 18px; font-weight: 800;
        }
        footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid #e2e8f0; font-size: 12px; color: #64748b; line-height: 1.6; }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;header&amp;gt;
        &amp;lt;div class="brand"&amp;gt;
            &amp;lt;h1&amp;gt;{{ $business['name'] }}&amp;lt;/h1&amp;gt;
            &amp;lt;div class="addr"&amp;gt;
                {{ $business['address'] }}&amp;lt;br&amp;gt;
                {{ $business['email'] }} - {{ $business['phone'] }}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="meta"&amp;gt;
            &amp;lt;div class="label"&amp;gt;Invoice&amp;lt;/div&amp;gt;
            &amp;lt;div class="invoice-no"&amp;gt;#{{ $invoice['number'] }}&amp;lt;/div&amp;gt;
            &amp;lt;div class="dates"&amp;gt;
                Issued {{ $invoice['issued_at'] }}&amp;lt;br&amp;gt;
                Due {{ $invoice['due_at'] }}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/header&amp;gt;

    &amp;lt;div class="bill-to"&amp;gt;
        &amp;lt;div class="label"&amp;gt;Bill to&amp;lt;/div&amp;gt;
        &amp;lt;div class="name"&amp;gt;{{ $customer['name'] }}&amp;lt;/div&amp;gt;
        &amp;lt;div class="addr"&amp;gt;{{ $customer['address'] }}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;table&amp;gt;
        &amp;lt;thead&amp;gt;
            &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;Description&amp;lt;/th&amp;gt;
                &amp;lt;th class="right"&amp;gt;Qty&amp;lt;/th&amp;gt;
                &amp;lt;th class="right"&amp;gt;Unit&amp;lt;/th&amp;gt;
                &amp;lt;th class="right"&amp;gt;Amount&amp;lt;/th&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
            @foreach ($items as $item)
                &amp;lt;tr&amp;gt;
                    &amp;lt;td&amp;gt;{{ $item['description'] }}&amp;lt;/td&amp;gt;
                    &amp;lt;td class="right"&amp;gt;{{ $item['qty'] }}&amp;lt;/td&amp;gt;
                    &amp;lt;td class="right"&amp;gt;£{{ number_format($item['unit'], 2) }}&amp;lt;/td&amp;gt;
                    &amp;lt;td class="right"&amp;gt;£{{ number_format($item['qty'] * $item['unit'], 2) }}&amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
            @endforeach
        &amp;lt;/tbody&amp;gt;
    &amp;lt;/table&amp;gt;

    &amp;lt;div class="totals"&amp;gt;
        &amp;lt;div class="totals-inner"&amp;gt;
            &amp;lt;div class="totals-row"&amp;gt;&amp;lt;span&amp;gt;Subtotal&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;£{{ number_format($totals['subtotal'], 2) }}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class="totals-row"&amp;gt;&amp;lt;span&amp;gt;VAT ({{ $totals['vat_rate'] }}%)&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;£{{ number_format($totals['vat'], 2) }}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class="totals-row total"&amp;gt;&amp;lt;span&amp;gt;Total&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;£{{ number_format($totals['total'], 2) }}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;footer&amp;gt;
        Payment due within 14 days. Bank transfer details on request.&amp;lt;br&amp;gt;
        {{ $business['name'] }} - Company no. {{ $business['company_no'] }}
    &amp;lt;/footer&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a service class that takes a domain Invoice, renders the Blade template, posts it to the API, and returns the PNG bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Services;

use App\Models\Invoice;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\View;
use RuntimeException;

class InvoiceImageRenderer
{
    public function __construct(
        private readonly string $apiKey,
    ) {}

    public function render(Invoice $invoice): string
    {
        $html = View::make('invoices.image', [
            'business' =&amp;gt; config('invoicing.business'),
            'customer' =&amp;gt; [
                'name' =&amp;gt; $invoice-&amp;gt;customer-&amp;gt;name,
                'address' =&amp;gt; $invoice-&amp;gt;customer-&amp;gt;formatted_address,
            ],
            'invoice' =&amp;gt; [
                'number' =&amp;gt; $invoice-&amp;gt;number,
                'issued_at' =&amp;gt; $invoice-&amp;gt;issued_at-&amp;gt;format('j F Y'),
                'due_at' =&amp;gt; $invoice-&amp;gt;due_at-&amp;gt;format('j F Y'),
            ],
            'items' =&amp;gt; $invoice-&amp;gt;items-&amp;gt;map(fn ($i) =&amp;gt; [
                'description' =&amp;gt; $i-&amp;gt;description,
                'qty' =&amp;gt; $i-&amp;gt;quantity,
                'unit' =&amp;gt; $i-&amp;gt;unit_price,
            ])-&amp;gt;all(),
            'totals' =&amp;gt; [
                'subtotal' =&amp;gt; $invoice-&amp;gt;subtotal,
                'vat_rate' =&amp;gt; $invoice-&amp;gt;vat_rate,
                'vat' =&amp;gt; $invoice-&amp;gt;vat_amount,
                'total' =&amp;gt; $invoice-&amp;gt;total,
            ],
        ])-&amp;gt;render();

        $response = Http::withToken($this-&amp;gt;apiKey)
            -&amp;gt;timeout(30)
            -&amp;gt;post('https://api.html2img.com/v1/render', [
                'html' =&amp;gt; $html,
                'viewport_width' =&amp;gt; 800,
                'viewport_height' =&amp;gt; 1100,
                'device_scale_factor' =&amp;gt; 2,
                'wait_for_selector' =&amp;gt; '.totals-row.total',
                'full_page' =&amp;gt; true,
            ]);

        if ($response-&amp;gt;failed()) {
            throw new RuntimeException(
                "Invoice render failed: {$response-&amp;gt;status()} {$response-&amp;gt;body()}"
            );
        }

        return $response-&amp;gt;body();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bind it in a service provider or resolve it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$renderer = new InvoiceImageRenderer(config('services.html2img.key'));
$png = $renderer-&amp;gt;render($invoice);

Storage::disk('s3')-&amp;gt;put("invoices/{$invoice-&amp;gt;number}.png", $png);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details in that render call worth pointing out. &lt;code&gt;full_page: true&lt;/code&gt; is what makes this work for invoices specifically, because an invoice's height depends on how many line items it has, and you don't want to clip or stretch. The viewport width stays fixed at 800 (a comfortable reading width for a document), and the height becomes whatever the content needs. &lt;code&gt;wait_for_selector: '.totals-row.total'&lt;/code&gt; ensures the render happens after the full content tree has mounted, which matters if the Google Font hasn't finished loading yet. &lt;code&gt;device_scale_factor: 2&lt;/code&gt; gives you a 1600-pixel-wide PNG that looks crisp when viewed on retina screens or printed.&lt;/p&gt;

&lt;p&gt;Blade does the HTML escaping for you when you use &lt;code&gt;{{ $var }}&lt;/code&gt;, so user-supplied data like customer names and addresses can't break the layout or inject markup. If you're pulling item descriptions from a rich-text source, use &lt;code&gt;{!! $var !!}&lt;/code&gt; deliberately and only after you've sanitised it server-side.&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%2Fq95fbgmv8m2ameppy3bt.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%2Fq95fbgmv8m2ameppy3bt.png" width="800" height="1100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas worth knowing
&lt;/h2&gt;

&lt;p&gt;Fonts are the biggest source of surprise on first run. The &lt;code&gt;@import&lt;/code&gt; inside the &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; block works, but adds a network round-trip inside the render. For a production pipeline that's generating thousands of invoices, either host the font file yourself and reference it via &lt;code&gt;@font-face&lt;/code&gt; with a publicly reachable URL, or base64-encode the woff2 and inline it. The &lt;code&gt;wait_for_selector&lt;/code&gt; option helps, but if your selector is visible before the font has swapped in, you still get a fallback for a brief moment. Adding a small &lt;code&gt;ms_delay&lt;/code&gt; of 300-500ms on top of the selector wait is a reasonable belt-and-braces.&lt;/p&gt;

&lt;p&gt;Numbers need locale-aware formatting. &lt;code&gt;number_format()&lt;/code&gt; defaults to US conventions. For UK invoices I use &lt;code&gt;number_format($amount, 2, '.', ',')&lt;/code&gt;. For European invoices where the comma is the decimal separator, switch the arguments. Getting this wrong in production is embarrassing.&lt;/p&gt;

&lt;p&gt;Don't render invoices synchronously inside a web request if you can avoid it. Queue it. Laravel's job system handles this naturally, and the API supports webhooks if you want the render itself to be asynchronous and get notified when it's done. For batch runs, the webhook flow is substantially faster because you're not blocking on each render.&lt;/p&gt;

&lt;p&gt;One operational note: version your invoice template. When you change the design, old invoices that get re-rendered will look different from their original version, which can cause accounting confusion. Either snapshot the rendered PNG to S3 at generation time and serve that, or include a &lt;code&gt;template_version&lt;/code&gt; column on the invoice and pick the right Blade view based on it. I do the former.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://html2img.com/docs/usage/laravel" rel="noopener noreferrer"&gt;Laravel usage guide&lt;/a&gt; covers the framework integration in more depth, including the HTTP client setup and how to test the renderer without hitting the API. For longer documents or batch work, the &lt;a href="https://html2img.com/docs/parameters/webhook-url" rel="noopener noreferrer"&gt;webhook parameter docs&lt;/a&gt; walk through the async pattern. And if you want more examples of document-style templates beyond invoices, the &lt;a href="https://html2img.com/docs/examples/invoice-receipt" rel="noopener noreferrer"&gt;invoice and receipt example&lt;/a&gt; has a few variants.&lt;/p&gt;

&lt;p&gt;Render the view, post the HTML, save the PNG. Your Blade template is the source of truth for what the invoice looks like, the same way it's the source of truth for the rest of your app.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>html</category>
      <category>invoice</category>
    </item>
    <item>
      <title>Replacing Puppeteer on AWS Lambda for Screenshots</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Thu, 30 Apr 2026 08:44:00 +0000</pubDate>
      <link>https://forem.com/accreditly/replacing-puppeteer-on-aws-lambda-for-screenshots-3622</link>
      <guid>https://forem.com/accreditly/replacing-puppeteer-on-aws-lambda-for-screenshots-3622</guid>
      <description>&lt;p&gt;My chrome-aws-lambda function had been running fine for about eight months. Then Chrome shipped a new major version, the pinned Chromium binary in my layer went stale, pages started rendering with missing fonts, and the Lambda cold starts crept from two seconds up to nearly nine. I spent a weekend rebuilding the layer, swapping to &lt;code&gt;@sparticuz/chromium&lt;/code&gt;, and arguing with webpack about why &lt;code&gt;puppeteer-core&lt;/code&gt; kept bundling things it shouldn't.&lt;/p&gt;

&lt;p&gt;That was the third time in two years I'd done some version of that weekend. At that point I started seriously looking at Puppeteer Lambda alternatives, because the pattern was clear: running headless Chrome inside a 250MB function limit is a thing you can make work, but it's not a thing that stays working without active maintenance.&lt;/p&gt;

&lt;p&gt;This article is about what I moved to instead. It's specifically for people who have a working Puppeteer-on-Lambda setup that's getting expensive to maintain, or who are about to build one and want to know if there's a less painful option. The short version: yes, and the trade-off is usually worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Puppeteer on Lambda gets expensive to own
&lt;/h2&gt;

&lt;p&gt;The problems compound. Individually none of them are deal-breakers, but together they chew through engineering time.&lt;/p&gt;

&lt;p&gt;Lambda has a 250MB unzipped deployment size limit. A full Chromium binary is around 170MB compressed, more unzipped, so you're immediately using a Lambda layer or container image. Every project that needs screenshots now has infrastructure decisions to make before it renders a single pixel.&lt;/p&gt;

&lt;p&gt;Chrome ships a new major version roughly every four weeks. &lt;code&gt;chrome-aws-lambda&lt;/code&gt; famously stopped receiving updates, and its successor &lt;code&gt;@sparticuz/chromium&lt;/code&gt; needs a matching &lt;code&gt;puppeteer-core&lt;/code&gt; version. Mismatch them and pages render oddly, or not at all. I've had a deployment where screenshots of the same URL produced different results locally (using a Chrome 120 binary) versus on Lambda (pinned to 114), and spent half a day figuring out why a flexbox layout was collapsing only in production.&lt;/p&gt;

&lt;p&gt;Cold starts are the user-facing tax. Booting a headless Chrome instance inside a Lambda container takes two to four seconds even on warm infrastructure, more if the binary has to be extracted from the layer first. You can keep functions warm, but that undoes some of the cost argument for serverless in the first place.&lt;/p&gt;

&lt;p&gt;Memory is the quiet killer. Chrome's per-page memory footprint is unpredictable. A page with a complex JavaScript-rendered chart can briefly spike to 700MB. Your function runs fine on a 1024MB allocation ninety-five percent of the time, and then a specific URL causes an OOM and the whole invocation fails. You discover this in production.&lt;/p&gt;

&lt;p&gt;None of these are bugs. They're the shape of the problem. Running a browser engine inside a serverless function is just an awkward fit, and the fit doesn't improve over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternative: treat the browser as an API
&lt;/h2&gt;

&lt;p&gt;The pattern I settled on is to treat rendering as a third-party service rather than infrastructure I own. My Lambda function does what Lambdas are good at, which is receiving an event, doing some business logic, and making HTTP calls. When it needs an image, it posts to a rendering API and gets back a PNG. The browser lives somewhere else, maintained by someone whose entire job is keeping it running.&lt;/p&gt;

&lt;p&gt;There are a few services that do this. I landed on &lt;a href="https://html2img.com/" rel="noopener noreferrer"&gt;html2img.com&lt;/a&gt; because its API accepts raw HTML directly, which matters when the thing I'm trying to screenshot is something I've generated server-side (invoices, social cards, receipts) rather than a public URL. For URL-based screenshots it also works fine, but the HTML-first path is what got me off Puppeteer.&lt;/p&gt;

&lt;p&gt;The migration is mostly subtractive. You delete the Chromium layer, remove &lt;code&gt;puppeteer-core&lt;/code&gt; from your dependencies, swap the browser-launching block for a &lt;code&gt;fetch&lt;/code&gt; call, and your Lambda package size drops by about 80%.&lt;/p&gt;

&lt;p&gt;Here's what we're going to be making:&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%2Fa6zq5t3vb5sidia6u94n.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%2Fa6zq5t3vb5sidia6u94n.png" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the replacement looks like in practice
&lt;/h2&gt;

&lt;p&gt;Here's a before-and-after for a Lambda function that generates a receipt image when an order is placed. The before version uses &lt;code&gt;@sparticuz/chromium&lt;/code&gt; and &lt;code&gt;puppeteer-core&lt;/code&gt;. The after version uses &lt;code&gt;fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Puppeteer version, trimmed for readability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const chromium = require('@sparticuz/chromium');
const puppeteer = require('puppeteer-core');

exports.handler = async (event) =&amp;gt; {
  const { order } = JSON.parse(event.body);
  const html = buildReceiptHtml(order);

  const browser = await puppeteer.launch({
    args: chromium.args,
    executablePath: await chromium.executablePath(),
    headless: chromium.headless,
  });

  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 600, height: 800, deviceScaleFactor: 2 });
    await page.setContent(html, { waitUntil: 'networkidle0' });
    const png = await page.screenshot({ type: 'png' });

    return {
      statusCode: 200,
      headers: { 'Content-Type': 'image/png' },
      body: png.toString('base64'),
      isBase64Encoded: true,
    };
  } finally {
    await browser.close();
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works. It's also 180MB of dependencies, takes three seconds to cold start, and will break the next time Chrome updates and you forget to bump the Chromium package.&lt;/p&gt;

&lt;p&gt;The replacement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.handler = async (event) =&amp;gt; {
  const { order } = JSON.parse(event.body);
  const html = buildReceiptHtml(order);

  const response = await fetch('https://api.html2img.com/v1/render', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.HTML2IMG_API_KEY}`,
    },
    body: JSON.stringify({
      html,
      viewport_width: 600,
      viewport_height: 800,
      device_scale_factor: 2,
      wait_for_selector: '.total',
    }),
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`Render failed: ${response.status} ${err}`);
  }

  const png = Buffer.from(await response.arrayBuffer());

  return {
    statusCode: 200,
    headers: { 'Content-Type': 'image/png' },
    body: png.toString('base64'),
    isBase64Encoded: true,
  };
};

function buildReceiptHtml(order) {
  const rows = order.items.map(item =&amp;gt; `
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;${escape(item.name)}&amp;lt;/td&amp;gt;
      &amp;lt;td class="qty"&amp;gt;${item.quantity}&amp;lt;/td&amp;gt;
      &amp;lt;td class="price"&amp;gt;£${item.price.toFixed(2)}&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  `).join('');

  return `
    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;html&amp;gt;
      &amp;lt;head&amp;gt;
        &amp;lt;style&amp;gt;
          body {
            width: 600px;
            font-family: -apple-system, system-ui, sans-serif;
            padding: 48px;
            color: #111;
          }
          h1 { font-size: 28px; margin-bottom: 8px; }
          .meta { color: #666; font-size: 14px; margin-bottom: 32px; }
          table { width: 100%; border-collapse: collapse; }
          td { padding: 12px 0; border-bottom: 1px solid #eee; font-size: 16px; }
          .qty, .price { text-align: right; }
          .total {
            margin-top: 24px;
            padding-top: 24px;
            border-top: 2px solid #111;
            font-size: 22px;
            font-weight: 700;
            display: flex;
            justify-content: space-between;
          }
          footer { margin-top: 48px; color: #888; font-size: 13px; }
        &amp;lt;/style&amp;gt;
      &amp;lt;/head&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;h1&amp;gt;${escape(order.businessName)}&amp;lt;/h1&amp;gt;
        &amp;lt;div class="meta"&amp;gt;Order #${order.id} - ${order.date}&amp;lt;/div&amp;gt;
        &amp;lt;table&amp;gt;&amp;lt;tbody&amp;gt;${rows}&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;
        &amp;lt;div class="total"&amp;gt;&amp;lt;span&amp;gt;Total&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;£${order.total.toFixed(2)}&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;footer&amp;gt;Thanks for your order.&amp;lt;/footer&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  `;
}

function escape(str) {
  return String(str)
    .replace(/&amp;amp;/g, '&amp;amp;amp;')
    .replace(/&amp;lt;/g, '&amp;amp;lt;')
    .replace(/&amp;gt;/g, '&amp;amp;gt;');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same input, same output. No browser to boot, no layer to maintain, no Chromium version to track. The Lambda deployment is now just your handler code and whatever you were doing before the Puppeteer block, which in my case was about 40KB zipped.&lt;/p&gt;

&lt;p&gt;A couple of things worth pointing out in the replacement. The &lt;code&gt;wait_for_selector&lt;/code&gt; parameter tells the renderer to wait until &lt;code&gt;.total&lt;/code&gt; exists in the DOM before capturing, which matters if your HTML has any client-side rendering or webfonts. &lt;code&gt;device_scale_factor: 2&lt;/code&gt; gives you a retina-quality PNG rendered into the specified viewport, which is typically what you want for anything that'll be displayed or printed. And &lt;code&gt;escape&lt;/code&gt; is doing the job that &lt;code&gt;page.setContent&lt;/code&gt; was implicitly doing for you before, which is preventing user-supplied data from breaking your HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas worth knowing
&lt;/h2&gt;

&lt;p&gt;The big behavioural difference is latency. An API call adds a round-trip that your in-Lambda Puppeteer didn't have. In practice this has been faster overall for me, because the cold start I was paying on Lambda was longer than the API round-trip, but it's worth measuring for your own workload. If you're doing thousands of renders synchronously inside a single request handler, you'll want to parallelise the API calls or use the webhook flow so the render happens asynchronously.&lt;/p&gt;

&lt;p&gt;For anything bulk or latency-sensitive, the webhook pattern is worth knowing about. You post the HTML with a &lt;code&gt;webhook_url&lt;/code&gt; parameter, get back a job ID immediately, and html2img posts the finished PNG to your webhook when it's done. Your Lambda returns in milliseconds, and a separate function handles the completed image. This is how I run anything that generates more than one image per invocation.&lt;/p&gt;

&lt;p&gt;Error handling needs more thought than with Puppeteer, because you now have a network dependency. Wrap the fetch in a retry with backoff for 5xx responses. Log the response body on failure, not just the status code, because the API's error messages usually tell you exactly what's wrong (a missing selector, an HTML syntax error, a timeout). And make sure your function timeout is higher than the API's maximum render time plus a margin.&lt;/p&gt;

&lt;p&gt;Finally, don't commit your API key. It reads as obvious advice but I've seen people check a key into a repo because their Lambda needs it at runtime and "it's just for a test." Use Parameter Store, Secrets Manager, or at minimum an environment variable set through the console, never a hardcoded literal.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://html2img.com/docs/usage" rel="noopener noreferrer"&gt;Node.js usage guide&lt;/a&gt; covers the full parameter surface, and the &lt;a href="https://html2img.com/docs/parameters/webhook-url" rel="noopener noreferrer"&gt;webhook documentation&lt;/a&gt; walks through the async flow if you're doing bulk work. If you're specifically migrating existing Puppeteer screenshot code, the &lt;a href="https://html2img.com/docs/parameters" rel="noopener noreferrer"&gt;parameters reference&lt;/a&gt; maps the Puppeteer options you're used to (viewport, wait conditions, full-page capture) to the equivalent API parameters.&lt;/p&gt;

&lt;p&gt;Less infrastructure, fewer weekends lost to Chrome version drift, same PNGs out the other end. For any Lambda that currently boots a browser, that's a good trade.&lt;/p&gt;

</description>
      <category>puppeteer</category>
      <category>aws</category>
      <category>lambda</category>
      <category>screenshots</category>
    </item>
    <item>
      <title>Dynamic OG Images in Next.js Without @vercel/og (1,200 630)</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Mon, 27 Apr 2026 08:42:29 +0000</pubDate>
      <link>https://forem.com/accreditly/dynamic-og-images-in-nextjs-without-vercelog-1200x630-30ic</link>
      <guid>https://forem.com/accreditly/dynamic-og-images-in-nextjs-without-vercelog-1200x630-30ic</guid>
      <description>&lt;p&gt;I had a perfectly reasonable OG image template. Flexbox layout, custom font, a gradient background, a small CSS grid for a two-column footer with the author name and publish date. Worked in the browser. Looked great in Figma. Then I moved it into &lt;code&gt;next/og&lt;/code&gt; and half of it disappeared.&lt;/p&gt;

&lt;p&gt;No errors. No warnings. Satori, the renderer behind &lt;code&gt;ImageResponse&lt;/code&gt;, just silently ignored the parts it doesn't support. CSS grid, gone. The &lt;code&gt;calc()&lt;/code&gt; I was using for spacing, ignored. A CSS variable for the brand colour, treated as an unknown value. The fallback rendered, which was worse than if it had thrown, because I didn't notice until someone shared the link on Slack and the preview was visibly broken.&lt;/p&gt;

&lt;p&gt;If you've been building dynamic OG images in Next.js and hit the same wall, this article is about the other way to do it: rendering your template in an actual browser via an API, and serving the resulting PNG from your &lt;code&gt;opengraph-image&lt;/code&gt; route. Same developer ergonomics, no CSS subset to memorise, and your template is literally just HTML and CSS that renders the way you'd expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why @vercel/og falls short once your template gets real
&lt;/h2&gt;

&lt;p&gt;Satori is clever. It parses JSX, approximates a subset of CSS, and produces an SVG which then gets rasterised to PNG. For simple cards, a title over a solid background with one font, it's lovely. The problems show up when your designer hands you something that looks like an actual marketing asset.&lt;/p&gt;

&lt;p&gt;Flexbox is supported. Grid is not. &lt;code&gt;display: flex&lt;/code&gt; has to be set explicitly on every container, even &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;, because Satori's defaults aren't the browser's. Custom fonts have to be fetched inside the request handler and passed into &lt;code&gt;ImageResponse&lt;/code&gt;, not loaded at module scope. The entire bundle, including fonts and any inlined imagery, has to stay under 500KB on Edge. CSS variables don't resolve. &lt;code&gt;calc()&lt;/code&gt; doesn't compute. Box shadows work, mostly, until they don't.&lt;/p&gt;

&lt;p&gt;You can work around all of this. I did, for a while. The problem is that every design tweak becomes a translation exercise. You're not writing CSS any more, you're writing the subset of CSS that Satori understands, and the feedback loop is slow because rendering happens at request time on Edge.&lt;/p&gt;

&lt;p&gt;The alternative that I keep coming back to is rendering the template in a real Chromium instance. You write normal HTML and CSS, including grid, variables, custom fonts loaded with &lt;code&gt;@font-face&lt;/code&gt;, animations you can freeze on a specific frame if you want, anything. Then you screenshot it at 1200×630 and serve the PNG. The only question is where that Chromium lives.&lt;/p&gt;

&lt;p&gt;Running Puppeteer yourself on Vercel or AWS Lambda is possible but tedious. The chromium binary is too large for the default Lambda size limit, so you end up on &lt;code&gt;@sparticuz/chromium&lt;/code&gt; or a layer, watching memory usage, dealing with cold starts, pinning versions when Chrome updates break things. For an OG image pipeline that has to work reliably every time someone shares a link, I'd rather not own that problem. An API that takes HTML and gives you back a PNG cuts out the whole category of infrastructure work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approach
&lt;/h2&gt;

&lt;p&gt;The pattern is straightforward. In your route folder, you add an &lt;code&gt;opengraph-image.tsx&lt;/code&gt; file (Next.js picks this up automatically and wires it to the right meta tag). Inside it, you build the HTML and CSS for the card using whatever data the route has access to, post it to the html2img API, and return the resulting PNG as the response.&lt;/p&gt;

&lt;p&gt;Because Next.js caches OG image routes aggressively by default, you don't pay the API cost on every share. The image gets generated once per unique URL and then served from the framework's cache. If your content changes, you bust the cache the same way you'd bust any route cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the thing in Next.js
&lt;/h2&gt;

&lt;p&gt;Here's a working implementation for a blog where each post has its own OG image with the title, author, publish date, and category.&lt;/p&gt;

&lt;p&gt;Here's what we're going to be making:&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%2Fzaiox25iklnsaaoix4ia.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%2Fzaiox25iklnsaaoix4ia.png" width="800" height="420"&gt;&lt;/a&gt;Looks great, right?&lt;/p&gt;

&lt;p&gt;I'm using the App Router. If you're on Pages Router, the same idea works inside an API route, you just return the bytes yourself.&lt;/p&gt;

&lt;p&gt;Inside the post route folder, &lt;code&gt;app/posts/[slug]/opengraph-image.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { getPostBySlug } from '@/lib/posts';

export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default async function OpengraphImage({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getPostBySlug(params.slug);

  const html = `
    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;html&amp;gt;
      &amp;lt;head&amp;gt;
        &amp;lt;style&amp;gt;
          @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900&amp;amp;display=swap');
          :root {
            --brand: #0b1220;
            --accent: #f59e0b;
            --muted: #94a3b8;
          }
          * { box-sizing: border-box; margin: 0; padding: 0; }
          body {
            width: 1200px;
            height: 630px;
            background: linear-gradient(135deg, var(--brand) 0%, #1e293b 100%);
            font-family: 'Inter', system-ui, sans-serif;
            color: white;
            padding: 80px;
            display: grid;
            grid-template-rows: auto 1fr auto;
          }
          .category {
            font-size: 20px;
            font-weight: 700;
            color: var(--accent);
            text-transform: uppercase;
            letter-spacing: 2px;
          }
          h1 {
            font-size: 68px;
            font-weight: 900;
            line-height: 1.1;
            align-self: center;
            max-width: 900px;
          }
          footer {
            display: grid;
            grid-template-columns: 1fr auto;
            align-items: end;
            color: var(--muted);
            font-size: 22px;
          }
          .author { color: white; font-weight: 700; }
        &amp;lt;/style&amp;gt;
      &amp;lt;/head&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;div class="category"&amp;gt;${escapeHtml(post.category)}&amp;lt;/div&amp;gt;
        &amp;lt;h1&amp;gt;${escapeHtml(post.title)}&amp;lt;/h1&amp;gt;
        &amp;lt;footer&amp;gt;
          &amp;lt;div&amp;gt;&amp;lt;span class="author"&amp;gt;${escapeHtml(post.author)}&amp;lt;/span&amp;gt; - ${post.publishedAt}&amp;lt;/div&amp;gt;
          &amp;lt;div&amp;gt;yourdomain.com&amp;lt;/div&amp;gt;
        &amp;lt;/footer&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  `;

  const response = await fetch('https://api.html2img.com/v1/render', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.HTML2IMG_API_KEY}`,
    },
    body: JSON.stringify({
      html,
      viewport_width: 1200,
      viewport_height: 630,
      device_scale_factor: 2,
      wait_for_selector: 'h1',
    }),
  });

  if (!response.ok) {
    throw new Error(`html2img returned ${response.status}`);
  }

  const png = await response.arrayBuffer();

  return new Response(png, {
    headers: {
      'Content-Type': 'image/png',
      'Cache-Control': 'public, max-age=31536000, immutable',
    },
  });
}

function escapeHtml(str: string) {
  return str
    .replace(/&amp;amp;/g, '&amp;amp;amp;')
    .replace(/&amp;lt;/g, '&amp;amp;lt;')
    .replace(/&amp;gt;/g, '&amp;amp;gt;')
    .replace(/"/g, '&amp;amp;quot;');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth pointing out. &lt;code&gt;device_scale_factor: 2&lt;/code&gt; gives you a retina-quality 2400×1260 PNG, which is what Twitter actually wants these days, rendered into the 1200×630 viewport. &lt;code&gt;wait_for_selector: 'h1'&lt;/code&gt; makes sure the page has rendered the heading before the screenshot runs, which matters if you're pulling a font from Google Fonts. Without that wait, you can occasionally get an image where the font hasn't loaded yet and the fallback renders instead.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;escapeHtml&lt;/code&gt; helper is not optional. If someone's post title contains a quote character or an ampersand and you interpolate it into an HTML string without escaping, you get either a broken image or, depending on what you're rendering, a small HTML injection vector on your own server. Always escape.&lt;/p&gt;

&lt;p&gt;Set your API key as an environment variable and you're done. Next.js will call this function when Twitter or LinkedIn or Slack hits &lt;code&gt;/posts/my-post/opengraph-image.png&lt;/code&gt;, cache the result, and serve it from CDN on subsequent requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas I've hit
&lt;/h2&gt;

&lt;p&gt;Fonts are the most common source of surprise. Google Fonts via &lt;code&gt;@import&lt;/code&gt; works but adds a network round-trip inside the render. If latency matters to you, either use a system font stack, inline a &lt;code&gt;@font-face&lt;/code&gt; with a base64-encoded woff2, or host the font on your own CDN. The &lt;code&gt;wait_for_selector&lt;/code&gt; parameter helps, but if the selector appears before the font loads, you still get a fallback. For guaranteed results, add a small &lt;code&gt;ms_delay&lt;/code&gt; on top.&lt;/p&gt;

&lt;p&gt;Image assets inside the HTML need to be publicly accessible URLs. If your logo is behind auth, inline it as a base64 data URL instead. Same for any background images.&lt;/p&gt;

&lt;p&gt;Caching is worth thinking about carefully. The &lt;code&gt;Cache-Control: immutable&lt;/code&gt; header on the route is safe because Next.js gives each OG image route a unique URL per slug, but if your post content changes and you want the OG image to reflect that, you need to either include a content hash in the route or manually revalidate. I lean towards including a short hash of the title in the URL for content that might be edited.&lt;/p&gt;

&lt;p&gt;Finally, error handling. If the API call fails, you don't want your share previews to break. Wrap the fetch in a try/catch and return a static fallback image on failure. A broken OG image is much worse than a generic one.&lt;/p&gt;

&lt;p&gt;The framework-specific setup is covered in more detail in &lt;a href="https://html2img.com/docs/usage/react" rel="noopener noreferrer"&gt;the html2img React and Next.js usage guide&lt;/a&gt;. If you want to see more template examples beyond blog cards, &lt;a href="https://html2img.com/docs/examples" rel="noopener noreferrer"&gt;the product card and testimonial card examples&lt;/a&gt; are good starting points for social-share-style imagery. And for the full list of rendering parameters, including webhook support for async generation when you're batch-regenerating a lot of images, the &lt;a href="https://html2img.com/docs/parameters" rel="noopener noreferrer"&gt;parameters reference&lt;/a&gt; has the complete set.&lt;/p&gt;

&lt;p&gt;Write the HTML the way you'd write any landing page, let a real browser render it, cache the output. That's the whole pattern.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>css</category>
      <category>html</category>
      <category>vercel</category>
    </item>
    <item>
      <title>How to use Tailwinds `safelist` to handle dynamic classes</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Fri, 23 Aug 2024 09:25:00 +0000</pubDate>
      <link>https://forem.com/accreditly/how-to-use-tailwinds-safelist-to-handle-dynamic-classes-4o16</link>
      <guid>https://forem.com/accreditly/how-to-use-tailwinds-safelist-to-handle-dynamic-classes-4o16</guid>
      <description>&lt;p&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; is a popular utility-first CSS framework that allows developers to create custom designs quickly and efficiently. By default, Tailwind CSS generates a wide range of utility classes, which can lead to large file sizes. To address this issue, Tailwind CSS comes with a built-in feature called PurgeCSS that removes unused styles from the production build, making the final CSS file smaller and more performant. However, this automatic removal may sometimes cause issues when certain styles are used dynamically or conditionally in your application. In this article, we'll dive deep into the &lt;code&gt;safelist&lt;/code&gt; feature in Tailwind CSS, learn how to whitelist specific styles, and explore various scenarios where using &lt;code&gt;safelist&lt;/code&gt; can be helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Understanding PurgeCSS in Tailwind CSS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://purgecss.com/" rel="noopener noreferrer"&gt;PurgeCSS&lt;/a&gt; is a powerful tool that scans your project files for any class names used and removes the unused ones from the final CSS file. This significantly reduces the size of the generated CSS, making your application load faster. &lt;/p&gt;

&lt;p&gt;By default, Tailwind CSS includes PurgeCSS configuration that scans your HTML, JavaScript, and Vue files for any class names. You can easily tweak what files are picked up within the &lt;code&gt;content&lt;/code&gt; array of the config file.&lt;/p&gt;

&lt;p&gt;In some situations, you might need to prevent specific styles from being removed, even if they're not detected in your files. This is where the &lt;code&gt;safelist&lt;/code&gt; feature comes into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Introducing Safelist
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Safelist&lt;/code&gt; is a feature in Tailwind CSS that allows you to whitelist certain styles so they don't get removed during the purging process. This is particularly useful when you have dynamic class names generated through JavaScript or applied based on user interaction. Another very common use-case for &lt;code&gt;safelist&lt;/code&gt; is when colors or styles are driven from a CMS or backend framework. One such example might be a system that allows a website admin to edit the color of a category in a CMS, which in turn changes the color of the nav items for that category. Tailwind won't see the actual class name as the file will contain server-side code that outputs the color.&lt;/p&gt;

&lt;p&gt;By adding these class names to the safelist, you ensure that they will always be included in your final CSS file, regardless of whether PurgeCSS can find them in your project files or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Configuring Safelist in &lt;code&gt;tailwind.config.js&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To configure the &lt;code&gt;safelist&lt;/code&gt; in your Tailwind CSS project, you need to modify the &lt;code&gt;tailwind.config.js&lt;/code&gt; file. The &lt;code&gt;safelist&lt;/code&gt; is an array of class names that you want to keep in your final CSS file, even if they're not found in your project files. Here's an example of how to add class names to the &lt;code&gt;safelist&lt;/code&gt;:&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;// tailwind.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// your content files here&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;safelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bg-red-500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text-white&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hover:bg-red-700&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;  
  &lt;span class="c1"&gt;// other configurations&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;bg-red-500&lt;/code&gt;, &lt;code&gt;text-white&lt;/code&gt;, and &lt;code&gt;hover:bg-red-700&lt;/code&gt; classes are added to the &lt;code&gt;safelist&lt;/code&gt;. These classes will always be included in your final CSS file, even if PurgeCSS doesn't find them in your project files.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. More advanced configurations
&lt;/h2&gt;

&lt;p&gt;If you have a lot of classes to manage within &lt;code&gt;safelist&lt;/code&gt;, perhaps due to multiple colors and the need to support variants/modifiers such as &lt;code&gt;:hover&lt;/code&gt;, &lt;code&gt;:focus&lt;/code&gt;, &lt;code&gt;:active&lt;/code&gt; and &lt;code&gt;dark:&lt;/code&gt; then it can quickly become very challenging to manage these within &lt;code&gt;safelist&lt;/code&gt;. The list will become huge very quickly.&lt;/p&gt;

&lt;p&gt;That's where patterns come in. Tailwind support regex within the &lt;code&gt;safelist&lt;/code&gt;:&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;safelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/from-&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;blue|green|indigo|pink|orange|rose&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;-200/&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/to-&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;blue|green|indigo|pink|orange|rose&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;-100/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these 2 entries we are effectively adding 12 classes. &lt;code&gt;from-{color}-200&lt;/code&gt; and &lt;code&gt;to-{color}-100&lt;/code&gt;, where &lt;code&gt;{color}&lt;/code&gt; is all of the colors in the list. It makes it much easier to manage the lists. Remember that &lt;code&gt;tailwind.config.js&lt;/code&gt; is just a JavaScript file, so you can manage variables at the top of the file if you're managing lists of colors that are repeated heavily.&lt;/p&gt;

&lt;p&gt;It's also possible to define variants for everything within the list without needing to explicitly list them in regex:&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;safelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/text-&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;blue|green|indigo|pink|orange|rose&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;600|400&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="na"&gt;variants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hover&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/from-&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;blue|green|indigo|pink|orange|rose&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;-200/&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/to-&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;blue|green|indigo|pink|orange|rose&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;-100/&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;
  
  
  5. Safelist Examples and Use Cases
&lt;/h2&gt;

&lt;p&gt;There are several scenarios where using the &lt;code&gt;safelist&lt;/code&gt; feature can be helpful:&lt;/p&gt;

&lt;p&gt;Dynamic class names: If you're generating class names dynamically based on some data or user input, PurgeCSS may not detect these classes and remove them from the final CSS file. By adding these dynamic classes to the &lt;code&gt;safelist&lt;/code&gt;, you can ensure they're always available in your application.&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;// Example of a dynamic class name based on user input&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This value might come from an API or user input&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alertClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`alert-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Generated class name: 'alert-success'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;alertClass&lt;/code&gt; variable generates a class name based on user input or data from an API. Since PurgeCSS can't detect this dynamic class name, you should add it to the &lt;code&gt;safelist&lt;/code&gt; in your &lt;code&gt;tailwind.config.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Conditional styles: If you have styles that only apply under specific conditions, such as a dark mode or a mobile view, you can use the &lt;code&gt;safelist&lt;/code&gt; to ensure those styles are always included in your final CSS file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example of a conditional style based on a media query&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;767&lt;/span&gt;&lt;span class="nx"&gt;px&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;hidden&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;mobile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the hidden-mobile class is only applied when the viewport width is less than 768 pixels. Since this class might not be detected by PurgeCSS, you should add it to the &lt;code&gt;safelist&lt;/code&gt; in your &lt;code&gt;tailwind.config.js&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Best Practices for Safelisting
&lt;/h2&gt;

&lt;p&gt;When using the &lt;code&gt;safelist&lt;/code&gt; feature in Tailwind CSS, keep the following best practices in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only add classes to the &lt;code&gt;safelist&lt;/code&gt; that are truly necessary. Adding too many classes can bloat your final CSS file and negate the benefits of PurgeCSS.&lt;/li&gt;
&lt;li&gt;If you have many dynamic class names or a complex application, consider using a function or regular expression to generate the safelist array. This can help keep your configuration cleaner and more maintainable.&lt;/li&gt;
&lt;li&gt;Test your production build to ensure that all required styles are included. This can help you catch any issues early on and avoid surprises when deploying your application.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;safelist&lt;/code&gt; feature in Tailwind CSS provides a powerful way to whitelist specific styles and ensure they are included in your final CSS file, even if they are not detected by PurgeCSS. By understanding how to configure the &lt;code&gt;safelist&lt;/code&gt; and use it effectively in various scenarios, you can make your Tailwind CSS projects more robust and maintainable. Remember to follow best practices when using the &lt;code&gt;safelist&lt;/code&gt; to ensure your final CSS file remains lean and performant.&lt;/p&gt;

&lt;p&gt;Feel free to look over the &lt;a href="https://tailwindcss.com/docs/content-configuration#safelisting-classes" rel="noopener noreferrer"&gt;Tailwind Docs on Safelist&lt;/a&gt; usage.&lt;/p&gt;

</description>
      <category>tailwindcss</category>
      <category>css</category>
      <category>frontend</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Send email from WordPress the right way</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Tue, 20 Aug 2024 09:14:46 +0000</pubDate>
      <link>https://forem.com/accreditly/send-email-from-wordpress-the-right-way-4jok</link>
      <guid>https://forem.com/accreditly/send-email-from-wordpress-the-right-way-4jok</guid>
      <description>&lt;p&gt;Sending emails is a critical feature for many WordPress websites, whether it’s for account confirmations, notifications, or customized alerts. While many developers might default to using PHP’s &lt;code&gt;mail()&lt;/code&gt; function, WordPress offers more robust and reliable alternatives. Let's explore the best practices for sending both plaintext and HTML emails within WordPress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Avoid PHP’s &lt;code&gt;mail()&lt;/code&gt; Function?
&lt;/h2&gt;

&lt;p&gt;While PHP’s &lt;code&gt;mail()&lt;/code&gt; function is straightforward, it has several drawbacks, particularly in terms of flexibility and reliability. It lacks support for SMTP authentication, which can lead to emails being flagged as spam. WordPress's built-in functions are designed to overcome these limitations, offering better formatting options and more consistent delivery rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending Plaintext Emails with WordPress
&lt;/h2&gt;

&lt;p&gt;WordPress makes it easy to send plaintext emails using the &lt;code&gt;wp_mail()&lt;/code&gt; function, which is more versatile than PHP’s native &lt;code&gt;mail()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Here’s a basic example of how you can use &lt;code&gt;wp_mail()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'user@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Welcome to Our Platform!'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Thank you for joining us.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;wp_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to add headers, such as a custom "From" address or "Reply-To," you can do so with an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'From: Support Team &amp;lt;support@example.com&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Reply-To: support@example.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;wp_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures that your emails are sent with the correct headers, improving deliverability and reducing the likelihood of your emails being marked as spam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crafting HTML Emails in WordPress
&lt;/h2&gt;

&lt;p&gt;If you want to send emails that are more visually engaging, such as newsletters or account information emails, HTML is the way to go. To send HTML emails in WordPress, you’ll need to set the content type to &lt;code&gt;text/html&lt;/code&gt;. This can be achieved by using the &lt;code&gt;wp_mail_content_type&lt;/code&gt; filter.&lt;/p&gt;

&lt;p&gt;Here’s an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;set_html_mail_content_type&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'text/html'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_mail_content_type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'set_html_mail_content_type'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$recipient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'user@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Your Account Information'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;h1&amp;gt;Welcome to Our Platform!&amp;lt;/h1&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;p&amp;gt;Here are your account details.&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;wp_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Reset content type to avoid affecting other emails&lt;/span&gt;
&lt;span class="nf"&gt;remove_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wp_mail_content_type'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'set_html_mail_content_type'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we first define a function to set the content type to HTML and then add it as a filter. After sending the email, the filter is removed to ensure that subsequent emails are not inadvertently sent as HTML.&lt;/p&gt;

&lt;p&gt;For a more in-depth discussion on the best practices for sending emails from WordPress, including additional code examples and troubleshooting tips, check out our &lt;a href="https://accreditly.io/articles/how-to-send-email-from-wordpress-the-right-way-html-and-plaintext" rel="noopener noreferrer"&gt;full article here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhance Your WordPress Skills
&lt;/h2&gt;

&lt;p&gt;If you're looking to deepen your understanding of WordPress, including how to handle email functionality more effectively, consider pursuing our &lt;a href="https://accreditly.io/certifications/wordpress-development" rel="noopener noreferrer"&gt;WordPress Development Certification&lt;/a&gt;. It’s a comprehensive program designed to take your skills to the next level, covering everything from basic setup to advanced customization techniques.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Differences Between "Test Coverage" and "Code Coverage"</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Thu, 13 Jun 2024 08:06:54 +0000</pubDate>
      <link>https://forem.com/accreditly/the-differences-between-test-coverage-and-code-coverage-20fc</link>
      <guid>https://forem.com/accreditly/the-differences-between-test-coverage-and-code-coverage-20fc</guid>
      <description>&lt;p&gt;As developers, we often hear terms like "test coverage" and "code coverage" thrown around in discussions about software quality. While they may sound similar, they represent different aspects of testing and development. Understanding the nuances between these two concepts is essential for improving code quality and ensuring robust software.&lt;/p&gt;

&lt;p&gt;In this article, we’ll delve into what test coverage and code coverage mean, their importance, how they differ, and how you can effectively use them to enhance your development process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Test Coverage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test coverage&lt;/strong&gt; is a metric that helps us understand how much of our application has been tested. It focuses on the completeness of the testing effort and is usually expressed as a percentage. Test coverage metrics can include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensuring all user requirements have corresponding test cases, known as 'requirements coverage'.&lt;/li&gt;
&lt;li&gt;Verifying that all functions or methods are tested, known as 'functional coverage'.&lt;/li&gt;
&lt;li&gt;Confirming that every branch (e.g., if-else conditions) in the code has been tested, known as 'branch coverage'.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Test Coverage Matters
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Test coverage helps identify untested parts of your application, pointing out areas that may need more thorough testing.&lt;/li&gt;
&lt;li&gt;By ensuring all aspects of your application are tested, you can catch bugs early, leading to higher quality software.&lt;/li&gt;
&lt;li&gt;High test coverage can give developers and stakeholders confidence that the application has been thoroughly vetted. This is especially true when introducing new developers to the project who may not understand some intricacies.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Measuring Test Coverage
&lt;/h2&gt;

&lt;p&gt;Tools like &lt;strong&gt;JUnit&lt;/strong&gt; for Java, &lt;strong&gt;NUnit&lt;/strong&gt; for .NET, and &lt;strong&gt;pytest&lt;/strong&gt; for Python, &lt;strong&gt;PHPUnit&lt;/strong&gt; and the newer &lt;strong&gt;Pest&lt;/strong&gt; for PHP provide features for measuring test coverage. These tools generate reports that show which parts of the application were executed during tests, helping developers identify untested sections.&lt;/p&gt;

&lt;p&gt;Here's a simple example using pytest in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test_example.py
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_add&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# To run the test and measure coverage
# Use the following command:
# pytest --cov=.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pytest-cov&lt;/code&gt; plugin generates a report showing the percentage of code covered by the tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Code Coverage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Code coverage&lt;/strong&gt;, on the other hand, measures the extent to which your code has been executed. It's about ensuring that your codebase has been thoroughly exercised by tests. Key metrics for code coverage include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The percentage of lines of code executed, known as 'line coverage'.&lt;/li&gt;
&lt;li&gt;The percentage of executable statements run, known as 'statement coverage'.&lt;/li&gt;
&lt;li&gt;The percentage of possible execution paths tested, known as 'path coverage'.&lt;/li&gt;
&lt;li&gt;The percentage of functions executed, known as 'function coverage'.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Code Coverage Matters
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Code coverage can help identify dead or redundant code that’s never executed, allowing you to clean up your codebase.&lt;/li&gt;
&lt;li&gt;Well-covered code is often easier to maintain because it’s more likely to have fewer hidden bugs.&lt;/li&gt;
&lt;li&gt;High code coverage ensures that changes or refactors don’t introduce new bugs since most paths and lines are tested.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Measuring Code Coverage
&lt;/h2&gt;

&lt;p&gt;Popular tools for measuring code coverage include &lt;strong&gt;Istanbul&lt;/strong&gt; for JavaScript, &lt;strong&gt;Jacoco&lt;/strong&gt; for Java, and &lt;strong&gt;coverage.py&lt;/strong&gt; for Python and PHPUnit (with XDebug, PCOV and phpdbg support) for PHP. These tools integrate with CI/CD pipelines to ensure continuous monitoring of code coverage.&lt;/p&gt;

&lt;p&gt;Here's an example using coverage.py with a Python script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# example.py
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cannot divide by zero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="c1"&gt;# test_example.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divide&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_multiply&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;multiply&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_divide&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# To run the coverage report
# Use the following command:
# coverage run -m pytest
# coverage report
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;coverage&lt;/code&gt; tool will produce a report showing how much of the code has been executed during the tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Differences Between Test Coverage and Code Coverage
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Focus
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Coverage&lt;/strong&gt;: Focuses on the extent to which the testing suite covers the application's functionality, requirements, and conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Coverage&lt;/strong&gt;: Concentrates on the extent to which the actual lines of code have been executed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Measurement
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Coverage&lt;/strong&gt;: Measures how well the tests cover the requirements and functionality of the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Coverage&lt;/strong&gt;: Measures how well the tests execute the code itself, identifying which lines or branches have been run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Purpose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test Coverage&lt;/strong&gt;: Ensures all user scenarios and requirements are tested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Coverage&lt;/strong&gt;: Ensures the written code is exercised and validated, uncovering dead or untested code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using Test Coverage and Code Coverage Together
&lt;/h2&gt;

&lt;p&gt;While both metrics provide valuable insights, relying solely on one can be misleading. High test coverage doesn't guarantee that all the code has been executed, and high code coverage doesn’t mean all functional requirements are tested. Combining both gives a more comprehensive view of your testing effectiveness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Showing Off Coverage
&lt;/h2&gt;

&lt;p&gt;If you run an open source project (or even closed source, I suppose), it is common to show badges on the &lt;code&gt;README.md&lt;/code&gt; file of your project that show off the coverage of your project. Check out the very popular &lt;a href="https://github.com/dwyl/repo-badges"&gt;&lt;code&gt;repo-badges&lt;/code&gt;&lt;/a&gt; GitHub repo for a bunch of examples.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write Tests&lt;/strong&gt;: Write comprehensive tests covering all functional requirements and edge cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure Test Coverage&lt;/strong&gt;: Use tools like pytest or JUnit to measure and report on test coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure Code Coverage&lt;/strong&gt;: Use tools like coverage.py or Istanbul to measure code execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine Reports&lt;/strong&gt;: Analyze combined reports to ensure all aspects are covered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor and Improve&lt;/strong&gt;: Refactor code and tests based on coverage insights to improve overall quality.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;We go into more detail about &lt;a href="https://accreditly.io/articles/whats-the-difference-between-test-coverage-and-code-coverage"&gt;test coverage vs code coverage&lt;/a&gt; on our own article base. Additionally you can take a look at the following articles for some more info.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://martinfowler.com/bliki/TestCoverage.html"&gt;Understanding Test Coverage&lt;/a&gt; by Martin Fowler&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.softwaretestinghelp.com/code-coverage-vs-test-coverage/"&gt;Code Coverage vs Test Coverage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pytest-cov.readthedocs.io/en/latest/"&gt;pytest-cov Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://coverage.readthedocs.io/en/coverage-5.5/"&gt;Coverage.py Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>DRY: What is it and how to implement it. Don't Repeat Yourself</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Tue, 07 May 2024 13:44:35 +0000</pubDate>
      <link>https://forem.com/accreditly/dry-what-is-it-and-how-to-implement-it-dont-repeat-yourself-59fj</link>
      <guid>https://forem.com/accreditly/dry-what-is-it-and-how-to-implement-it-dont-repeat-yourself-59fj</guid>
      <description>&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY (Don't Repeat Yourself) principle&lt;/a&gt; is a key concept in software development that focuses on reducing code duplication and improving maintainability. By following the DRY principle, developers can create cleaner, more efficient, and less error-prone codebases. In this article, we will discuss the importance of the DRY principle and illustrate its application in JavaScript with two practical examples.&lt;/p&gt;

&lt;p&gt;If you're looking for an example of writing DRY code then check out our &lt;a href="https://accreditly.io/articles/how-to-use-tailwinds-safelist-to-handle-dynamic-classes#content-4-more-advanced-configurations"&gt;article about safelist classes in Tailwind&lt;/a&gt;, which rewrites the ruleset to make them DRY.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 1: Understanding the DRY Principle
&lt;/h2&gt;

&lt;p&gt;The DRY principle states that each piece of logic in a codebase should have a single, unambiguous representation. In other words, you should avoid duplicating code and extract repetitive parts into reusable components or functions. This approach ensures that when you need to make changes or fix bugs, you only have to do it in one place, making your code more maintainable and less prone to errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 2: Benefits of the DRY Principle
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Improved maintainability: With less duplicated code, it is easier to understand, modify, and extend your codebase.&lt;/li&gt;
&lt;li&gt;Reduced likelihood of bugs: By centralizing logic, you reduce the chances of introducing inconsistencies and errors when updating your code.&lt;/li&gt;
&lt;li&gt;Enhanced readability: DRY code is generally more concise and easier to read, making it simpler for other developers to understand and work with your code.&lt;/li&gt;
&lt;li&gt;Easier debugging: When logic is centralized, it's simpler to track down and fix issues, as you only need to investigate one location instead of multiple instances of the same logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Section 3: DRY Principle in JavaScript – Example 1
&lt;/h2&gt;

&lt;p&gt;Consider the following code that calculates the price of items after applying a discount:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before applying DRY:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPriceAfterDiscountA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&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;discountA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discountA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPriceAfterDiscountB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&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;discountB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discountB&lt;/span&gt;&lt;span class="p"&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="nf"&gt;getPriceAfterDiscountA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 90&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="nf"&gt;getPriceAfterDiscountB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we have two functions with nearly identical logic, except for the discount values. This code is repetitive and not DRY.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After applying DRY:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPriceAfterDiscountA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&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;discountA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discountA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPriceAfterDiscountB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&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;discountB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;discountB&lt;/span&gt;&lt;span class="p"&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="nf"&gt;getPriceAfterDiscountA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 90&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="nf"&gt;getPriceAfterDiscountB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've extracted the common logic into an &lt;code&gt;applyDiscount()&lt;/code&gt; function, which is now used by both &lt;code&gt;getPriceAfterDiscountA()&lt;/code&gt; and &lt;code&gt;getPriceAfterDiscountB()&lt;/code&gt;. This change makes our code DRY and more maintainable.&lt;/p&gt;

&lt;p&gt;As an of maintainability, imagine we have 10 discounts instead of the 2 above, and we have a change to this code where we want to apply some kind of pre or post-tax discount across all available discounts. Using the original method we would have to make that change in 10 places, but in the DRY code we just change the &lt;code&gt;applyDiscount&lt;/code&gt; function. The extra layer of abstraction will result in less code, less bugs, easier to read code and more maintainable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 4: DRY Principle in JavaScript – Example 2
&lt;/h2&gt;

&lt;p&gt;Let's look at another example where we want to calculate the area of different shapes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before applying DRY:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateCircleArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateRectangleArea&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="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateTriangleArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After applying DRY:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rectangle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;triangle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&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="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="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="nf"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 78.53981633974483&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="nf"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rectangle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 24&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="nf"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;triangle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 10.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we've extracted the logic for calculating the area of different shapes into a single &lt;code&gt;calculateArea()&lt;/code&gt; function. This function takes the shape and its dimensions as arguments and uses a switch statement to determine the appropriate calculation. By doing so, we've made our code DRY and easier to maintain.&lt;/p&gt;

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

&lt;p&gt;Embracing the DRY principle in JavaScript is an essential practice for writing clean, maintainable, and efficient code. By identifying repetitive code patterns and extracting them into reusable functions, you can reduce the likelihood of bugs and make your codebase more readable and easier to understand. Remember to always be on the lookout for opportunities to apply the DRY principle in your projects, as doing so will significantly improve the quality of your code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please note:&lt;/em&gt; This article was originally posted on &lt;a href="https://accreditly.io/articles/dry-what-is-it-and-how-to-implement-it-dont-repeat-yourself"&gt;Accreditly&lt;/a&gt;, the home of web development certifications for &lt;a href="https://accreditly.io/certifications/php-fundamentals"&gt;PHP&lt;/a&gt;, &lt;a href="https://accreditly.io/certifications/javascript-fundamentals"&gt;JavaScript&lt;/a&gt;, &lt;a href="https://accreditly.io/certifications/wordpress-development"&gt;WordPress&lt;/a&gt; and &lt;a href="https://accreditly.io/certifications/database-fundamentals"&gt;Database Design&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Don't nest ternary operators. Please!</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Thu, 25 Jan 2024 13:27:00 +0000</pubDate>
      <link>https://forem.com/accreditly/dont-nest-ternary-operators-please-1g08</link>
      <guid>https://forem.com/accreditly/dont-nest-ternary-operators-please-1g08</guid>
      <description>&lt;p&gt;In JavaScript, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator"&gt;ternary operator&lt;/a&gt; (also known as the "conditional operator") is a concise and handy tool for conditional logic. However, when it comes to nesting these operators, you might be stepping into a tangled web of complexity. Let's discuss why nesting ternaries can be problematic and explore alternative approaches for cleaner code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Understanding Ternary Operators&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A ternary operator in JavaScript is a one-liner alternative to an if-else statement. It takes three operands: a condition, a result upon the condition being true, and a result if it's false.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Syntax:&lt;/strong&gt;&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;condition&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;exprIfTrue&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;exprIfFalse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isAdult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;The Temptation of Nesting Ternaries&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Nesting ternary operators means using one ternary inside another. It's often seen as a shortcut to handle multiple conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example of Nested Ternaries:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senior&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Adult&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Minor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this might seem like an efficient use of space, it introduces several issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Readability Concerns&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Nested ternaries can quickly become hard to read and understand, especially for someone new to the code. What seems straightforward to you now might be a puzzle for future-you or other developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard-to-Read Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&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;isAuthenticated&lt;/span&gt; &lt;span class="p"&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;hasPremium&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome, premium user!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome, user!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Please log in.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;2. Debugging Difficulties&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Debugging nested ternaries can be challenging. Isolating which part of the ternary chain is causing an issue is more complex than debugging a simple if-else statement or a switch case.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Maintainability Issues&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;As your codebase grows and evolves, maintaining nested ternaries becomes problematic. Any modification might require a significant unraveling and reworking of the logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Performance Misconception&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Some developers might think nesting ternaries is faster or more efficient. However, any performance gains are negligible and certainly not worth the trade-off in readability and maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Alternatives to Nested Ternaries&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;1. &lt;strong&gt;Simple If-Else Statements:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;   For clarity and ease of understanding, a traditional if-else structure is often best.&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="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&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;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senior&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Adult&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="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Minor&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="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;2. &lt;strong&gt;Switch Statements:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;   For multiple conditions, a switch statement can be more readable.&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="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senior&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="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Adult&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="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Minor&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="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;3. &lt;strong&gt;Function Abstraction:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;   Encapsulate complex logic in a function for better abstraction and reusability.&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="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&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;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senior&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="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&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;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Adult&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="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Minor&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="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;While ternary operators are a useful feature of JavaScript, nesting them can lead to code that's hard to read, debug, and maintain. Opting for clearer, more straightforward alternatives like if-else statements, switch statements, or function abstraction makes your code more accessible and maintainable. Remember, in programming, clarity should almost always trump cleverness. Production code isn't code golf, shorter isn't always better.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>How to use Generators in JavaScript</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Fri, 10 Nov 2023 11:17:52 +0000</pubDate>
      <link>https://forem.com/accreditly/how-to-use-generators-in-javascript-jol</link>
      <guid>https://forem.com/accreditly/how-to-use-generators-in-javascript-jol</guid>
      <description>&lt;p&gt;JavaScript, a language that has been consistently evolving, introduced a powerful feature in its ES6 (ECMAScript 2015) iteration: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator"&gt;Generators&lt;/a&gt;. While they might seem daunting at first, generators are invaluable tools for handling asynchronous operations and creating custom iterable sequences. Let's unwrap the mystique behind JavaScript generators.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Generators?
&lt;/h2&gt;

&lt;p&gt;Generators are special functions in JavaScript that allow you to yield (or produce) multiple values on a per-request basis. They pause their execution when yielding a value and can resume from where they left off. This "pausing" capability makes generators versatile for many scenarios, particularly asynchronous tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Syntax of Generators
&lt;/h2&gt;

&lt;p&gt;Generators are defined similarly to regular functions but with an asterisk (&lt;code&gt;*&lt;/code&gt;). The &lt;code&gt;yield&lt;/code&gt; keyword is used to produce a sequence of values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;myGenerator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;first value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;second value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;third value&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;h2&gt;
  
  
  Using a Generator
&lt;/h2&gt;

&lt;p&gt;To use a generator, you must first call it, which returns a generator object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;myGenerator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This object follows the iterator protocol and has a &lt;code&gt;next()&lt;/code&gt; method:&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="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// { value: 'first value', done: false }&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;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// { value: 'second value', done: false }&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;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// { value: 'third value', done: false }&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;gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// { value: undefined, done: true }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benefits of Generators
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Unlike traditional functions that might build and return a huge array, generators produce values on the fly. This means you're not storing large data structures in memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Together with Promises, generators offer a smoother way to handle asynchronous operations. This synergy gave birth to async/await, which is essentially syntactic sugar over generators and promises.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Beyond producing a sequence of values, generators can be used to define custom iteration behaviors.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Yielding Other Generators
&lt;/h2&gt;

&lt;p&gt;Generators can yield other generators, making them composable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;generatorA&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A2&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="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;generatorB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;generatorA&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B1&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;genB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generatorB&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="nx"&gt;genB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// { value: 'A1', done: false }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generators and Error Handling
&lt;/h2&gt;

&lt;p&gt;You can handle errors in generators with try-catch blocks. If an error is thrown inside a generator, it will set the &lt;code&gt;done&lt;/code&gt; property of the generator to &lt;code&gt;true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;errorGenerator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all good&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Problem occurred&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;
  
  
  Real-world Use Cases
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Fetching chunks of data lazily, such as paginated API results or reading large files in segments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generating infinite sequences, like an endless series of unique IDs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pausing and resuming functions, allowing for more complex flow control.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Generators offer an alternative and often cleaner approach to handling asynchronous operations and generating sequences in JavaScript. While they've been somewhat overshadowed by the rise of async/await, understanding generators gives a deeper insight into the language's capabilities. With generators in your JS toolkit, you're better equipped to tackle a wider range of programming challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Lazy Loading of Data
&lt;/h3&gt;

&lt;p&gt;Imagine you have an application that needs to load large sets of data from a server, like a list of products in an e-commerce site. Instead of loading all data at once, which can be inefficient and slow, you can use a generator to lazily load the data as needed.&lt;/p&gt;

&lt;p&gt;Scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have an API endpoint that returns a list of products.&lt;/li&gt;
&lt;li&gt;The API supports pagination, allowing you to fetch a limited number of products per request.&lt;/li&gt;
&lt;li&gt;You want to display these products in batches on the user interface, loading more as the user scrolls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JavaScript Generator Implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;dataFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&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;offset&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hasMoreData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMoreData&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?limit=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;offset=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;hasMoreData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Using the generator&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dataFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadMore&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;nextBatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;nextBatch&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update UI with new batch of products&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Initial load&lt;/span&gt;
&lt;span class="nf"&gt;loadMore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Subsequent loads, e.g., triggered by scrolling&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scroll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Load more data when the user scrolls down&lt;/span&gt;
  &lt;span class="nf"&gt;loadMore&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;h3&gt;
  
  
  Example: Multi-Step Form Navigation
&lt;/h3&gt;

&lt;p&gt;In a web application, you might have a multi-step form where the user needs to complete several steps in sequence. Using a generator, you can create a smooth and controlled navigation flow through these steps.&lt;/p&gt;

&lt;p&gt;Scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The application has a form divided into multiple steps (e.g., personal details, address information, payment details).&lt;/li&gt;
&lt;li&gt;The user should be able to move forward and backward between steps.&lt;/li&gt;
&lt;li&gt;The application should keep track of the current step and display it accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JavaScript Generator Implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;formWizard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&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;currentStep&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentStep&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;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currentStep&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;currentStep&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;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currentStep&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;currentStep&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Define the steps of the form&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Step 1: Personal Details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Step 2: Address Information&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Step 3: Payment Details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Create an instance of the form wizard&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wizard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;formWizard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Function to move to the next step&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;nextStep&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;wizard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentStep&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update UI to show the current step&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Function to move to the previous step&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;prevStep&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;wizard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prev&lt;/span&gt;&lt;span class="dl"&gt;'&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentStep&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update UI to show the current step&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Initial step&lt;/span&gt;
&lt;span class="nf"&gt;nextStep&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>DOM traversal in JavaScript</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Mon, 21 Aug 2023 13:39:03 +0000</pubDate>
      <link>https://forem.com/accreditly/dom-traversal-in-javascript-3b00</link>
      <guid>https://forem.com/accreditly/dom-traversal-in-javascript-3b00</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The Document Object Model (DOM) represents the structure of an HTML document. Navigating or "traversing" this structure is a fundamental aspect of web development, enabling developers to select, modify, delete, or add content on a webpage. This comprehensive guide delves deep into the art of DOM traversal using JavaScript, equipping you with a robust toolkit for handling various traversal scenarios.&lt;/p&gt;

&lt;p&gt;We've done a more comprehensive version of this article over on &lt;a href="https://accreditly.io"&gt;Accreditly's website&lt;/a&gt;: &lt;a href="https://accreditly.io/articles/a-guide-to-dom-traversal-in-javascript"&gt;A guide to DOM traversal in JavaScript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An remember, if you're interested in showcasing your JavaScript skills then be sure to check out our &lt;a href="https://accreditly.io/certifications/javascript-fundamentals"&gt;JavaScript Fundamentals certification&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Basic DOM Selectors
&lt;/h2&gt;

&lt;p&gt;Before we jump into traversal, let's review some fundamental DOM selectors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  getElementById(): Returns a reference to the first element with the specified ID.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  getElementsByClassName(): Returns a live HTMLCollection of elements with the given class name.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  getElementsByTagName(): Returns a live HTMLCollection of elements with the given tag name.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paragraphs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  querySelector(): Returns the first element that matches a specified CSS selector.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mainImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.main-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  querySelectorAll(): Returns a static NodeList representing elements that match a specified CSS selector.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ul li&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Parent, Child, and Sibling Relationships
&lt;/h2&gt;

&lt;p&gt;At the core of DOM traversal are the relationships between nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1. Parent Nodes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  parentNode: Returns the parent node of a specified element.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parentOfButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2. Child Nodes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  firstChild &amp;amp; lastChild: Return the first and last child of a node, respectively.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstChildOfDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;firstChild&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;lastChildOfDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;lastChild&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  children: Returns an HTMLCollection of an element's child elements (excludes text and comment nodes).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;divChildren&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  firstElementChild &amp;amp; lastElementChild: Similar to &lt;code&gt;firstChild&lt;/code&gt; and &lt;code&gt;lastChild&lt;/code&gt;, but strictly return element nodes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstElementChildOfDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;firstElementChild&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.3. Sibling Nodes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  nextSibling &amp;amp; previousSibling: Return the next and previous sibling of an element, respectively.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextToButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;nextSibling&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;prevToButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;previousSibling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;  nextElementSibling &amp;amp; previousElementSibling: Similar to &lt;code&gt;nextSibling&lt;/code&gt; and &lt;code&gt;previousSibling&lt;/code&gt;, but strictly for element nodes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextElementToButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;nextElementSibling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Traversal Methods
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1. Node Iteration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  childNodes: Returns a NodeList of child nodes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ul&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;childNodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2. Filtering Elements
&lt;/h3&gt;

&lt;p&gt;Utilize &lt;code&gt;Array.prototype.filter&lt;/code&gt; to filter nodes based on conditions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ul&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;children&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;redItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. DOM Traversal with Events
&lt;/h2&gt;

&lt;p&gt;Combine event listeners with traversal methods to create interactive elements.&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;nextElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextElementSibling&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;nextElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;nextElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Advanced Traversal Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  5.1. Recursive Traversal
&lt;/h3&gt;

&lt;p&gt;Traverse the entire DOM tree recursively. This method is useful when the depth is unknown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;traverseDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&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;child&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;traverseDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;child&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;traverseDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.2. Climbing Up the DOM
&lt;/h3&gt;

&lt;p&gt;In some cases, you may need to find a parent element with a specific selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;findAncestor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)&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;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;el&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;listItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;li&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;containingDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;findAncestor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mastering DOM traversal is paramount for any full-stack or frontend developer. JavaScript provides a plethora of methods and properties to navigate the intricate relationships of the DOM. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>dom</category>
    </item>
    <item>
      <title>Streaming Large Files with PHP to Save Memory</title>
      <dc:creator>Accreditly</dc:creator>
      <pubDate>Sat, 12 Aug 2023 13:24:52 +0000</pubDate>
      <link>https://forem.com/accreditly/streaming-large-files-with-php-to-save-memory-4nmk</link>
      <guid>https://forem.com/accreditly/streaming-large-files-with-php-to-save-memory-4nmk</guid>
      <description>&lt;p&gt;Handling large files is a common task for web developers. However, if not done properly, it can lead to high memory usage and slow performance. In this tutorial, we will look at how to stream large files to the browser in a memory-efficient way using PHP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of PHP&lt;/li&gt;
&lt;li&gt;An active PHP development environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: File Streaming Basics
&lt;/h2&gt;

&lt;p&gt;Before we dive into the code, let's first understand what file streaming is. When you open a file in PHP with &lt;code&gt;fopen()&lt;/code&gt;, you can read it line by line or character by character with &lt;code&gt;fgets()&lt;/code&gt; or &lt;code&gt;fgetc()&lt;/code&gt;, instead of loading the entire file into memory. This is known as file streaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Setting Up the PHP Script
&lt;/h2&gt;

&lt;p&gt;Let's create a new PHP script, &lt;code&gt;download.php&lt;/code&gt;. In this script, we will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the file we want to stream.&lt;/li&gt;
&lt;li&gt;Read a portion of the file and output it to the browser.&lt;/li&gt;
&lt;li&gt;Repeat step 2 until the entire file has been read and sent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'path/to/your/largefile.pdf'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Make sure the file exists&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'File not found.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Set headers to tell the browser to download the file&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type: application/octet-stream'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Disposition: attachment; filename="'&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Length: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Open the file in binary mode&lt;/span&gt;
&lt;span class="nv"&gt;$fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Output the file&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;feof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Read and output a chunk of the file&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Flush the output buffer to free up memory&lt;/span&gt;
    &lt;span class="nb"&gt;ob_flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Close the file&lt;/span&gt;
&lt;span class="nb"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, we use &lt;a href="https://php.net/fopen"&gt;&lt;code&gt;fopen()&lt;/code&gt;&lt;/a&gt; to open the file and &lt;a href="https://php.net/fread"&gt;&lt;code&gt;fread()&lt;/code&gt;&lt;/a&gt; to read a chunk of 8192 bytes at a time (which is approximately 8KB). We then output this chunk using &lt;code&gt;echo&lt;/code&gt; and &lt;code&gt;flush&lt;/code&gt; the output buffer to free up the memory used. This process repeats until the end of the file (&lt;code&gt;feof($fp)&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In this tutorial, you've learned how to stream large files to the browser in a memory-efficient way using PHP. This method is very useful when dealing with large files that could otherwise consume significant server memory and lead to performance issues. Always remember to close any open file handles and flush the output buffer to free up memory.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>files</category>
    </item>
  </channel>
</rss>
