fix(remark-extend): support md files with frontmatter
This commit is contained in:
parent
656b39e0e4
commit
7eb8588bb1
4 changed files with 105 additions and 11 deletions
5
.changeset/purple-ties-complain.md
Normal file
5
.changeset/purple-ties-complain.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'remark-extend': patch
|
||||
---
|
||||
|
||||
support md files with frontmatter
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,18 +35,40 @@ 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', () => {
|
||||
|
|
@ -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'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue