Skip to content

A powerful and flexible Vue library for rendering arbitrary HTML content with two distinct rendering modes: Direct Mode (with script execution) and Shadow Mode (with style isolation)

Notifications You must be signed in to change notification settings

ddavid93/vue-html-renderer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

VueHTMLRenderer

A powerful and flexible Vue library for rendering arbitrary HTML content with two distinct rendering modes: Direct Mode (with script execution) and Shadow Mode (with style isolation). Compatible with Vue 2.7+ and Vue 3.

⚠️ SECURITY WARNING
This library does NOT sanitize or validate HTML content. If you render HTML containing malicious scripts in Direct Mode, those scripts WILL execute. Always sanitize untrusted HTML content before passing it to this component, or use Shadow Mode (which disables script execution) when rendering content from untrusted sources.

πŸ“‹ Table of Contents


🎯 Overview

This library provides a unified solution for rendering HTML content in Vue applications (Vue 2.7+ and Vue 3) with full control over rendering behavior. It addresses common challenges when working with dynamic HTML content, such as:

  • Script Execution: Execute embedded JavaScript with proper browser-like semantics
  • Style Isolation: Prevent CSS conflicts using Shadow DOM
  • Font Loading: Proper @font-face handling in Shadow DOM
  • HTML Structure Preservation: Maintain complete HTML structure including <html>, <head>, and <body> tags

🚫 Why Not iFrame?

You might wonder: "Why not just use an <iframe>?" Here are the key reasons:

Problems with iFrame:

  1. Manual Size Management

    • iFrames require explicit width and height
    • Content doesn't naturally flow with the parent layout
    • Responsive sizing requires complex JavaScript solutions
  2. Complex Security Configuration

    • Sandbox flags must be manually configured
    • Easy to misconfigure and create security vulnerabilities
    • Different browsers have different default behaviors
  3. Communication Overhead

    • Parent-child communication requires postMessage API
    • Complex bidirectional data flow
    • Difficult to share state or context
  4. Performance Impact

    • Each iframe creates a complete browser context
    • Higher memory usage
    • Slower initial load times
  5. SEO and Accessibility Issues

    • Search engines may not index iframe content properly
    • Screen readers may have difficulty navigating
    • URL management is more complex

Advantages of This Library:

βœ… Automatic Layout Integration: Content flows naturally with the parent document
βœ… Smart Script Handling: Controlled execution with proper async/defer/sequential semantics
βœ… Efficient Style Isolation: Shadow DOM provides isolation without the overhead
βœ… Better Performance: Lower memory footprint, faster rendering
βœ… Seamless Integration: Direct access to Vue context and reactivity
βœ… Font Loading: Automatic handling of @font-face declarations in Shadow DOM


✨ Features

Direct Mode (isShadow=false)

  • βœ… Full script execution support
  • βœ… Async, defer, and sequential script handling
  • βœ… Module script support (type="module")
  • βœ… Inline and external scripts
  • βœ… Browser-like execution semantics
  • βœ… No style isolation (uses parent styles)

Shadow Mode (isShadow=true)

  • βœ… Complete style isolation using Shadow DOM
  • βœ… Preserves full HTML structure (<html>, <head>, <body>)
  • βœ… Automatic @font-face extraction and injection
  • βœ… No script execution (by design, for security)
  • βœ… Perfect for rendering formatted documents
  • βœ… CSS encapsulation (no style leakage)

Common Features

  • βœ… Vue Composition API (Vue 2.7+ and Vue 3)
  • βœ… Full TypeScript support
  • βœ… Comprehensive documentation
  • βœ… Custom Element compatibility
  • βœ… Clean lifecycle management
  • βœ… Framework-agnostic utilities

πŸ—οΈ Architecture

The library is organized by responsibility for easy maintenance and potential library distribution:

vue-html-renderer/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ App.vue                    # Main Vue component
β”‚   β”œβ”€β”€ main.ts                    # Library entry point (exports)
β”‚   β”œβ”€β”€ extras/
β”‚   β”‚   β”œβ”€β”€ types.ts               # TypeScript type definitions
β”‚   β”‚   └── utils.ts               # Shared utility functions
β”‚   β”œβ”€β”€ composables/
β”‚   β”‚   └── useHtmlRenderer.ts     # Composable (internal use)
β”‚   └── renderers/
β”‚       β”œβ”€β”€ shadowRenderer.ts      # Shadow DOM rendering logic
β”‚       └── directRenderer.ts      # Direct rendering with script execution
└── README.md                      # This file

Design Principles

  1. Separation of Concerns: Each module has a single, well-defined responsibility
  2. Type Safety: Full TypeScript coverage with comprehensive type definitions
  3. Extensibility: Easy to add new rendering modes or features
  4. Reusability: Framework-agnostic utilities can be used outside Vue
  5. Documentation: Every function and type is thoroughly documented

πŸ“¦ Installation

import HtmlRenderer from '@/components/htmlRenderer/HtmlRenderer.vue'
// or
import { useHtmlRenderer } from '@/components/htmlRenderer/composables/useHtmlRenderer'

As a Standalone Library (Future)

For distribution as a standalone npm package:

npm install @your-org/html-renderer
# or
yarn add @your-org/html-renderer

πŸš€ Usage

Basic Component Usage

Direct Mode (with script execution)

<template>
  <HtmlRenderer :html="htmlContent" />
</template>

<script setup lang="ts">
import HtmlRenderer from '@/components/htmlRenderer/HtmlRenderer.vue';

const htmlContent = `
  <div>
    <h1>Hello World</h1>
    <script>
      console.log('This script will execute!');
    </script>
  </div>
`;
</script>

Shadow Mode (with style isolation)

<template>
  <HtmlRenderer :html="htmlContent" :is-shadow="true" />
</template>

<script setup lang="ts">
import HtmlRenderer from '@/components/htmlRenderer/HtmlRenderer.vue'

const htmlContent = `
  <!doctype html>
  <html>
    <head>
      <style>
        body { background: #f0f0f0; font-family: Arial; }
        h1 { color: blue; }
      </style>
    </head>
    <body>
      <h1>Isolated Styles</h1>
      <p>These styles won't affect the parent document!</p>
    </body>
  </html>
`
</script>

Composable Usage

<template>
  <div ref="hostRef"></div>
</template>

<script setup lang="ts">
import { useHtmlRenderer } from '@/components/htmlRenderer/composables/useHtmlRenderer'

const { hostRef, clear } = useHtmlRenderer({
  html: '<div>Content</div>',
  isShadow: false,
})

// Manually clear content if needed
// clear();
</script>

πŸ“š API Reference

Component: HtmlRenderer

Props

Prop Type Required Default Description
html string Yes - The HTML string to render
isShadow boolean No false Whether to use Shadow DOM mode

Example

<HtmlRenderer :html="myHtmlString" :is-shadow="true" />

Composable: useHtmlRenderer

Parameters

interface IHtmlRendererOptions {
  html: string // The HTML string to render
  isShadow?: boolean // Whether to use Shadow DOM mode (default: false)
}

Returns

interface IHtmlRendererComposable {
  hostRef: Ref<HTMLElement | undefined> // Template ref for the host element
  clear: () => void // Function to clear rendered content
  shadowRoot?: Ref<ShadowRoot | undefined> // Shadow root ref (shadow mode only)
}

Example

const { hostRef, clear, shadowRoot } = useHtmlRenderer({
  html: '<div>Content</div>',
  isShadow: true,
})

Utility Functions

From utils.ts

// Generate a unique ID
function uid(): string

// Normalize HTML (handle escaping/encoding)
function normalizeHtml(raw: string): string

// Normalize attribute values
function normalizeAttr(val: string): string

// Find placeholder comment node
function findPlaceholderNode(root: ParentNode, id: string): Comment | null

Type Definitions

See types.ts for complete type definitions:

  • IHtmlRendererOptions
  • IHtmlRendererComposable
  • IHtmlRendererProps
  • IScriptMeta
  • RenderMode
  • IFontFaceExtractionOptions

βš–οΈ Rendering Modes Comparison

Feature Direct Mode Shadow Mode
Script Execution βœ… Yes (full support) ❌ No (by design)
Style Isolation ❌ No (uses parent styles) βœ… Yes (complete isolation)
HTML Structure Partial βœ… Complete (<html>, <body>, <head>)
Font Loading βœ… Automatic βœ… Automatic (@font-face injection)
Performance Fast Very Fast
Security Requires trust in HTML source Higher (no scripts)
Use Cases Interactive content, widgets Documents, formatted content

