In this article, we'll take a deep dive into how Obsidian Publish renders Markdown files and resolves cross-links between pages. We'll break down the main components and classes involved, and look at the process step-by-step. We'll cover topics like Markdown rendering, resolving internal links, loading site data, and explain how the NavigationView component works. This article aims to provide a structural and technical overview of how Obsidian Publish works under the hood.
The Obsidian Publish website source code contains the following modules:
The Publish class initializes and manages the entire site. It loads the site info, options, and cache data. It contains properties for rendering Markdown content, navigation, search, graph view, and outline view.
It handles tasks like:
class Publish {
constructor() {
this.render = new MarkdownRenderer(this)
}
navigate(filepath, subpath) {
// Loads and renders the Markdown file
}
onResize() {
// Handles resize events
}
setTheme(theme) {
// Applies a theme
}
applyCss(css) {
// Applies CSS styles
}
registerMarkdownPostProcessor(processor, sortOrder) {
// Registers a post-processor for Markdown content
}
}
This class renders Markdown content, handles scrolling, load external embeds, etc.
class MarkdownRenderer {
loadFile(filepath) {
// Loads a Markdown file and renders it
}
onScroll() {
// Handles scroll events
}
loadEmbed(src) {
// Loads external embeds like images, audio, videos etc.
}
}
Rendering Markdown in Obsidian Publish works like this:
this.site.loadMarkdownFile(filepath)
this.renderer.set(markdownContent)
The Markdown renderer class then parses the Markdown and renders it into DOM nodes.
After rendering, a series of post-processors are run on the DOM nodes to:
Once post-processing is complete, the rendered DOM nodes are injected into the DOM.
The MarkdownRenderer then handles scrolling, resize events, external embeds, etc. for that rendered Markdown content.
One of the most interesting part of obsidian is it's cross-links logics. Here is how it works when page render.
function resolveInternalLinks(el) {
const links = el.findAll('a.internal-link');
for (const link of links) {
const href = link.getAttr("data-href");
const { path, subpath } = parseURL(href);
// Resolve the link destination
const dest = site.cache.getLinkpathDest(path);
// Set the resolved link href
if (dest) {
link.setAttr("href", site.getPublicHref(dest) + subpath);
}
}
}
<a>
href to the public URL of that destination page.So in summary, cross-links are resolved by:
<a>
href to that URLThis allows links in your Markdown to just use "linkpath" references, and Obsidian Publish figures out the actual destination and public URL at render time.
This class represents the actual site data, including options, cache, etc.
class PublishSite {
loadCache() {
// Loads the site cache data
}
loadOptions() {
// Loads the site options
}
getConfig(optionName) {
// Gets a site option
}
getInternalUrl(filepath) {
// Gets the internal URL for a file
}
}
The site data contains all the information needed to render an Obsidian Publish site. It includes:
The Publish code fetches this data when initializing the site. Then it uses that data to:
So the site data contains all the information Obsidian Publish needs to fully render and configure the site. The Publish code handles fetching this data when initializing and uses it throughout the rendering process.
The most important parts of site data are:
The NavigationView component is used to render the site navigation. It shows a tree-based view of the site files and folders. Its main purposes are:
So in summary, its main purpose is to provide the site navigation by:
The code also contains components like:
Which are used to render the corresponding UI parts on the site.