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()],
});
entry
js? – Agony