open-quran-view

Font Loading Strategy

Guide for understanding font loading in open-quran-view, including the v0.2.0+ static assets system.


Table of Contents

  1. Overview
  2. The Problem
  3. Solution: Static Assets (v0.2.0+)
  4. How It Works
  5. Data Generation
  6. Runtime Resolution
  7. Migration Guide
  8. Related Documentation

Overview

open-quran-view uses a static assets system (v0.2.0+) to reliably load fonts and data files. This system pre-generates import.meta.url based URLs at build time, eliminating runtime path resolution issues.

Key Benefits


The Problem

Legacy Approach (Before v0.2.0)

The original implementation constructed URLs at runtime:

// ❌ Problematic approach (pre-v0.2.0)
const fontUrl = new URL("../data/fonts/hafs-v2/p1.woff2", import.meta.url).href;
const response = await fetch(fontUrl);

Why It Fails

Scenario Behavior Result
workspace: protocol Vite intercepts requests during dev ✅ Works locally
Published to npm Files in node_modules are inaccessible ❌ Fails
file:// URLs Browsers block HTTP requests to local files ❌ Fails

Error Observed

Failed to load page: NetworkError: A network error occurred.

This occurred because browsers cannot make fetch() requests to file:// URLs from node_modules.


Solution: Static Assets (v0.2.0+)

The v0.2.0 release introduced a static assets system that pre-generates all asset URLs at build time.

How It Works

┌─────────────────────────────────────────────────────────────────┐
│                    Build Time (generate-static-*)               │
│                                                                 │
│  1. Scan src/data/fonts/ for all font files                    │
│  2. Scan src/data/pages/ and src/data/metadata/                │
│  3. Generate TypeScript files with static URL constants         │
│  4. Output to src/core/static/                                  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Runtime (import.meta.url)                    │
│                                                                 │
│  1. Consumer imports static assets                              │
│  2. import.meta.url is resolved by the bundler                  │
│  3. fetch() receives correct URL                                │
└─────────────────────────────────────────────────────────────────┘

How It Works

1. Static Font URLs

Generated file: src/core/static/fonts.ts

export const staticFonts = {
  "hafs-v2": {
    1: new URL("../../data/fonts/hafs-v2/p1.woff2", import.meta.url).href,
    2: new URL("../../data/fonts/hafs-v2/p2.woff2", import.meta.url).href,
    // ... 604 entries
  },
  "hafs-v4": { /* ... */ },
  "hafs-unicode": { /* ... */ },
} as const;

export function getFontUrl(layout: MushafLayout, page: number): string {
  const pageStr = String(page);
  return staticFonts[layout]?.[pageStr];
}

2. Static Data URLs

Generated file: src/core/static/data.ts

export const staticData = {
  pages: {
    "hafs-v2": new URL("../../data/pages/hafs-v2/pages.json", import.meta.url).href,
    "hafs-v4": new URL("../../data/pages/hafs-v4/pages.json", import.meta.url).href,
    "hafs-unicode": new URL("../../data/pages/hafs-unicode/pages.json", import.meta.url).href,
  },
  metadata: {
    surahs: new URL("../../data/metadata/surahs.json", import.meta.url).href,
    juz: new URL("../../data/metadata/juz.json", import.meta.url).href,
  },
} as const;

export function getPagesUrl(layout: string): string {
  return staticData.pages[layout];
}

export function getMetadataUrl(type: "surahs" | "juz"): string {
  return staticData.metadata[type];
}

Data Generation

Static assets are generated by running the generation scripts:

Individual Scripts

# Generate static font URLs
yarn generate:static:fonts

# Generate static data URLs
yarn generate:static:data

Combined

# Generate both
yarn generate:static

# Or generate everything (metadata, pages, fonts, static assets)
yarn generate:all

Build Integration

The prepare script in package.json runs automatically:

{
  "scripts": {
    "prepare": "yarn generate:all && yarn build"
  }
}

Runtime Resolution

How import.meta.url Works

When the static assets are imported at runtime:

// In the consumer's application
import { getFontUrl } from 'open-quran-view/core/static/fonts';

const fontUrl = getFontUrl('hafs-v2', 1);
// Returns something like:
// - Vite dev:   http://localhost:5173/node_modules/open-quran-view/dist/data/fonts/hafs-v2/p1.woff2
// - Production: /node_modules/open-quran-view/dist/data/fonts/hafs-v2/p1.woff2

The bundler (Vite, webpack, etc.) correctly resolves import.meta.url because the URLs were pre-generated with the correct relative paths.

Build Output

After running yarn build, the static assets are included in the package:

dist/
├── core/
│   └── static/
│       ├── fonts.js    # Static font URLs
│       └── data.js     # Static data URLs
├── data/
│   ├── fonts/
│   │   └── hafs-v2/p1.woff2...
│   └── pages/
│       └── hafs-v2/pages.json
└── view/
    ├── react/index.js
    └── web/index.js

Migration Guide

For Consumers (Using open-quran-view)

No special configuration needed:

npm install open-quran-view
import { OpenQuranView } from 'open-quran-view/view';

function App() {
  return <OpenQuranView page={1} mushafLayout="hafs-v2" />;
}

Before v0.2.0 (Legacy)

Required manual setup with Vite plugin (see [archived documentation]).

For Contributors

If you modify data files, regenerate static assets:

# After modifying src/data/fonts/ or src/data/pages/
yarn generate:static
yarn build