Last active
October 29, 2024 23:13
-
-
Save duttonw/a2d6223b6d6ca2b41295b75c811f64d0 to your computer and use it in GitHub Desktop.
Embed Svg's for Handlebars (with build embed as well as dynamic on template compile) for us in (Vite or esbuild) build systems.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Handlebars from "handlebars"; | |
/** | |
* Embeds an SVG file into the template | |
* | |
* Do note: rendering when inside handlebars will not be relative to a imported template file. | |
* | |
* It has two modes, browser mode and node mode. | |
* When in browser mode, it will place a random id to be filled in when the file is successfully collected | |
* | |
* | |
* @param filePath | |
* @param options | |
* @returns {Handlebars.SafeString|string} | |
*/ | |
export default function( filePath, options) { | |
//console.log(filePath) | |
if (typeof window === 'undefined') { | |
// Node.js environment | |
const fs = require('fs'); | |
const path = require('path'); | |
try { | |
const fullPath = path.resolve(filePath); | |
const svgContent = fs.readFileSync(fullPath, 'utf8'); | |
return new Handlebars.SafeString(svgContent); | |
} catch (error) { | |
console.error(`Error reading SVG file: ${filePath}`, error); | |
throw error; | |
} | |
} else { | |
// Browser environment | |
// Using a placeholder while we fetch the content later | |
const id = `svg-${Math.random().toString(36).substr(2, 9)}`; | |
fetch(filePath) | |
.then(response => { | |
if (!response.ok) { | |
throw new Error(`Failed to fetch SVG: ${response.statusText}, ${filePath}`); | |
} | |
return response.text(); | |
}) | |
.then(svgContent => { | |
// Insert the SVG content into the DOM after fetching | |
const element = document.getElementById(id); | |
if (element) { | |
element.innerHTML = svgContent; | |
} | |
}) | |
.catch(error => { | |
console.error(`Error fetching SVG file: ${filePath}`, error); | |
}); | |
// Return a placeholder div with a unique ID | |
return new Handlebars.SafeString(`<div id="${id}">Loading SVG...</div>`); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from 'fs'; | |
import path from 'path'; | |
/** | |
* This file contains a plugin for Vite and esbuild that embeds SVG files into Handlebars templates. | |
* | |
* The format it is looking for is {{ embedSvgs "./image.svg" }} | |
* The regex is: | |
* esbuild: {{\s*embedSvg\s*"([^"]+)"\s*}} | |
* vite : {{\s*embedSvg\s*\\"([^"]+)\\"\s*}} <-- Vite auto escapes double quotes | |
* | |
* The plugin reads the SVG file specified in the embedSvg tag and replaces the tag with the content of the SVG file. | |
* | |
* The file path is relative to the Handlebars file that contains the embedSvg tag. | |
* | |
* There is also the [embedSvg](./src/helpers/Handlebars/embedSvg.js) helper function. | |
* Do note: rendering when inside handlebars can't relative reference the imported template file. | |
*/ | |
/** | |
* Escapes backslashes, single quotes, and double quotes in a string for JavaScript. | |
* @param content | |
* @returns {*} | |
*/ | |
function escapeForJavaScript(content) { | |
// Escape backslashes, single quotes, and double quotes | |
return content | |
.replace(/\\/g, '\\\\') | |
.replace(/'/g, "\\'") | |
.replace(/"/g, '\\"') | |
.replace(/\r?\n/g, '\\n'); // Escape newlines for JavaScript strings | |
} | |
export function viteHandlebarsEmbedSvgPlugin() { | |
return { | |
name: 'vite-embed-svg', | |
enforce: 'pre', // Ensure this plugin runs before other transforms | |
transform(code, id) { | |
if (!id.endsWith('.js') && !id.endsWith('.hbs?raw') && !id.endsWith('.hbs')) { | |
return null; // Only process .hbs files | |
} | |
// if (id.endsWith("/header/html/component.hbs?raw" || id.endsWith("handlebars.partials.js"))) { | |
// console.error(id) | |
// //console.error(code) | |
// } | |
let changed = false; | |
// Regex pattern to match {{embedSvg "file.svg"}} | |
const svgEmbedPattern = /{{\s*embedSvg\s*\\"([^"]+)\\"\s*}}/g; | |
// Replace {{embedSvg "file.svg"}} with the content of the SVG | |
const transformedCode = code.replace(svgEmbedPattern, (match, filePath) => { | |
console.error("found embedSvg file:" + path.resolve(path.dirname(id), filePath) + " in file:" + id) | |
changed = true | |
try { | |
const svgPath = path.resolve(path.dirname(id), filePath); | |
const svgContent = fs.readFileSync(svgPath, 'utf8'); | |
return escapeForJavaScript(svgContent); | |
} catch (error) { | |
console.error(`Error embedding SVG for ${filePath}:`, error); | |
return match; // Leave the original tag if there's an error | |
} | |
}); | |
if (changed) { | |
//console.error("returning changed") | |
// Return the transformed code | |
return transformedCode | |
} else { | |
// no change | |
return null; // Only process .hbs files | |
} | |
}, | |
}; | |
} | |
export function esBuildHandlebarsEmbedSvgPlugin() { | |
return { | |
name: 'embed-svg', | |
setup(build) { | |
build.onLoad({ filter: /\.hbs$/ }, async (args) => { | |
let contents = await fs.promises.readFile(args.path, 'utf8'); | |
if (args.path.endsWith("/header/html/component.hbs" || args.path.endsWith("handlebars.partials.js"))) { | |
console.error(args.path) | |
//console.error(contents) | |
} | |
// Regex pattern to match {{embedSvg "file.svg"}} | |
const svgEmbedPattern = /{{\s*embedSvg\s*"([^"]+)"\s*}}/g; | |
// Replace {{embedSvg "file.svg"}} with the content of the SVG | |
contents = contents.replace(svgEmbedPattern, (match, filePath) => { | |
//console.error(match); | |
//console.error(filePath); | |
try { | |
const dirPath = path.dirname(args.path) | |
const svgPath = path.resolve(path.dirname(args.path), filePath); | |
//console.error(dirPath ); | |
//console.error(svgPath ); | |
const svgContent = fs.readFileSync(svgPath, 'utf8'); | |
return svgContent; | |
} catch (error) { | |
console.error(`Error embedding SVG: ${filePath} in file: ${args.path}`); | |
throw error; | |
//return match; // Leave the original tag if there's an error | |
} | |
}); | |
// Return the modified contents to esbuild | |
return { | |
contents, | |
loader: 'text', | |
}; | |
}); | |
}, | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//.storybook/main.js | |
const { mergeConfig } = require('vite'); | |
const customViteConfig = require('../vite.config.js'); // Adjust the path as needed | |
/** @type { import('@storybook/html-vite').StorybookConfig } */ | |
const config = { | |
...all other config goes here | |
framework: { | |
//Build the storybook with html-vite rendered - faster than webpack | |
//https://www.npmjs.com/package/@storybook/html-vite | |
name: "@storybook/html-vite", | |
options: {}, | |
}, | |
// https://storybook.js.org/docs/api/main-config-vite-final | |
// Use the Vite configuration from the main project (yes this is a esbuild project but storybook uses vite) | |
async viteFinal(config) { | |
// Merge custom Vite configuration | |
return mergeConfig(config, customViteConfig); | |
}, | |
}; | |
export default config; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import embedSvg from "./Handlebars/embedSvg.js"; | |
export default function handlebarsHelpersRollup(handlebars) { | |
handlebars.registerHelper("embedSvg", embedSvg); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { defineConfig } from 'vite'; | |
import { viteHandlebarsEmbedSvgPlugin } from './handlebarsEmbedSvgPlugin.js' | |
export default defineConfig({ | |
root: './dist', | |
plugins: [ | |
{ | |
name: "html-transform", | |
transform(src, id) { | |
if (id.endsWith(".mustache") || id.endsWith(".html") || id.endsWith(".hbs")) { | |
// Transform your HTML files here (src is the file content as a string) | |
return src; | |
} | |
}, | |
}, | |
viteHandlebarsEmbedSvgPlugin() | |
], | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment