How to include HTML partials using Vite?
Asked Answered
C

3

16

Is it possible to include snippets of shared HTML using Vite (vanilla)? I'm looking for a way to have the HTML prerendered without injecting via JS.

Something like:

<html>
  <head>
    { include 'meta-tags' }
  </head>
  <body> 
    { include 'nav' }
    <h1>Hello World</h1>
  <body>
</html>
Chirpy answered 23/1, 2022 at 1:44 Comment(0)
C
27

vite-plugin-handlebars was the solution I was looking for. Partials were super easy to set up with this package:

Setup:

// vite.config.js
import { resolve } from 'path';
import handlebars from 'vite-plugin-handlebars';

export default {
  plugins: [
    handlebars({
      partialDirectory: resolve(__dirname, 'partials'),
    }),
  ],
};

File where you want to include partial:

<!-- index.html -->
{{> header }}

<h1>The Main Page</h1>

Rendered output:

<header><a href="/">My Website</a></header>

<h1>The Main Page</h1>
Chirpy answered 31/1, 2022 at 0:4 Comment(0)
C
2

You could use the vite-plugin-html that enables EJS templates in index.html:

// vite.config.js
import { defineConfig } from 'vite'
import { createHtmlPlugin } from 'vite-plugin-html'

export default defineConfig({
  plugins: [
    createHtmlPlugin({
      entry: 'main.js',

      /**
       * Data that needs to be injected into the index.html ejs template
       */
      inject: {
        data: {
          metaTags: `<meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />`,
          nav: `<nav>
            <a href="https://google.com">Google</a> | 
            <a href="https://apple.com">Apple</a>
          </nav>`,
        },
      },
    }),
  ],
})
<!-- index.html -->
<html>
  <head>
    <%- metaTags %>
  </head>
  <body>
    <%- nav %>
    <h1>Hello World</h1>
  <body>
</html>

demo

Crisp answered 29/1, 2022 at 6:15 Comment(1)
why does it need a entry js?Agony
M
1

i made a simple Vite plugin to handle this case, its allows nested <include src="./your-html-fragment.html" /> inside your template and allows to use variables inside them, in this case it will be <include src="./your-html-fragment.html" locals="{ "key": "value", "another-key": "another-value" }" /> (variables inside fragment should look like {{ key }})

import { defineConfig } from "vite";
import path from "path";
import fs from "fs";

function renderTemplate(template, locals) {
  const result = template.replace(/{{\s*([^}\s]+)\s*}}/g, (match, key) => {
    return locals[key] !== undefined ? locals[key] : "";
  });
  return result;
}

function parseLocals(localsString) {
  if (!localsString) {
    return {};
  }
  try {
    const trimmedString = localsString.replace(/^\s*|\s*$/g, "");
    const strippedString = trimmedString.replace(/^'([\s\S]*)'$/, "$1");
    const result = JSON.parse(strippedString);
    return result;
  } catch (error) {
    console.error("Error parsing locals:", error);
    console.error("Problematic string:", localsString);
    return {};
  }
}

function processIncludes(html, parentDir, parentLocals = {}) {
  const includeRegex =
    /<include\s+src="(.+?)"(?:\s+locals='([\s\S]*?)')?(?:\s+locals="([\s\S]*?)")?\s*><\/include>/g;

  let match;
  let newHtml = html;

  while ((match = includeRegex.exec(newHtml)) !== null) {
    const [includeTag, src, singleQuoteLocals, doubleQuoteLocals] = match;
    const filePath = path.resolve(parentDir, src);

    let content = "";
    try {
      content = fs.readFileSync(filePath, "utf-8");
    } catch (err) {
      console.error(`Error reading file: ${filePath}`, err);
      continue;
    }

    let locals = { ...parentLocals };
    const localsString = singleQuoteLocals || doubleQuoteLocals;
    if (localsString) {
      const parsedLocals = parseLocals(localsString);
      locals = { ...locals, ...parsedLocals };
    }

    content = renderTemplate(content, locals);
    content = processIncludes(content, path.dirname(filePath), locals);
    newHtml = newHtml.replace(includeTag, content);
  }
  return newHtml;
}

function htmlIncludePlugin() {
  return {
    name: "html-include-plugin",
    transformIndexHtml(html, { filename }) {
      const result = processIncludes(html, path.dirname(filename));
      return result;
    },
  };
}

export default defineConfig({
  root: "./src",
  build: {
    outDir: "../dist",
    emptyOutDir: true,
  },
  plugins: [htmlIncludePlugin()],
});
Magel answered 11/7, 2024 at 13:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.