single-page-markdown-website
Version:
Create a nice single-page documentation website from one or more Markdown files
101 lines • 4.18 kB
JavaScript
import fs from 'fs-extra';
import { globby } from 'globby';
import isUrl from 'is-url';
import path from 'path';
import remarkParse from 'remark-parse';
import stringify from 'remark-stringify';
import { unified } from 'unified';
import { visit } from 'unist-util-visit';
import { VFile } from 'vfile';
import { resolveNewImageFilePath } from './resolve-new-image-file-path.js';
export async function readMarkdownFilesAsync(globs, images = {}) {
const files = [];
const filePaths = await globby(globs);
if (filePaths.length === 0) {
throw new Error(`No files found for: ${globs.join(', ')}`);
}
for (const filePath of filePaths) {
const { fileContent, fileImages } = await readMarkdownFileAsync(filePath, images);
files.push(fileContent);
for (const originalFilePath in fileImages) {
images[originalFilePath] = fileImages[originalFilePath];
}
}
return { files, images };
}
async function readMarkdownFileAsync(filePath, images) {
const value = await fs.readFile(filePath, 'utf8');
const file = await unified()
.use(remarkParse)
.use(remarkReplaceLocalImageFilePaths, { images })
.use(remarkTransclude)
.use(stringify)
.process(new VFile({ path: filePath, value }));
return {
fileContent: file.toString(),
fileImages: file.data.images
};
}
const imageElementSrcRegex = /(<img src=)"([^"]+)"/g;
const remarkReplaceLocalImageFilePaths = function (options) {
return function (node, file) {
const directory = path.dirname(file.path);
function createNewFilePath(imageSrc) {
const originalFilePath = path.join(imageSrc[0] === '/' ? process.cwd() : directory, imageSrc);
const newFilePath = resolveNewImageFilePath(imageSrc, Object.values(options.images));
options.images[originalFilePath] = newFilePath;
return newFilePath;
}
visit(node, ['html', 'image'], function (node) {
if (node.type === 'image') {
const imageSrc = node.url;
if (isUrl(imageSrc) === true) {
return;
}
node.url = createNewFilePath(imageSrc);
return;
}
if (node.type === 'html') {
const html = node.value;
node.value = html.replace(imageElementSrcRegex, function (match, prefix, imageSrc) {
if (isUrl(imageSrc) === true) {
return match;
}
const filePath = createNewFilePath(imageSrc);
return `${prefix}"${filePath}"`;
});
}
});
file.data.images = options.images;
};
};
const remarkTransclude = function () {
return async function (node, file) {
if (typeof file.data.images === 'undefined') {
throw new Error('`file.data.images` is `undefined`');
}
const fileImages = file.data.images;
let result = [];
for (const childNode of node.children) {
if (childNode.type === 'paragraph' &&
childNode.children.length === 1 &&
childNode.children[0].type === 'text') {
const value = childNode.children[0].value;
if (value.indexOf('./') === 0 || value[0] === '/') {
const directory = value[0] === '/' ? process.cwd() : path.dirname(file.path);
const glob = path.join(directory, value.slice(1));
const { files, images } = await readMarkdownFilesAsync([glob], fileImages);
const tree = unified().use(remarkParse).parse(files.join('\n'));
result = result.concat(tree.children);
for (const originalFilePath in images) {
fileImages[originalFilePath] = images[originalFilePath];
}
continue;
}
}
result.push(childNode);
}
node.children = result;
};
};
//# sourceMappingURL=read-markdown-files-async.js.map