When to Use Direct Mode

  • βœ… You need JavaScript to execute
  • βœ… You trust the HTML source
  • βœ… You want interactive content
  • βœ… You need to share parent document styles
  • βœ… You're rendering dynamic widgets

When to Use Shadow Mode

  • βœ… You need style isolation
  • βœ… You don't need scripts
  • βœ… You're rendering formatted documents (coupons, certificates, vouchers)
  • βœ… You want to prevent CSS conflicts
  • βœ… You need to preserve complete HTML structure
  • βœ… Security is a priority (no script execution)

πŸ’‘ Examples

Example 1: Rendering a Coupon (Shadow Mode)

<template>
  <HtmlRenderer :html="couponHtml" :is-shadow="true" />
</template>

<script setup lang="ts">
import HtmlRenderer from '@/components/htmlRenderer/HtmlRenderer.vue'

const couponHtml = `
  <!doctype html>
  <html>
    <head>
      <style>
        @font-face {
          font-family: 'CustomFont';
          src: url('https://example.com/font.woff2') format('woff2');
        }
        body {
          font-family: 'CustomFont', sans-serif;
          background: white;
          width: 18cm;
          height: 26.7cm;
        }
        .coupon-title {
          font-size: 24pt;
          color: #333;
          text-align: center;
        }
      </style>
    </head>
    <body>
      <div class="coupon-title">$50 Gift Certificate</div>
      <p>Valid until: 2025-12-31</p>
    </body>
  </html>
`
</script>

Example 2: Interactive Widget (Direct Mode)

<template>
  <HtmlRenderer :html="widgetHtml" :is-shadow="false" />
</template>

<script setup lang="ts">
import HtmlRenderer from '@/components/htmlRenderer/HtmlRenderer.vue';

const widgetHtml = `
  <div id="widget">
    <button id="clickMe">Click Me</button>
    <span id="counter">0</span>
  </div>
  <script>
    let count = 0;
    document.getElementById('clickMe').addEventListener('click', () => {
      count++;
      document.getElementById('counter').textContent = count;
    });
  </script>
`;
</script>

Example 3: Loading External Scripts

<template>
  <HtmlRenderer :html="scriptHtml" :is-shadow="false" />
</template>

<script setup lang="ts">
const scriptHtml = `
  <div id="map"></div>
  <script src="https://cdn.example.com/map-library.js" defer></script>
  <script defer>
    // This runs after map-library.js loads
    initMap('map');
  </script>
`;
</script>

🎯 Best Practices

Security

  1. Always sanitize untrusted HTML before rendering in direct mode
  2. Use shadow mode for content from untrusted sources (no script execution)
  3. Validate external script sources when using direct mode
  4. Be cautious with inline event handlers (onclick, etc.)

Performance

  1. Avoid re-rendering - The component renders once on mount
  2. Use keys if you need to force re-rendering with different content
  3. Minimize HTML size for faster parsing
  4. Consider lazy loading for heavy content

Styling

  1. Shadow mode: Include all styles in the HTML string (they won't leak)
  2. Direct mode: Be aware of style inheritance from parent document
  3. Use scoped styles in parent components to avoid conflicts
  4. Test font loading in shadow mode (fonts are automatically injected)

Script Execution (Direct Mode)

  1. Understand execution order: Sequential β†’ Async (fire-and-forget) β†’ Defer
  2. Use defer for scripts that need DOM to be ready
  3. Use async for independent scripts
  4. Module scripts (type="module") are always deferred by default

🀝 Contributing

When contributing to this library, please follow these guidelines:

  1. Maintain separation of concerns: Keep renderers, composables, and utilities separate
  2. Document everything: All functions, types, and modules should have JSDoc comments
  3. Write tests: Add tests for new features or bug fixes
  4. Follow TypeScript best practices: Use strict typing, avoid any
  5. Update this README: Keep documentation in sync with code changes

πŸ“„ License

Free to use.


πŸ™ Acknowledgments

This library was built to solve real-world challenges in rendering dynamic HTML content in Vue applications (Vue 2.7+ and Vue 3), specifically for rendering formatted documents like coupons and vouchers with proper style isolation and font loading.


Built with ❀️ for Vue developers (Vue 2.7+ and Vue 3) who need powerful HTML rendering capabilities.

About

A powerful and flexible Vue library for rendering arbitrary HTML content with two distinct rendering modes: Direct Mode (with script execution) and Shadow Mode (with style isolation)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published