Return to Selected Works
web/Modular Web Reader

Digital Library

“Building an accessible digital reading experience with modular ES6 modules and declarative component injection—no build tools.”

A modular, zero-framework web reader and catalog platform featuring declarative HTML component injection and client-side search.

Role
Frontend Architect
Context
3 Weeks (Semester 1 Web Project)
Team
Solo Project
Core Stack
HTML5, CSS Grid, Vanilla JS (ES6 Modules), Declarative Injection
Digital Library

Fig 1.0 — Architecture execution snapshot (Digital Library)

The Friction

Why build a multi-page digital library without a frontend framework or bundler?

Modern web development often introduces immense overhead for simple multi-page applications: megabytes of node_modules, webpack or vite build configurations, and complex hydration logic just to display static books and commentaries.

I wanted to build an elegant, fast-loading digital library using only vanilla web standards. The challenge was maintaining code modularity—sharing navigation bars, footers, and modals across dozens of book pages without copying and pasting HTML, and without relying on server-side rendering or heavy JavaScript single-page application frameworks.

Deliberate Constraints

The system architecture was not chosen in an unconstrained vacuum. Each structural decision emerged directly from four non-negotiable technical boundaries.

[ZERO FRAMEWORKS OR BUNDLERS]

No React, Vue, Webpack, or npm build pipelines. 100% native browser execution.

Architectural Outcome

Required native ES6 module imports (<script type='module'>) and custom declarative HTML injection.

[DECLARATIVE COMPONENT REUSE]

Navigation, footers, and modal dialogs must be reusable without duplicating markup across pages.

Architectural Outcome

Engineered a custom data-include engine that fetches and injects HTML component fragments asynchronously during DOMContentLoaded.

[DISTRACTION-FREE TYPOGRAPHIC RHYTHM]

Extended reading requires comfortable line measure, accessible contrast, and zero layout shift.

Architectural Outcome

Designed a modular CSS hierarchy with strict 65–75 character measure and persistent dark/light theme switching via localStorage.

[INSTANT IN-MEMORY CATALOG SEARCH]

Filtering hundreds of book entries must happen instantly without server queries or external search engines.

Architectural Outcome

Built an in-memory search index that parses structured JSON records and updates DOM nodes with minimal reflow.

System Architecture & Data Pipeline

The application architecture is partitioned into two distinct JavaScript layers: a core foundation (/assets/js/core/) handling data fetching, declarative HTML component injection, and theme state; and a feature layer (/assets/js/features/) managing search, book grids, and modal previews.

Runtime Dispatch via Virtual Method Table (vtable)
<<Abstract Base>> VehicleInclude/Vehicle.h
- vehicleID: string | model: string | rentalRate: float
- status: VehicleStatus (Available | Rented | Sold)
+ virtual ~Vehicle(); // Mandatory for polymorphic delete
+ virtual calculateCost(int days) = 0;
+ virtual getCategory() const = 0;
EconomyIDs 3000s

Alto, Cultus, Corolla. Standard tiered rental base.

calcCost: days * baseRate
LuxuryIDs 4000s

Audi A6, BMW 7, Land Cruiser. Chauffeur insurance rate.

calcCost: days * baseRate * 1.25
SUVIDs 5000s

Sportage, Tucson, Fortuner. All-terrain security deposit.

calcCost: days * baseRate + terrainFee
VanIDs 6000s

Bolan, Hiace, Coaster. High-capacity commercial rate.

calcCost: days * baseRate (cap > 15)

Dynamic Polymorphism at Runtime: The orchestrator holds a single container std::vector<Vehicle*> fleet. When executing reservations or computing quotes, method calls to v->calculateCost(days) dynamically dispatch to the concrete subclass implementation through each instance's vtable pointer.

Subsystem Decomposition

Declarative HTML Inclusion Engine

assets/js/core/include.js

Scans DOM for elements with data-include attributes and injects shared HTML snippets.

Impl: Fetches HTML partials (components/navbar.html, components/footer.html) and swaps outerHTML asynchronously.

Central Data Caching Layer

assets/js/core/data.js

Loads and caches book catalog records from assets/data/books.json.

Impl: Maintains an in-memory cache to prevent redundant HTTP fetch requests across view transitions.

Real-Time Search & Filter Engine

assets/js/features/search.js

Filters the active catalog by title, author, and category with debounced keystroke listeners.

Impl: Applies substring matching across cached book entities and re-renders catalog grid cards in real time.

Persistent Theme & Modal Manager

assets/js/core/theme.js & book.js

Manages dark/light theme persistence in localStorage and orchestrates 'Quick Peek' book preview modals.

Impl: Toggles data-theme attributes on documentElement and manages focus trapping during modal displays.

The Hard Part: Asynchronous HTML Injection & Event Binding Race Conditions

How injecting HTML components dynamically breaks traditional DOM event listeners.

When breaking down pages to use reusable navigation and search bars, page-specific feature scripts (like search.js and theme.js) began throwing 'null element' errors on cold loads.

Because fetch() for data-include snippets is asynchronous, feature scripts running on DOMContentLoaded were attempting to attach event listeners to elements (such as the mobile menu toggle or search input) that had not yet been injected into the DOM tree.

assets/js/app.js & core/include.js
javascript
// core/include.js - Asynchronous declarative injection
export async function loadComponents() {
  const elements = document.querySelectorAll('[data-include]');
  const promises = Array.from(elements).map(async (el) => {
    const file = el.getAttribute('data-include');
    const response = await fetch(file);
    const html = await response.text();
    el.outerHTML = html;
  });

  // CRITICAL: Await all component injections before resolving
  await Promise.all(promises);
}

// app.js - Orchestrated Bootstrap
document.addEventListener('DOMContentLoaded', async () => {
  // 1. First await complete component injection
  await loadComponents();

  // 2. Safely initialize features now that DOM nodes exist
  initTheme();
  initSearch();
  initBooksGrid();
});
Using Promise.all() guarantees that all declarative HTML components are inserted before feature event handlers bind.
The Technical Resolution

We redesigned include.js to return a unified Promise.all() containing all injection fetch operations, and re-architected app.js as an asynchronous lifecycle bootstrapper that halts feature initialization until all injected DOM fragments are settled.

What the System Taught Me

Understanding the browser event loop and asynchronous DOM mutations is fundamental. When you don't have a virtual DOM framework to hide the timing, you learn how the browser actually schedules layout, script execution, and rendering.

Lighthouse Performance & Asset Audit

Audit results demonstrating sub-second load times and zero render-blocking JavaScript dependencies.

hmsaeed@taxila: ~/projects/vms (x86_64-gcc)
C++17
$lighthouse https://hmslibrary.netlify.app --view
[LIGHTHOUSE] Running audit against https://hmslibrary.netlify.app...
[METRIC] First Contentful Paint (FCP): 0.6s
[METRIC] Largest Contentful Paint (LCP): 0.8s
[METRIC] Total Blocking Time (TBT): 0ms
[METRIC] Cumulative Layout Shift (CLS): 0.002
[SCORE] Performance: 99/100
[SCORE] Accessibility: 100/100
[SCORE] Best Practices: 100/100
===========================================================
Audit Result: Near-instant static delivery with zero framework bundle weight.
$

Engineering Reflection

“Simplicity in web architecture is not the absence of ambition; it is the discipline to use the platform as it was intended.”

In the modern frontend landscape, developers frequently default to multi-megabyte toolchains before writing a single line of application logic. We create build problems, and then invent complex tools to solve the build problems.

Building this digital library proved that standard HTML, modular CSS, and modern ES6 modules provide everything needed for a fast, responsive, and maintainable web application.

The site loads in a fraction of a second, requires no compilation step, and can be hosted on any static web server in the world without maintenance.

Interested in discussing this architecture?

I'm always open to technical dialogue, code reviews, and exploring system constraints.

Start a Technical Conversation→