fix(remark-extend): support md files with frontmatter

This commit is contained in:
Thijs Louisse 2025-03-27 00:03:26 +01:00 committed by Thijs Louisse
parent 656b39e0e4
commit 7eb8588bb1
4 changed files with 105 additions and 11 deletions

View file

@ -0,0 +1,5 @@
---
'remark-extend': patch
---
support md files with frontmatter

View file

@ -19,17 +19,31 @@ const path = require('path');
* // execute function
* virtualMod.fn();
*
* @param {*} src
* @param {*} filename
* @param {string} src
* @param {string} filename
* @returns {object}
*/
function requireFromString(src, filename = 'tmp.js') {
const srcWithPath = `const path = require('path');\n${src}`;
// @ts-expect-error
const m = new module.constructor();
// @ts-expect-error
m.paths = module.paths;
m._compile(srcWithPath, filename);
return m.exports;
}
/**
* Frontmatter is problematic when traversing md files with
* unist-util-select: https://github.com/syntax-tree/unist-util-select/blob/main/index.js.
* So we remove it before parsing
* @param {string} mdFileString
* @returns {string}
*/
function stripFrontMatter(mdFileString) {
return mdFileString.replace(/^\s*---(.|\n)*?---/, '');
}
let toInsertNodes = [];
function handleImportedFile({
@ -39,11 +53,12 @@ function handleImportedFile({
globalReplaceFunction,
filePath,
missingEndSelectorMeansUntilEndOfFile = false,
currentFile,
}) {
return tree => {
const start = select(startSelector, tree);
if (!start) {
const msg = `The start selector "${startSelector}" could not find a matching node in "${filePath}".`;
const msg = `The start selector "${startSelector}", imported in "${currentFile}", could not find a matching node in "${filePath}".`;
throw new Error(msg);
}
const startIsNode = { ...start };
@ -54,7 +69,7 @@ function handleImportedFile({
const end = select(endSelector, tree);
if (!end) {
if (missingEndSelectorMeansUntilEndOfFile === false) {
const msg = `The end selector "${endSelector}" could not find a matching node in "${filePath}".`;
const msg = `The end selector "${endSelector}", imported in "${currentFile}", could not find a matching node in "${filePath}".`;
throw new Error(msg);
}
} else {
@ -92,6 +107,8 @@ function handleImportedFile({
// unified expect direct
// eslint-disable-next-line consistent-return
function remarkExtend({ rootDir = process.cwd(), page, globalReplaceFunction } = {}) {
const currentFile = path.resolve(rootDir, page.inputPath);
return tree => {
visit(tree, (node, index, parent) => {
if (
@ -186,11 +203,12 @@ function remarkExtend({ rootDir = process.cwd(), page, globalReplaceFunction } =
filePath,
fileImport,
missingEndSelectorMeansUntilEndOfFile,
currentFile,
})
.use(function plugin() {
this.Compiler = () => '';
});
parser.processSync(importFileContent.toString());
parser.processSync(stripFrontMatter(importFileContent.toString()));
if (node.type === 'root') {
node.children.splice(0, 0, ...toInsertNodes);

View file

@ -0,0 +1,24 @@
---
parts:
- API Table
- Form
- Systems
title: 'Form: API Table'
eleventyNavigation:
key: API Table >> Form >> Systems
title: API Table
order: 90
parent: Systems >> Form
---
# Red
red is the fire
## More Red
the sun can get red
## Additional Red
the red sea

View file

@ -4,6 +4,7 @@ import { expect } from 'chai';
import unified from 'unified';
import markdown from 'remark-parse';
import mdStringify from 'remark-html';
import gfm from 'remark-gfm';
import { remarkExtend } from '../src/remarkExtend.js';
@ -22,6 +23,9 @@ async function expectThrowsAsync(method, { errorMatch, errorMessage } = {}) {
} catch (err) {
error = err;
}
console.debug(error);
expect(error).to.be.an('Error', 'No error was thrown');
if (errorMatch) {
expect(error.message).to.match(errorMatch);
@ -31,20 +35,42 @@ async function expectThrowsAsync(method, { errorMatch, errorMessage } = {}) {
}
}
async function execute(input, { globalReplaceFunction } = {}) {
/**
*
* @param {string} input
* @param {{shouldStringify?: boolean; globalReplaceFunction?: Function;}} param1
*/
async function execute(input, { shouldStringify = true, globalReplaceFunction } = {}) {
const parser = unified()
//
.use(markdown)
.use(gfm)
.use(remarkExtend, {
rootDir: __dirname,
page: { inputPath: 'test-file.md' },
globalReplaceFunction,
})
.use(mdStringify);
});
if (shouldStringify) {
parser.use(mdStringify);
const result = await parser.process(input);
return result.contents;
}
let tree;
parser
.use(() => _tree => {
tree = _tree;
})
// @ts-expect-error
.use(function plugin() {
this.Compiler = () => '';
});
await parser.process(input);
return tree;
}
describe('remarkExtend', () => {
it('does no modifications if no action is found', async () => {
const result = await execute(
@ -196,7 +222,7 @@ describe('remarkExtend', () => {
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=Does not exit])')\n```";
await expectThrowsAsync(() => execute(input), {
errorMatch:
/The start selector "heading:has\(\[value=Does not exit\]\)" could not find a matching node in ".*"\.$/,
/The start selector "heading:has\(\[value=Does not exit\]\)", imported in ".*", could not find a matching node in ".*"\.$/,
});
});
@ -205,7 +231,7 @@ describe('remarkExtend', () => {
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=Does not exit])')\n```";
await expectThrowsAsync(() => execute(input), {
errorMatch:
/The end selector "heading:has\(\[value=Does not exit\]\)" could not find a matching node in ".*"\./,
/The end selector "heading:has\(\[value=Does not exit\]\)", imported in ".*", could not find a matching node in ".*"\./,
});
});
@ -404,4 +430,25 @@ describe('remarkExtend', () => {
].join('\n'),
);
});
it('supports files with frontmatter', async () => {
const result = await execute(
[
//
'### Static Headline',
"```js ::importBlock('./fixtures/three-sections-red-with-frontmatter.md', '## More Red')",
'```',
].join('\n'),
);
expect(result).to.equal(
[
//
'<h3>Static Headline</h3>',
'<h2>More Red</h2>',
'<p>the sun can get red</p>',
'',
].join('\n'),
);
});
});