0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00
astro/packages/integrations/markdoc/src/index.ts
Ben Holmes f42f47dc6a
feat: Expose $entry variable to Markdoc (#6588)
* wip: dream api

* deps: rollup types

* feat: get entry data in there

* fix: properly show mdoc errors in overlay

* feat: implement with cache

* fix: wait for in-flight entry resolution

* test: entry properties can be rendered

* chore: changeset

* fix: remove rollup type import

* Revert "deps: rollup types"

This reverts commit 484ccb1c81.

* docs: add README reference

* docs nit: missing space

Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com>

* refactor: split config config loading to separate helper

* refactor: choose more readable variable names

* refactor: store awaiting queue in existing cache

* docs: add clear code comments

* nit: add skip module code comment

* refactor: add `idHandledByContentRenderPlugin`

* nit: store chokidar modified events in const

* fix: remove loop from content renderer

* nit: else if -> if

---------

Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com>
2023-03-21 09:20:23 -04:00

107 lines
3.5 KiB
TypeScript

import type { Config } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { getAstroConfigPath, MarkdocError, parseFrontmatter } from './utils.js';
type SetupHookParams = HookParameters<'astro:config:setup'> & {
// `contentEntryType` is not a public API
// Add type defs here
addContentEntryType: (contentEntryType: ContentEntryType) => void;
};
export default function markdoc(markdocConfig: Config = {}): AstroIntegration {
return {
name: '@astrojs/markdoc',
hooks: {
'astro:config:setup': async (params) => {
const { updateConfig, config, addContentEntryType } = params as SetupHookParams;
function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
return {
data: parsed.data,
body: parsed.content,
slug: parsed.data.slug,
rawData: parsed.matter,
};
}
addContentEntryType({
extensions: ['.mdoc'],
getEntryInfo,
getRenderModule({ entry }) {
validateRenderProperties(markdocConfig, config);
const ast = Markdoc.parse(entry.body);
const content = Markdoc.transform(ast, {
...markdocConfig,
variables: {
...markdocConfig.variables,
entry,
},
});
return {
code: `import { jsx as h } from 'astro/jsx-runtime';\nimport { Renderer } from '@astrojs/markdoc/components';\nconst transformedContent = ${JSON.stringify(
content
)};\nexport async function Content ({ components }) { return h(Renderer, { content: transformedContent, components }); }\nContent[Symbol.for('astro.needsHeadRendering')] = true;`,
};
},
contentModuleTypes: await fs.promises.readFile(
new URL('../template/content-module-types.d.ts', import.meta.url),
'utf-8'
),
});
},
},
};
}
function validateRenderProperties(markdocConfig: Config, astroConfig: AstroConfig) {
const tags = markdocConfig.tags ?? {};
const nodes = markdocConfig.nodes ?? {};
for (const [name, config] of Object.entries(tags)) {
validateRenderProperty({ type: 'tag', name, config, astroConfig });
}
for (const [name, config] of Object.entries(nodes)) {
validateRenderProperty({ type: 'node', name, config, astroConfig });
}
}
function validateRenderProperty({
name,
config,
type,
astroConfig,
}: {
name: string;
config: { render?: string };
type: 'node' | 'tag';
astroConfig: Pick<AstroConfig, 'root'>;
}) {
if (typeof config.render === 'string' && config.render.length === 0) {
throw new Error(
`Invalid ${type} configuration: ${JSON.stringify(
name
)}. The "render" property cannot be an empty string.`
);
}
if (typeof config.render === 'string' && !isCapitalized(config.render)) {
const astroConfigPath = getAstroConfigPath(fs, fileURLToPath(astroConfig.root));
throw new MarkdocError({
message: `Invalid ${type} configuration: ${JSON.stringify(
name
)}. The "render" property must reference a capitalized component name.`,
hint: 'If you want to render to an HTML element, see our docs on rendering Markdoc manually: https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components',
location: astroConfigPath
? {
file: astroConfigPath,
}
: undefined,
});
}
}
function isCapitalized(str: string) {
return str.length > 0 && str[0] === str[0].toUpperCase();
}