Skip to content

Instantly share code, notes, and snippets.

@duttonw
Last active October 29, 2024 23:13
Show Gist options
  • Select an option

  • Save duttonw/a2d6223b6d6ca2b41295b75c811f64d0 to your computer and use it in GitHub Desktop.

Select an option

Save duttonw/a2d6223b6d6ca2b41295b75c811f64d0 to your computer and use it in GitHub Desktop.

Revisions

  1. duttonw renamed this gist Oct 29, 2024. 1 changed file with 0 additions and 0 deletions.
  2. duttonw renamed this gist Oct 29, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. duttonw revised this gist Oct 29, 2024. 2 changed files with 56 additions and 25 deletions.
    25 changes: 0 additions & 25 deletions #.storybook__main.js
    Original file line number Diff line number Diff line change
    @@ -1,25 +0,0 @@
    //.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;
    56 changes: 56 additions & 0 deletions embedSvg.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,56 @@
    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>`);
    }
    };
  4. duttonw revised this gist Oct 29, 2024. 2 changed files with 25 additions and 56 deletions.
    25 changes: 25 additions & 0 deletions .storybook__main.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,25 @@
    //.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;
    56 changes: 0 additions & 56 deletions embedSvg.js
    Original file line number Diff line number Diff line change
    @@ -1,56 +0,0 @@
    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>`);
    }
    };
  5. duttonw renamed this gist Oct 29, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. duttonw revised this gist Oct 29, 2024. No changes.
  7. duttonw created this gist Oct 29, 2024.
    25 changes: 25 additions & 0 deletions .storybook__main.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,25 @@
    //.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;
    56 changes: 56 additions & 0 deletions embedSvg.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,56 @@
    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>`);
    }
    };
    124 changes: 124 additions & 0 deletions handlebarsEmbedSvgPlugin.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,124 @@
    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',
    };
    });
    },
    };
    }
    5 changes: 5 additions & 0 deletions helpers.rollup.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,5 @@
    import embedSvg from "./Handlebars/embedSvg.js";

    export default function handlebarsHelpersRollup(handlebars) {
    handlebars.registerHelper("embedSvg", embedSvg);
    }
    19 changes: 19 additions & 0 deletions vite.config.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    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()
    ],
    });