markdown-rambler
Version:
Yet another opinionated & powerful static site generator.
885 lines (861 loc) • 34 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
MarkdownRambler: () => MarkdownRambler,
addScript: () => addScript,
append: () => append,
findElement: () => findElement,
html: () => html,
insertBefore: () => insertBefore,
insertBeforeStylesheets: () => insertBeforeStylesheets
});
module.exports = __toCommonJS(src_exports);
// src/util/at.ts
if (!("at" in Array.prototype)) {
Array.prototype.at = function(index) {
if (index >= 0)
return this[index];
return this[this.length + index];
};
}
// src/MarkdownRambler.ts
var import_os2 = require("os");
var import_util2 = require("util");
var import_node_path3 = require("path");
var import_globby = require("globby");
var import_lodash2 = __toESM(require("lodash"), 1);
var import_unified = require("unified");
var import_to_vfile = require("to-vfile");
var import_vfile_matter = require("vfile-matter");
var import_vfile_rename = require("vfile-rename");
var import_vfile_mkdirp = require("vfile-mkdirp");
var import_remark_rehype = __toESM(require("remark-rehype"), 1);
var import_rehype_stringify2 = __toESM(require("rehype-stringify"), 1);
var import_xast_util_feed = require("xast-util-feed");
var import_xast_util_to_xml = require("xast-util-to-xml");
var import_minisearch = __toESM(require("minisearch"), 1);
// src/mdast/parsers.ts
var import_remark_parse = __toESM(require("remark-parse"), 1);
var import_remark_frontmatter = __toESM(require("remark-frontmatter"), 1);
var import_remark_directive = __toESM(require("remark-directive"), 1);
var import_hastscript = require("hastscript");
var import_unist_util_visit = require("unist-util-visit");
// src/mdast/table.ts
var import_micromark_extension_gfm_table = require("micromark-extension-gfm-table");
var import_mdast_util_gfm_table = require("mdast-util-gfm-table");
function table() {
const data = this.data();
add("micromarkExtensions", import_micromark_extension_gfm_table.gfmTable);
add("fromMarkdownExtensions", import_mdast_util_gfm_table.gfmTableFromMarkdown);
add("toMarkdownExtensions", import_mdast_util_gfm_table.gfmTableToMarkdown);
function add(field, value) {
data[field] = data[field] || [];
data[field].push(value);
}
}
// src/mdast/parsers.ts
var parsers_default = [import_remark_parse.default, import_remark_frontmatter.default, table, import_remark_directive.default];
var transformDirectives = (directives2) => (tree, vFile) => {
const visitor = (node, index, parent) => {
if (node.type === "textDirective" || node.type === "leafDirective" || node.type === "containerDirective") {
const previousSibling = node.type === "textDirective" && parent.children[index - 1];
if (node.type === "textDirective" && previousSibling && !previousSibling.value.endsWith(" ")) {
node.type = "text";
node.value = `:${node.name}`;
return;
}
if (node.name in directives2) {
const hast = directives2[node.name](node, index, parent, vFile);
const data = node.data || (node.data = {});
data.hName = hast.tagName;
data.hProperties = hast.properties;
data.hChildren = hast.children;
} else {
const hast = (0, import_hastscript.h)(node.name, node.attributes);
const data = node.data || (node.data = {});
data.hName = hast.tagName;
data.hProperties = hast.properties;
}
}
};
(0, import_unist_util_visit.visit)(tree, visitor);
};
// src/mdast/formatters.ts
var import_remark_prettier = __toESM(require("remark-prettier"), 1);
var import_remark_reference_links = __toESM(require("remark-reference-links"), 1);
// src/unist/order-links.ts
var import_unist_util_visit2 = require("unist-util-visit");
var import_unist_util_is = require("unist-util-is");
var import_unist_util_remove = require("unist-util-remove");
function orderDefinitions() {
return transformer;
}
function transformer(tree) {
let id = 0;
const refs = [];
const defs = [];
const store = [];
(0, import_unist_util_visit2.visit)(tree, ["linkReference", "imageReference", "definition"], (node) => {
if ((0, import_unist_util_is.is)(node, ["linkReference", "imageReference"]))
refs.push(node);
if ((0, import_unist_util_is.is)(node, "definition"))
defs.push(node);
});
refs.forEach((ref) => {
const def = defs.find((d) => d.identifier === ref.identifier);
const reusableDef = store.find((d) => d.url === def.url);
if (reusableDef) {
ref.identifier = reusableDef.identifier;
ref.label = reusableDef.identifier;
} else {
const identifier = isNaN(ref.identifier) ? ref.identifier : String(++id);
ref.identifier = identifier;
ref.label = identifier;
store.push({
type: "definition",
title: def.title,
url: def.url,
identifier,
label: identifier
});
}
});
(0, import_unist_util_remove.remove)(tree, "definition");
store.sort(definitionSorter).forEach((def) => {
tree.children.push(def);
});
}
function definitionSorter(a, b) {
if (isNaN(a.identifier) && !isNaN(b.identifier))
return 1;
if (!isNaN(a.identifier) && isNaN(b.identifier))
return -1;
if (!isNaN(b.identifier) && !isNaN(a.identifier))
return Number(a.identifier) - Number(b.identifier);
return a.identifier - b.identifier;
}
var order_links_default = orderDefinitions;
// src/mdast/formatters.ts
var formatters_default = [import_remark_prettier.default, import_remark_reference_links.default, order_links_default];
// src/mdast/helpers.ts
var import_unist_util_visit3 = require("unist-util-visit");
var getDocumentTitle = (tree) => {
let title = "";
(0, import_unist_util_visit3.visit)(tree, "heading", (node) => {
if (node.depth === 1) {
title = node.children[0].value;
return false;
}
});
return title;
};
var removeDocumentTitle = () => (tree) => {
(0, import_unist_util_visit3.visit)(tree, "heading", (node, index, parent) => {
if (node.depth === 1) {
parent == null ? void 0 : parent.children.splice(index, 1);
return false;
}
});
};
// src/hast/transformers.ts
var import_node_path2 = require("path");
var import_rehype_urls = __toESM(require("rehype-urls"), 1);
var import_rehype_slug = __toESM(require("rehype-slug"), 1);
var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1);
var import_rehype_document = __toESM(require("rehype-document"), 1);
// src/hast/plugins.ts
var import_hastscript2 = require("hastscript");
// src/hast/traversal.ts
var import_unist_util_visit4 = require("unist-util-visit");
var findElement = (tree, test, callback) => {
const matcher = typeof test === "function" ? test : (node) => node.tagName === test;
(0, import_unist_util_visit4.visit)(tree, "element", (node, index, parent) => {
if (matcher(node) && parent && index !== null) {
return callback(node, index, parent);
}
});
};
var append = (tree, test, ...elements) => {
findElement(tree, test, (node) => {
node.children.push(...elements);
return false;
});
return tree;
};
var insertBefore = (tree, test, element) => {
findElement(tree, test, (node, index, parent) => {
parent == null ? void 0 : parent.children.splice(index, 0, element);
return false;
});
return tree;
};
var insertBeforeStylesheets = (tree, element) => {
const test = (node) => {
var _a;
const rel = [(_a = node == null ? void 0 : node.properties) == null ? void 0 : _a.rel].flat();
return node.tagName === "link" && rel.includes("stylesheet");
};
return insertBefore(tree, test, element);
};
// src/hast/plugins.ts
var addInlineScript = ({ type, content }) => (tree) => {
const script = (0, import_hastscript2.h)("script", { type }, content);
return append(tree, "body", script);
};
var addScript = ({ src }) => (tree) => {
const script = (0, import_hastscript2.h)("script", { src });
return append(tree, "body", script);
};
// src/util/index.ts
var import_path = require("path");
var groupByType = (vFiles) => vFiles.reduce((group, vFile) => {
const { type } = vFile.data.meta;
group[type] = group[type] ?? [];
group[type].push(vFile);
return group;
}, {});
var getDefaults = (type, defaults) => {
if (!type || type === "page" || !(type in defaults)) {
return (defaults == null ? void 0 : defaults.page) ?? {};
}
return { ...defaults.page, ...defaults[type] };
};
var resolveFrontMatter = (matter2, vFile) => {
const obj = {};
for (const key in matter2) {
switch (key) {
case "tags":
const tags = typeof matter2[key] === "string" ? matter2[key].split(/[ ,]+/) : matter2[key];
obj[key] = tags;
break;
case "image":
const image = typeof matter2[key] === "string" ? { src: matter2[key] } : matter2[key];
image.src = (0, import_path.isAbsolute)(image.src) ? image.src : (0, import_path.join)(vFile.data.pathname, image.src);
obj[key] = image;
break;
case "published":
case "modified":
obj[key] = new Date(matter2[key]);
break;
default:
obj[key] = matter2[key];
break;
}
}
return obj;
};
var buildMetaData = (vFile, type, options) => {
const defaults = getDefaults(type, options.defaults);
const { pathname } = vFile.data;
const base = {
type,
host: options.host,
pathname,
href: options.host + pathname,
name: options.name,
language: options.language,
manifest: options.manifest,
feed: options.feed
};
const matter2 = resolveFrontMatter(vFile.data.matter, vFile);
const pageStylesheets = typeof matter2.stylesheets === "string" ? [matter2.stylesheets] : matter2.stylesheets ?? [];
const pageScripts = typeof matter2.scripts === "string" ? [matter2.scripts] : matter2.scripts ?? [];
delete matter2.stylesheets;
delete matter2.scripts;
return Object.assign(base, defaults, matter2, {
pageStylesheets: pageStylesheets.map((sheet) => (0, import_path.join)(pathname, sheet)),
pageScripts: pageScripts.map((script) => (0, import_path.join)(pathname, script))
});
};
var iso = (value) => !value ? void 0 : (typeof value === "string" ? new Date(value) : value).toISOString();
var unique = (value, index, self) => self.indexOf(value) === index;
var ucFirst = (value) => value[0].toUpperCase() + value.slice(1);
// src/hast/metaTags.ts
var getMetaTags = (meta) => {
var _a;
const tags = [];
const type = meta.type === "article" ? "article" : "website";
tags.push({ property: "og:type", content: type });
if (meta.draft)
tags.push({ name: "robots", content: "noindex" });
if (meta.description)
tags.push({ name: "description", property: "og:description", content: meta.description });
if (meta.name)
tags.push({ property: "og:site_name", content: meta.name });
if (meta.href)
tags.push({ property: "og:url", content: meta.href });
if (meta.author)
tags.push({ name: "author", content: meta.author.name });
if (meta.published)
tags.push({ property: "article:published_time", content: iso(meta.published) });
if (meta.modified)
tags.push({ property: "article:modified_time", content: iso(meta.modified) });
if ((_a = meta.author) == null ? void 0 : _a.twitter) {
tags.push(
{ name: "twitter:title", property: "og:title", content: meta.title },
{ name: "twitter:description", content: meta.description },
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:image", property: "og:image", content: meta.host + meta.image.src },
{ name: "twitter:site", content: meta.author.twitter },
{ name: "twitter:creator", content: meta.author.twitter },
{ name: "twitter:image:alt", content: meta.title }
);
}
return tags;
};
// src/hast/linkTags.ts
var import_path2 = require("path");
var mimeTypes = {
".ico": "image/vnd.microsoft.icon",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".png": "image/png",
".svg": "image/svg+xml",
".webp": "image/webp"
};
var getLinkTags = (meta) => {
const tags = [
{ rel: "icon", href: "/favicon.ico", sizes: "any" },
{ rel: "apple-touch-icon", href: "/apple-touch-icon.png" }
];
if (meta.manifest)
tags.push({ rel: "manifest", href: meta.manifest });
if (meta.host && meta.href)
tags.push({ rel: "canonical", href: meta.href });
const stylesheets = meta.bundledStylesheets ?? meta.stylesheets;
if (stylesheets)
tags.push(...stylesheets.map((href) => ({ rel: "stylesheet", href })));
if (meta.pageStylesheets)
tags.push(...meta.pageStylesheets.map((href) => ({ rel: "stylesheet", href })));
if (meta.icon) {
const ext = (0, import_path2.extname)(meta.icon.src);
const type = mimeTypes[ext];
tags.push({ rel: "icon", href: meta.icon.src, type });
}
if (meta.prefetch) {
tags.push({ rel: "prefetch", href: meta.prefetch });
}
if (meta.feed) {
tags.push({
rel: "alternate",
type: "application/rss+xml",
href: meta.host + meta.feed.pathname,
title: meta.feed.title
});
}
return tags;
};
// src/util/fs.ts
var import_os = require("os");
var import_fs = require("fs");
var import_promises = require("fs/promises");
var import_node_path = require("path");
var import_svgo = require("svgo");
var isFloating = (pathname) => !/^[\.\/]/.test(pathname);
var isIndex = (filename) => /(README|index)\.md$/.test(filename);
var resolveTargetPathname = (from, to) => {
const fromPathname = resolvePathname(from);
const [, filename, suffix] = to.match(/(?<filename>[^\?#]*)(?<suffix>.*)/);
const toPathname = (0, import_node_path.resolve)(fromPathname, ...[isIndex(from) ? "." : ".."], resolvePathname(filename));
return toPathname + suffix;
};
var resolvePathname = (filename) => {
return (isFloating(filename) ? "/" : "") + filename.replace(/(README|index)?\.md$/, "").replace(/(.+)\/$/, "$1");
};
var ensureDir = async (target) => {
const dir = (0, import_node_path.dirname)(target);
try {
await (0, import_promises.access)(dir, import_fs.constants.W_OK);
} catch (error) {
await (0, import_promises.mkdir)(dir, { recursive: true });
}
};
var isDir = async (dir) => {
try {
await (0, import_promises.access)(dir, import_fs.constants.W_OK);
return true;
} catch (error) {
return false;
}
};
var write = async (target, output) => {
await ensureDir(target);
(0, import_promises.writeFile)(target, output);
};
var copy = async (source, target) => {
await ensureDir(target);
(0, import_promises.copyFile)(source, target);
};
var append2 = async (source, target) => {
await (0, import_promises.appendFile)(target, await (0, import_promises.readFile)(source) + import_os.EOL);
};
var optimizeSVG = async (source, target) => {
const optimized = (0, import_svgo.optimize)(await (0, import_promises.readFile)(source), {
path: source,
plugins: ["removeDimensions", { name: "removeAttrs", params: { attrs: "content" } }]
});
await ensureDir(target);
await (0, import_promises.writeFile)(target, optimized.data, "utf8");
};
var isInIgnoreDir = (file, ignorePatterns) => {
return ignorePatterns.some((dir) => {
if (dir instanceof RegExp)
return dir.test(file);
if (typeof dir === "string")
return new RegExp(dir).test(file);
if (typeof dir === "function")
return dir(file);
throw new Error(`Invalid ignoreDir value (${dir})`);
});
};
var watchDir = async ({ dir, cb, ignorePatterns }) => {
const callback = (eventType, filename) => {
const file = (0, import_node_path.join)(dir, filename);
if (isInIgnoreDir(file, ignorePatterns))
return;
if (eventType === "change") {
if (filename) {
cb([dir, filename]);
} else {
console.warn("Filename not provided for", eventType, filename);
}
}
};
if (await isDir(dir)) {
(0, import_fs.watch)(dir, { recursive: true }, callback);
return dir;
}
};
// src/hast/transformers.ts
var fixRelativeLinks = (vFile) => (url, node) => {
const { href, pathname } = url;
if (href.startsWith(".") && ((0, import_node_path2.extname)(pathname) === ".md" || node.tagName === "img")) {
return resolveTargetPathname(vFile.history.at(0), href);
}
return url;
};
var defaultTransformers = (vFile) => {
const { data } = vFile;
const { meta, structuredContent } = data;
const { title, language, scripts, bundledScripts, pageScripts } = meta;
const js = [...bundledScripts ?? scripts ?? [], ...pageScripts ?? []];
return [
[() => (0, import_rehype_urls.default)(fixRelativeLinks(vFile))],
// Hack to allow rehype-urls plugin again downstream
import_rehype_slug.default,
[import_rehype_autolink_headings.default, { behavior: "wrap", test: ["h2", "h3", "h4", "h5", "h6"] }],
[import_rehype_document.default, { title, language, meta: getMetaTags(meta), link: getLinkTags(meta), js }],
[addInlineScript, { type: "application/ld+json", content: JSON.stringify(structuredContent) }]
];
};
var transformers_default = defaultTransformers;
// src/hast/render.ts
var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
var import_rehype_format = __toESM(require("rehype-format"), 1);
var render_default = [import_rehype_format.default, import_rehype_stringify.default];
// src/util/structured-content.ts
var import_lodash = __toESM(require("lodash"), 1);
var getPage = (meta, base) => import_lodash.default.merge(
{},
base,
{
"@type": "WebSite",
sameAs: meta.sameAs
},
meta.structuredContent
);
var getArticle = (meta, base) => {
var _a, _b, _c;
const image = ((_a = meta.image) == null ? void 0 : _a.src) || ((_c = (_b = meta.publisher) == null ? void 0 : _b.logo) == null ? void 0 : _c.src);
return import_lodash.default.merge(
{},
base,
{
"@type": "Article",
headline: meta.title,
description: meta.description
},
image ? { image } : {},
meta.structuredContent
);
};
var getStructuredContent = (meta) => {
var _a, _b, _c;
const base = {
"@context": "https://schema.org",
"@type": "WebSite",
mainEntityOfPage: { "@type": "WebPage", "@id": meta.href },
datePublished: iso(meta.published),
dateModified: iso(meta.modified),
inLanguage: meta.language
};
if (((_a = meta.tags) == null ? void 0 : _a.length) > 0) {
base.keywords = meta.tags.join(",");
}
if (meta.author) {
base.author = { "@type": "Person", name: meta.author.name, url: meta.author.href };
}
if (meta.publisher) {
base.publisher = {
"@type": "Organization",
"@id": `${meta.publisher.href}/#organization`,
name: meta.publisher.name,
logo: { "@type": "ImageObject", url: meta.publisher.logo.src }
};
}
const type = ((_c = (_b = meta.structuredContent) == null ? void 0 : _b["@type"]) == null ? void 0 : _c.toLowerCase()) ?? meta.type;
switch (type) {
case "article":
return getArticle(meta, base);
default:
return getPage(meta, base);
}
};
// src/hast/layout.ts
var import_htm = __toESM(require("htm"), 1);
var import_hastscript3 = require("hastscript");
var html = import_htm.default.bind(import_hastscript3.h);
var layout = (options) => (node, vFile) => {
const children = options.layout(node.children, vFile.data.meta);
return { ...node, children: Array.isArray(children) ? children : [children] };
};
// src/MarkdownRambler.ts
var debug = (0, import_util2.debuglog)("markdown-rambler");
var dbg = (vFile, text) => {
const filename = typeof vFile === "string" ? vFile : vFile.history.at(0);
const padded = filename.length > 28 ? `...${filename.slice(-25)}` : filename.padStart(28);
debug(`[${padded}] ${text}`);
};
var MarkdownRambler = class {
constructor(options = {}) {
this.options = Object.freeze(this.setDefaultOptions(options));
}
setDefaultOptions(options) {
return import_lodash2.default.defaultsDeep(options, {
contentDir: !options.contentFiles ? "content" : ".",
contentFiles: "**/*",
publicDir: "public",
outputDir: "dist",
ignorePattern: /^(\.|node_modules)/,
host: "",
name: "",
language: "en",
manifest: false,
sitemap: true,
feed: false,
search: false
});
}
getTransformers(vFile) {
const { rehypePlugins = [] } = this.options;
const plugins = [
typeof transformers_default === "function" ? transformers_default(vFile) : transformers_default,
typeof rehypePlugins === "function" ? rehypePlugins(vFile) : rehypePlugins
];
return plugins.flat();
}
/** @public */
async run() {
const files = await this.getContentFiles();
if (!this.options.watch && this.options.publicDir) {
await this.bundleAssets("stylesheets");
await this.bundleAssets("scripts");
}
const markdownFiles = files.filter(([dir, filename]) => filename.endsWith(".md"));
const parsedVFiles = await this.parseMarkdownFiles(markdownFiles);
if (this.options.linkFiles) {
const files2 = groupByType(parsedVFiles);
parsedVFiles.forEach((vFile) => {
vFile.data.vFiles = files2;
});
}
const vFiles = await this.renderMarkdownFiles(parsedVFiles);
vFiles.map(async (vFile) => {
dbg(vFile, `Writing ${vFile.history.at(-1)}`);
this.verbose(`Writing ${vFile.history.at(-1)}`);
await (0, import_vfile_mkdirp.mkdirp)(vFile);
await import_to_vfile.toVFile.write(vFile);
});
const assetFiles = files.filter(([dir, filename]) => !filename.endsWith(".md"));
await Promise.all(assetFiles.map((file) => this.copyAsset(file)));
console.log(`\u2714 ${vFiles.length} pages and ${assetFiles.length} asset files (in ${this.options.outputDir})`);
const publishedVFiles = vFiles.filter((vFile) => !vFile.data.meta.draft);
if (this.options.feed) {
const feed = await this.renderFeed(publishedVFiles);
if (feed)
console.log(`\u2714 Feed (at ${feed})`);
}
if (this.options.sitemap) {
const sitemap = await this.renderSitemap(publishedVFiles);
if (sitemap)
console.log(`\u2714 Sitemap (at ${sitemap})`);
}
if (this.options.search) {
const search = await this.renderSearchIndex(publishedVFiles);
if (search)
console.log(`\u2714 Search index (at ${search})`);
}
if (this.options.watch) {
const watchDirs = await this.watch([this.options.contentDir, this.options.publicDir].flat().filter(unique));
watchDirs.forEach((dir) => console.log(`\u25B6\uFE0E Watching for changes in ${dir}`));
}
}
async getContentFiles() {
const dirs = [this.options.publicDir, this.options.contentDir].flat().filter(unique);
const glob = [this.options.contentFiles].flat().filter(unique);
const files = [];
for (const cwd of dirs) {
const result = await (0, import_globby.globby)(glob, { cwd, ignore: ["**/node_modules"] });
files.push(result.map((filename) => [cwd ?? "", filename]));
}
return files.flat();
}
async bundleAssets(assetType) {
const bundled = `bundled${ucFirst(assetType)}`;
const pageTypes = Object.keys(this.options.defaults);
const orderedPageTypes = ["page", ...pageTypes].filter(unique);
const assets = {};
for (const pageType of orderedPageTypes) {
assets[pageType] = assets[pageType] ?? [];
if (this.options.defaults[pageType][assetType]) {
for (const asset of this.options.defaults[pageType][assetType]) {
if (!assets[pageType].includes(asset)) {
const source = (0, import_node_path3.join)(this.options.publicDir, asset);
const ext = (0, import_node_path3.extname)(source);
const bundle = (0, import_node_path3.join)(asset, `../${pageType}.bundle${ext}`);
const target = (0, import_node_path3.join)(this.options.outputDir, bundle);
if (pageType !== "page" && assets.page.includes(asset)) {
} else if (assets[pageType].length === 0) {
dbg(source, `Copying ${target} (${asset})`);
this.verbose(`Copying ${target} (${asset})`);
await copy(source, target);
assets[pageType].push(asset);
if (pageType === "page") {
this.options.defaults[pageType][bundled] = [bundle];
} else {
this.options.defaults[pageType][bundled] = [this.options.defaults.page[bundled][0], bundle];
}
} else {
dbg(source, `Appending ${source} to ${target}`);
this.verbose(`Appending ${source} to ${target}`);
await append2(source, target);
assets[pageType].push(asset);
}
}
}
}
}
}
async copyAsset(file) {
const [dir, filename] = file;
const source = (0, import_node_path3.join)(dir, filename);
const target = (0, import_node_path3.join)(this.options.outputDir, filename);
if (filename.endsWith(".svg")) {
dbg(source, `Optimizing to ${target}`);
this.verbose(`Optimizing ${target}`);
await optimizeSVG(source, target);
} else {
dbg(source, `Copying to ${target}`);
this.verbose(`Copying ${target}`);
await copy(source, target);
}
}
async handleFile(file) {
const [dir, filename] = file;
if (filename.endsWith(".md")) {
const parsedVFile = await this.parseMarkdownFile(file);
const vFile = await this.renderMarkdownFile(parsedVFile);
dbg(vFile, `Writing ${vFile.history.at(-1)}`);
this.verbose(`Writing ${vFile.history.at(-1)}`);
await (0, import_vfile_mkdirp.mkdirp)(vFile);
await import_to_vfile.toVFile.write(vFile);
} else {
this.copyAsset(file);
}
}
async parseMarkdownFile([dir, filename]) {
const source = (0, import_node_path3.join)(dir, filename);
const vFile = (0, import_to_vfile.readSync)(source);
vFile.history.unshift(filename);
return this.parseMarkdownVFile(vFile);
}
async parseMarkdownVFile(vFile) {
dbg(vFile, `Parsing source file`);
const options = this.options;
const outputDir = options.outputDir;
const parsers = options.parsers ?? parsers_default;
const remarkPlugins = options.remarkPlugins ?? [];
const parser = (0, import_unified.unified)().use([...parsers, ...remarkPlugins]);
const tree = parser.parse(vFile);
if (options.formatMarkdown) {
dbg(vFile, `Formatting`);
const plugins = [parsers_default[0], parsers_default[1], ...formatters_default];
const formattedVFile = await (0, import_unified.unified)().use(plugins).process(String(vFile));
if (formattedVFile.value.toString() !== vFile.value.toString()) {
this.verbose(`Formatting ${vFile.history.at(-1)}`);
await write(vFile.history.at(-1), formattedVFile.value);
}
}
const filename = vFile.history.at(0);
const pathname = resolvePathname(filename);
vFile = (0, import_vfile_rename.rename)(vFile, { dirname: (0, import_node_path3.join)(outputDir, pathname), stem: "index", extname: ".html" });
dbg(vFile, `Rendered file will be written to ${vFile.history.at(-1)}`);
const type = typeof options.type === "function" ? options.type(filename, vFile.data.matter) || "page" : "page";
(0, import_vfile_matter.matter)(vFile);
vFile.data.pathname = pathname;
const meta = buildMetaData(vFile, type, options);
vFile.data.markdown = String(vFile.value);
vFile.data.tree = tree;
vFile.data.meta = meta;
vFile.data.meta.title = meta.title ?? getDocumentTitle(tree);
vFile.data.structuredContent = getStructuredContent(meta);
dbg(vFile, `The ${type} "${meta.title}" will be served from ${pathname}`);
return vFile;
}
parseMarkdownFiles(files) {
return Promise.all(files.map((file) => this.parseMarkdownFile(file)));
}
renderMarkdownFiles(vFiles) {
return Promise.all(vFiles.map((vFile) => this.renderMarkdownFile(vFile)));
}
async renderMarkdownFile(vFile) {
dbg(vFile, `Rendering ${vFile.history.at(-1)}`);
const { options } = this;
const transformers = this.getTransformers(vFile);
const renderers = options.renderers ?? render_default;
const processor = (0, import_unified.unified)().use(transformDirectives, options.directives ? options.directives : false).use(import_remark_rehype.default, options.remarkRehypeOptions ?? {}).use(layout, vFile.data.meta.layout ? { layout: vFile.data.meta.layout } : false).use(transformers).use(renderers);
const tree = await processor.run(vFile.data.tree, vFile);
vFile.value = processor.stringify(tree);
return vFile;
}
async renderFeed(vFiles) {
if (!this.options.feed)
return;
if (!this.options.host) {
this.verbose("Unable to render RSS feed without `host`");
return;
}
const { filter } = this.options.feed;
const filtered = typeof filter === "function" ? vFiles.filter((vFile) => filter(vFile.data.meta.type, vFile)) : [...vFiles];
const items = await Promise.all(
filtered.sort((a, b) => +new Date(b.data.meta.published) - +new Date(a.data.meta.published)).slice(0, 10).map(async (vFile) => {
const plugins = [removeDocumentTitle, import_remark_rehype.default, import_rehype_stringify2.default];
const processor = (0, import_unified.unified)().use(plugins);
const tree = await processor.run(vFile.data.tree);
const html2 = processor.stringify(tree);
const { meta } = vFile.data;
return {
title: meta.title,
description: meta.description,
descriptionHtml: html2,
author: meta.author,
url: (0, import_node_path3.join)(this.options.host, meta.pathname),
modified: meta.modified,
published: meta.published,
tags: meta.tags
};
})
);
const filename = (0, import_node_path3.join)(this.options.outputDir, this.options.feed.pathname);
const title = this.options.feed.title ?? this.options.name;
const description = this.options.feed.description ?? this.options.defaults.page.description;
const author = this.options.feed.author ?? this.options.defaults.page.author.name;
const contents = (0, import_xast_util_feed.rss)(
{
title,
description,
author,
tags: this.options.feed.tags,
url: this.options.host,
lang: this.options.language,
feedUrl: (0, import_node_path3.join)(this.options.host, this.options.feed.pathname)
},
items
);
await write(filename, (0, import_xast_util_to_xml.toXml)(contents));
return filename;
}
async renderSitemap(vFiles) {
if (!this.options.host) {
this.verbose("Unable to render sitemap without `host`");
return;
}
const { host } = this.options;
const filename = (0, import_node_path3.join)(this.options.outputDir, "sitemap.txt");
const items = vFiles.map((vFile) => vFile.data.meta.href);
await write(filename, items.sort().join(import_os2.EOL) + import_os2.EOL);
return filename;
}
async renderSearchIndex(vFiles) {
const defaults = { outputDir: "_search", filter: () => true };
const options = this.options.search === true ? defaults : import_lodash2.default.defaults(this.options.search, defaults);
const documents = vFiles.filter((vFile) => options.filter(vFile.data.meta.type, vFile)).map((vFile, index) => ({
id: index,
title: vFile.data.meta.title,
description: vFile.data.meta.description,
pathname: vFile.data.meta.pathname,
content: vFile.data.markdown,
tags: vFile.data.meta.tags
}));
const miniSearch = new import_minisearch.default({
fields: ["title", "tags", "description", "content"],
storeFields: ["title", "description", "pathname"]
});
await miniSearch.addAllAsync(documents);
const filename = (0, import_node_path3.join)(this.options.outputDir, options.outputDir, "index.json");
await write(filename, JSON.stringify(miniSearch.toJSON()));
return filename;
}
async watch(dirs) {
const ignorePatterns = [this.options.outputDir, this.options.ignorePattern].flat();
if (dirs) {
const watchDirs = await Promise.all(
dirs.map((dir) => watchDir({ dir, cb: (file) => this.handleFile(file), ignorePatterns }))
);
return watchDirs.filter(Boolean);
}
return [];
}
verbose(text) {
if (this.options.verbose) {
console.log(text);
}
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MarkdownRambler,
addScript,
append,
findElement,
html,
insertBefore,
insertBeforeStylesheets
});