Export Layer

Static Site Exporter

Source Files: siteExporter.ts, graphData.ts, backlinksWebview.ts Complexity: High

The Static Site Exporter converts your markdown notes into an interactive, beautiful HTML wiki. It packages pages, styling, search capabilities, theme preferences, backlinks, and graph pages into a self-contained _site/ directory ready for deployment.

1. Overview

Markdown notes are highly portable, but sharing an entire folder vault with non-developers or hosting it on the web can be difficult. The static site exporter solves this by packaging your vault into a modern, static web application:

  • Converts Markdown: Translates markdown notes into fully responsive HTML files.
  • Backlinks & TOC: Dynamically lists backlinks and compiles a Table of Contents (TOC) for every note.
  • Fuzzy Search: Generates a clientside search index (search-index.json) for real-time text and tag search.
  • Interactive Preview: Generates preview metadata (preview-data.json) to display hover popovers (showing note summaries/snippets) when hovering over internal wiki links.
  • D3 Interactive Graph: Generates a dedicated knowledge graph page identical to the VS Code extension view.

2. Under the Hood & Code Walkthrough

2.1 Crawling and Asset Packaging

When triggered, the exporter deletes the existing _site/ folder, recreates the output directories, copies pre-packaged CSS (prism, styling) and JS (D3, graph rendering, search, hover previews, theme switching) from extension resources, parses markdown files, and generates tag maps:

export async function exportSite(workspaceRoot: string, extensionPath: string): Promise<string> {
    const siteDir = path.join(workspaceRoot, '_site');

    if (fs.existsSync(siteDir)) {
        await fsp.rm(siteDir, { recursive: true });
    }
    fs.mkdirSync(path.join(siteDir, 'notes'), { recursive: true });
    fs.mkdirSync(path.join(siteDir, 'tags'), { recursive: true });
    fs.mkdirSync(path.join(siteDir, 'css'), { recursive: true });
    fs.mkdirSync(path.join(siteDir, 'js'), { recursive: true });

    // Copy CSS & JS assets from extension resources ...
    
    const notes = await gatherSiteNotes(workspaceRoot);
    const allTags = collectTags(notes);

    // Resolve backlinks
    for (const note of notes) {
        note.backlinks = findBacklinks(note, notes);
    }
    
    // Render markdown and write pages ...
}

2.2 Generating Search and Preview Indices

To power interactive search and link hovers in the browser, the exporter outputs two JSON index files at the root of the site:

// 1. Generate search-index.json for fuzzy search queries
const searchIndex = notes.map(n => ({
    title: n.slug,
    tags: n.tags,
    summary: n.summary,
    content: n.content.slice(0, 500),
    url: `notes/${n.slug}.html`
}));
await fsp.writeFile(path.join(siteDir, 'search-index.json'), JSON.stringify(searchIndex), 'utf8');

// 2. Generate preview-data.json for popup preview cards
const previewData = {};
for (const n of notes) {
    previewData[n.slug] = {
        title: n.slug,
        summary: n.summary,
        snippet: n.htmlContent.replace(/<[^>]+>/g, '').slice(0, 200)
    };
}
await fsp.writeFile(path.join(siteDir, 'preview-data.json'), JSON.stringify(previewData), 'utf8');