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
|
* // execute function
|
||||||
* virtualMod.fn();
|
* virtualMod.fn();
|
||||||
*
|
*
|
||||||
* @param {*} src
|
* @param {string} src
|
||||||
* @param {*} filename
|
* @param {string} filename
|
||||||
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
function requireFromString(src, filename = 'tmp.js') {
|
function requireFromString(src, filename = 'tmp.js') {
|
||||||
const srcWithPath = `const path = require('path');\n${src}`;
|
const srcWithPath = `const path = require('path');\n${src}`;
|
||||||
|
// @ts-expect-error
|
||||||
const m = new module.constructor();
|
const m = new module.constructor();
|
||||||
|
// @ts-expect-error
|
||||||
m.paths = module.paths;
|
m.paths = module.paths;
|
||||||
m._compile(srcWithPath, filename);
|
m._compile(srcWithPath, filename);
|
||||||
return m.exports;
|
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 = [];
|
let toInsertNodes = [];
|
||||||
|
|
||||||
function handleImportedFile({
|
function handleImportedFile({
|
||||||
|
|
@ -39,11 +53,12 @@ function handleImportedFile({
|
||||||
globalReplaceFunction,
|
globalReplaceFunction,
|
||||||
filePath,
|
filePath,
|
||||||
missingEndSelectorMeansUntilEndOfFile = false,
|
missingEndSelectorMeansUntilEndOfFile = false,
|
||||||
|
currentFile,
|
||||||
}) {
|
}) {
|
||||||
return tree => {
|
return tree => {
|
||||||
const start = select(startSelector, tree);
|
const start = select(startSelector, tree);
|
||||||
if (!start) {
|
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);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
const startIsNode = { ...start };
|
const startIsNode = { ...start };
|
||||||
|
|
@ -54,7 +69,7 @@ function handleImportedFile({
|
||||||
const end = select(endSelector, tree);
|
const end = select(endSelector, tree);
|
||||||
if (!end) {
|
if (!end) {
|
||||||
if (missingEndSelectorMeansUntilEndOfFile === false) {
|
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);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -92,6 +107,8 @@ function handleImportedFile({
|
||||||
// unified expect direct
|
// unified expect direct
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
function remarkExtend({ rootDir = process.cwd(), page, globalReplaceFunction } = {}) {
|
function remarkExtend({ rootDir = process.cwd(), page, globalReplaceFunction } = {}) {
|
||||||
|
const currentFile = path.resolve(rootDir, page.inputPath);
|
||||||
|
|
||||||
return tree => {
|
return tree => {
|
||||||
visit(tree, (node, index, parent) => {
|
visit(tree, (node, index, parent) => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -186,11 +203,12 @@ function remarkExtend({ rootDir = process.cwd(), page, globalReplaceFunction } =
|
||||||
filePath,
|
filePath,
|
||||||
fileImport,
|
fileImport,
|
||||||
missingEndSelectorMeansUntilEndOfFile,
|
missingEndSelectorMeansUntilEndOfFile,
|
||||||
|
currentFile,
|
||||||
})
|
})
|
||||||
.use(function plugin() {
|
.use(function plugin() {
|
||||||
this.Compiler = () => '';
|
this.Compiler = () => '';
|
||||||
});
|
});
|
||||||
parser.processSync(importFileContent.toString());
|
parser.processSync(stripFrontMatter(importFileContent.toString()));
|
||||||
|
|
||||||
if (node.type === 'root') {
|
if (node.type === 'root') {
|
||||||
node.children.splice(0, 0, ...toInsertNodes);
|
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 unified from 'unified';
|
||||||
import markdown from 'remark-parse';
|
import markdown from 'remark-parse';
|
||||||
import mdStringify from 'remark-html';
|
import mdStringify from 'remark-html';
|
||||||
|
import gfm from 'remark-gfm';
|
||||||
|
|
||||||
import { remarkExtend } from '../src/remarkExtend.js';
|
import { remarkExtend } from '../src/remarkExtend.js';
|
||||||
|
|
||||||
|
|
@ -22,6 +23,9 @@ async function expectThrowsAsync(method, { errorMatch, errorMessage } = {}) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error = err;
|
error = err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.debug(error);
|
||||||
|
|
||||||
expect(error).to.be.an('Error', 'No error was thrown');
|
expect(error).to.be.an('Error', 'No error was thrown');
|
||||||
if (errorMatch) {
|
if (errorMatch) {
|
||||||
expect(error.message).to.match(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()
|
const parser = unified()
|
||||||
//
|
//
|
||||||
.use(markdown)
|
.use(markdown)
|
||||||
|
.use(gfm)
|
||||||
.use(remarkExtend, {
|
.use(remarkExtend, {
|
||||||
rootDir: __dirname,
|
rootDir: __dirname,
|
||||||
page: { inputPath: 'test-file.md' },
|
page: { inputPath: 'test-file.md' },
|
||||||
globalReplaceFunction,
|
globalReplaceFunction,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldStringify) {
|
||||||
|
parser.use(mdStringify);
|
||||||
|
const result = await parser.process(input);
|
||||||
|
return result.contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree;
|
||||||
|
parser
|
||||||
|
.use(() => _tree => {
|
||||||
|
tree = _tree;
|
||||||
})
|
})
|
||||||
.use(mdStringify);
|
// @ts-expect-error
|
||||||
const result = await parser.process(input);
|
.use(function plugin() {
|
||||||
return result.contents;
|
this.Compiler = () => '';
|
||||||
|
});
|
||||||
|
|
||||||
|
await parser.process(input);
|
||||||
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('remarkExtend', () => {
|
describe('remarkExtend', () => {
|
||||||
|
|
@ -196,7 +222,7 @@ describe('remarkExtend', () => {
|
||||||
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=Does not exit])')\n```";
|
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=Does not exit])')\n```";
|
||||||
await expectThrowsAsync(() => execute(input), {
|
await expectThrowsAsync(() => execute(input), {
|
||||||
errorMatch:
|
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```";
|
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=Does not exit])')\n```";
|
||||||
await expectThrowsAsync(() => execute(input), {
|
await expectThrowsAsync(() => execute(input), {
|
||||||
errorMatch:
|
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'),
|
].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