Create Your Own Vite Plugin

  • javascript
  • linkedom
  • plugin development
  • typescript
  • vite
Jul 7, 2024
Dive into the world of Vite plugins with LinkeDOM, and elevate your web development projects by crafting your own tools.

Introduction

What if you could tailor your development environment to fit your exact needs? Enter Vite plugins—a gateway to customizing and enhancing your build process. In this post, we’ll explore how to craft a Vite plugin using LinkeDOM, empowering you to reuse HTML partials effortlessly in your web projects.

Understanding Vite plugins not only demystifies modern development tooling but also equips you with the skills to innovate within frameworks like SvelteKit, Nuxt, Astro, Remix, and Solid Start.

Tutorial Start: Create a New Project

Begin by setting up a fresh Vite project. Open your terminal and navigate to your desired directory, then run:

npm create vite@latest

Choose the following options for this tutorial:

  • Framework: Vanilla JS
  • Language: TypeScript

To start with a clean slate, remove unnecessary files:

# Delete unnecessary files
rm src/style.css src/counter.ts
# Clear main.ts content
echo "" > src/main.ts

Install Required Dependencies

We’ll need TypeScript types for NodeJS and LinkeDOM for HTML parsing and manipulation on the server side.

npm install -D @types/node
npm install linkedom

Configure Vite

Create a vite.config.ts file in the root directory. For simplicity, we’ll author the plugin inline.

We’ll utilize the transformIndexHtml hook to access and modify the index.html string. Explore other available hooks in the Vite documentation to expand your plugin’s capabilities.

// vite.config.ts
import { defineConfig } from "vite";

export default defineConfig({
    plugins: [
        {
            name: "html-partials",
            transformIndexHtml: {
                order: "pre",
                async handler(html) {
                    console.log(html);
                },
            },
        },
    ],
});

Start the development server to see the hook in action:

npm run dev

Check your console to see the index.html content logged.

Parse and Modify HTML with LinkeDOM

LinkeDOM allows us to use familiar DOM methods on the server side. Let’s enhance our plugin to manipulate the HTML.

// vite.config.ts
import { parseHTML } from "linkedom";
import { defineConfig } from "vite";

export default defineConfig({
    plugins: [
        {
            name: "html-partials",
            transformIndexHtml: {
                order: "pre",
                async handler(html) {
                    // Parse the HTML
                    const dom = parseHTML(html);

                    // Append a welcome message
                    dom.document.body.append("Hello world!");

                    // Return the modified HTML
                    return dom.document.toString();
                },
            },
        },
    ],
});

Reload your app, and you should see “Hello world!” appended to the body.

Create a Partial

Let’s take component reuse to the next level by creating HTML partials. These are reusable snippets that can be inserted into your HTML, complete with scripts and styles.

Consider the following structure:

.
└── src/
    └── partials/
        └── my-button.html

my-button.html contains:

<!-- src/partials/my-button.html -->

<script type="module">
    // @ts-check

    customElements.define(
        "my-button",
        class extends HTMLElement {
            trigger = this.querySelector("button");

            connectedCallback() {
                if (this.trigger) {
                    this.trigger.addEventListener("click", () => alert("Alert!"));
                }
            }
        },
    );
</script>

<button><slot></slot></button>

<style>
    my-button button {
        border: none;
        background-color: darkblue;
        padding: 0.5rem 0.75rem;
        color: white;
    }
</style>

Develop the Plugin

Our goal is to read these partials and inject their content into the main HTML. Here’s how to achieve this:

  1. Read Partial Files: Fetch all HTML partials from the src/partials directory.
  2. Parse and Inject: Use LinkeDOM to parse each partial and inject its scripts, styles, and content into the main DOM.
// vite.config.ts
import { parseHTML } from "linkedom";
import fs from "node:fs/promises";
import { defineConfig } from "vite";

export default defineConfig({
    plugins: [
        {
            name: "html-partials",
            transformIndexHtml: {
                order: "pre",
                async handler(html) {
                    const dom = parseHTML(html);
                    const partialFileNames = await fs.readdir("src/partials");

                    for (const fileName of partialFileNames) {
                        const name = fileName.split(".").at(0);
                        const content = await fs.readFile(`src/partials/${fileName}`, "utf-8");

                        if (name && content) {
                            const partialDom = parseHTML(content);
                            const scriptsAndStyles = partialDom.document.querySelectorAll("script, style");

                            for (const el of scriptsAndStyles) {
                                dom.document.head.append(el.cloneNode(true));
                                el.remove();
                            }

                            const elements = dom.document.querySelectorAll(name);
                            for (const el of elements) {
                                const slot = partialDom.document.querySelector("slot");

                                if (slot?.parentElement) {
                                    slot.parentElement.innerHTML = el.innerHTML;
                                }

                                el.innerHTML = partialDom.document.toString();
                            }
                        }
                    }

                    return dom.document.toString();
                },
            },
        },
    ],
});

This comprehensive plugin reads each partial, injects necessary scripts and styles into the <head>, and replaces custom elements with the processed content.

Testing the Plugin

Update your index.html to include the new partial:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <link rel="icon" type="image/svg+xml" href="/vite.svg" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Vite + TS</title>
    </head>
    <body>
        <!-- Insert custom button -->
        <my-button>Alert</my-button>

        <script type="module" src="/src/main.ts"></script>
    </body>
</html>

Restart your development server:

npm run dev

Open your browser and inspect the <head> to see the injected scripts and styles. Clicking the “Alert” button should trigger an alert, confirming that your partials are functioning as intended.

Finally, build your project to ensure everything compiles correctly:

npm run build

Check the dist/ directory to see your processed HTML and bundled JavaScript.

Conclusion

Creating a Vite plugin empowers you to customize and optimize your development workflow. By integrating LinkeDOM, we’ve built a plugin that not only reuses HTML partials but also deepens our understanding of Vite’s plugin ecosystem. This foundation opens the door to more sophisticated plugins and innovative development strategies.

Explore more Vite plugin options and hooks and discover a community bustling with first-party and third-party plugins in the Vite documentation.

Thanks for reading and happy coding!