From 595b069136c26f03483d88a7304cdcded32220e8 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 23 Mar 2023 17:50:22 -0400 Subject: [PATCH 01/33] deps: esbuild --- packages/integrations/markdoc/package.json | 3 ++- pnpm-lock.yaml | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index 67b311c651fb..d477f2d4fd88 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -21,6 +21,7 @@ "exports": { ".": "./dist/index.js", "./components": "./components/index.ts", + "./astro-stub": "./dist/astro-stub.js", "./package.json": "./package.json" }, "scripts": { @@ -32,6 +33,7 @@ }, "dependencies": { "@markdoc/markdoc": "^0.2.2", + "esbuild": "^0.17.12", "gray-matter": "^4.0.3", "zod": "^3.17.3" }, @@ -45,7 +47,6 @@ "@types/mocha": "^9.1.1", "astro-scripts": "workspace:*", "chai": "^4.3.6", - "devalue": "^4.2.0", "linkedom": "^0.14.12", "mocha": "^9.2.2", "rollup": "^3.20.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 046e984bc940..6ed940ddc033 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3076,7 +3076,7 @@ importers: astro: workspace:* astro-scripts: workspace:* chai: ^4.3.6 - devalue: ^4.2.0 + esbuild: ^0.17.12 gray-matter: ^4.0.3 linkedom: ^0.14.12 mocha: ^9.2.2 @@ -3085,6 +3085,7 @@ importers: zod: ^3.17.3 dependencies: '@markdoc/markdoc': 0.2.2 + esbuild: 0.17.12 gray-matter: 4.0.3 zod: 3.20.6 devDependencies: @@ -3094,7 +3095,6 @@ importers: astro: link:../../astro astro-scripts: link:../../../scripts chai: 4.3.7 - devalue: 4.2.3 linkedom: 0.14.21 mocha: 9.2.2 rollup: 3.20.1 @@ -9822,6 +9822,7 @@ packages: /devalue/4.2.3: resolution: {integrity: sha512-JG6Q248aN0pgFL57e3zqTVeFraBe+5W2ugvv1mLXsJP6YYIYJhRZhAl7QP8haJrqob6X10F9NEkuCvNILZTPeQ==} + dev: false /didyoumean/1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} From 2d452a2f677dabab737577f58c579b6f3289c573 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 23 Mar 2023 17:52:42 -0400 Subject: [PATCH 02/33] feat: support direct component imports for render! --- examples/with-markdoc/astro.config.mjs | 14 +-- examples/with-markdoc/markdoc.config.mjs | 13 +++ .../src/components/DocsContent.astro | 32 ------ examples/with-markdoc/src/pages/index.astro | 19 ++-- .../markdoc/components/Renderer.astro | 11 ++- .../markdoc/components/TreeNode.ts | 4 +- .../integrations/markdoc/src/astro-stub.ts | 4 + .../markdoc/src/experimental-asset-config.ts | 21 ++++ packages/integrations/markdoc/src/index.ts | 60 +++++------ .../integrations/markdoc/src/load-config.ts | 99 +++++++++++++++++++ 10 files changed, 193 insertions(+), 84 deletions(-) create mode 100644 examples/with-markdoc/markdoc.config.mjs delete mode 100644 examples/with-markdoc/src/components/DocsContent.astro create mode 100644 packages/integrations/markdoc/src/astro-stub.ts create mode 100644 packages/integrations/markdoc/src/experimental-asset-config.ts create mode 100644 packages/integrations/markdoc/src/load-config.ts diff --git a/examples/with-markdoc/astro.config.mjs b/examples/with-markdoc/astro.config.mjs index d88ed2098532..29d846359bb2 100644 --- a/examples/with-markdoc/astro.config.mjs +++ b/examples/with-markdoc/astro.config.mjs @@ -3,17 +3,5 @@ import markdoc from '@astrojs/markdoc'; // https://astro.build/config export default defineConfig({ - integrations: [ - markdoc({ - tags: { - aside: { - render: 'Aside', - attributes: { - type: { type: String }, - title: { type: String }, - }, - }, - }, - }), - ], + integrations: [markdoc()], }); diff --git a/examples/with-markdoc/markdoc.config.mjs b/examples/with-markdoc/markdoc.config.mjs new file mode 100644 index 000000000000..b5e9eed24b34 --- /dev/null +++ b/examples/with-markdoc/markdoc.config.mjs @@ -0,0 +1,13 @@ +import Aside from './src/components/Aside.astro'; + +export default { + tags: { + aside: { + render: Aside, + attributes: { + type: { type: String }, + title: { type: String }, + }, + }, + }, +}; diff --git a/examples/with-markdoc/src/components/DocsContent.astro b/examples/with-markdoc/src/components/DocsContent.astro deleted file mode 100644 index 162c1fc6d0c4..000000000000 --- a/examples/with-markdoc/src/components/DocsContent.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -import Aside from './Aside.astro'; -import type { CollectionEntry } from 'astro:content'; - -type Props = { - entry: CollectionEntry<'docs'>; -}; - -const { entry } = Astro.props; -const { Content } = await entry.render(); ---- - - - - diff --git a/examples/with-markdoc/src/pages/index.astro b/examples/with-markdoc/src/pages/index.astro index 01412cce12ea..7efcbeda88af 100644 --- a/examples/with-markdoc/src/pages/index.astro +++ b/examples/with-markdoc/src/pages/index.astro @@ -1,18 +1,25 @@ --- import { getEntryBySlug } from 'astro:content'; -import DocsContent from '../components/DocsContent.astro'; import Layout from '../layouts/Layout.astro'; const intro = await getEntryBySlug('docs', 'intro'); +const { Content } = await intro.render(); ---

{intro.data.title}

- - - - - +
+ + diff --git a/packages/integrations/markdoc/components/Renderer.astro b/packages/integrations/markdoc/components/Renderer.astro index 6ae8ee85079c..c407b25a7fa4 100644 --- a/packages/integrations/markdoc/components/Renderer.astro +++ b/packages/integrations/markdoc/components/Renderer.astro @@ -1,15 +1,20 @@ --- -import type { RenderableTreeNode } from '@markdoc/markdoc'; +import type { Config } from '@markdoc/markdoc'; +import Markdoc from '@markdoc/markdoc'; import type { AstroInstance } from 'astro'; import { validateComponentsProp } from '../dist/utils.js'; import { ComponentNode, createTreeNode } from './TreeNode.js'; type Props = { - content: RenderableTreeNode; + config: Config; + stringifiedAst: string; components?: Record; }; -const { content, components } = Astro.props as Props; +const { stringifiedAst, config, components } = Astro.props as Props; + +const ast = Markdoc.Ast.fromJSON(stringifiedAst); +const content = Markdoc.transform(ast, config); // Will throw if components is invalid if (components) { diff --git a/packages/integrations/markdoc/components/TreeNode.ts b/packages/integrations/markdoc/components/TreeNode.ts index f46355d5c82b..f4a192c77e5a 100644 --- a/packages/integrations/markdoc/components/TreeNode.ts +++ b/packages/integrations/markdoc/components/TreeNode.ts @@ -63,8 +63,8 @@ export function createTreeNode( return { type: 'text', content: '' }; } - if (node.name in components) { - const component = components[node.name]; + if (typeof node.name === 'function') { + const component = node.name; const props = node.attributes; const children = node.children.map((child) => createTreeNode(child, components)); diff --git a/packages/integrations/markdoc/src/astro-stub.ts b/packages/integrations/markdoc/src/astro-stub.ts new file mode 100644 index 000000000000..6f4ab4a830a0 --- /dev/null +++ b/packages/integrations/markdoc/src/astro-stub.ts @@ -0,0 +1,4 @@ +// Used by the build-time config loader. +// All `.astro` imports from the `markdoc.config.mjs` file +// are stubbed to return this. +export default true; diff --git a/packages/integrations/markdoc/src/experimental-asset-config.ts b/packages/integrations/markdoc/src/experimental-asset-config.ts new file mode 100644 index 000000000000..7e621fe9e0e8 --- /dev/null +++ b/packages/integrations/markdoc/src/experimental-asset-config.ts @@ -0,0 +1,21 @@ +import type { Config as MarkdocConfig } from '@markdoc/markdoc'; +import Markdoc from '@markdoc/markdoc'; + +export const expermentalAssetConfig: MarkdocConfig = { + nodes: { + image: { + ...Markdoc.nodes.image, + transform(node, config) { + const attributes = node.transformAttributes(config); + const children = node.transformChildren(config); + + if (node.type === 'image' && '__optimizedSrc' in node.attributes) { + const { __optimizedSrc, ...rest } = node.attributes; + return new Markdoc.Tag('Image', { ...rest, src: __optimizedSrc }, children); + } else { + return new Markdoc.Tag('img', attributes, children); + } + }, + }, + }, +}; diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index ebfc09ba7ae0..c3af2311ece1 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -18,6 +18,7 @@ import { // @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations. import { emitESMImage } from 'astro/assets'; import type { Plugin as VitePlugin } from 'vite'; +import { loadMarkdocConfig } from './load-config.js'; type SetupHookParams = HookParameters<'astro:config:setup'> & { // `contentEntryType` is not a public API @@ -38,6 +39,9 @@ export default function markdocIntegration( addContentEntryType, } = params as SetupHookParams; + const configLoadResult = await loadMarkdocConfig(astroConfig); + const userMarkdocConfig = configLoadResult?.config ?? {}; + updateConfig({ vite: { plugins: [safeAssetsVirtualModulePlugin({ astroConfig })], @@ -60,13 +64,6 @@ export default function markdocIntegration( validateRenderProperties(userMarkdocConfig, astroConfig); const ast = Markdoc.parse(entry.body); const pluginContext = this; - const markdocConfig: MarkdocConfig = { - ...userMarkdocConfig, - variables: { - ...userMarkdocConfig.variables, - entry, - }, - }; if (astroConfig.experimental?.assets) { await emitOptimizedImages(ast.children, { @@ -74,31 +71,38 @@ export default function markdocIntegration( pluginContext, filePath: entry._internal.filePath, }); - - markdocConfig.nodes ??= {}; - markdocConfig.nodes.image = { - ...Markdoc.nodes.image, - transform(node, config) { - const attributes = node.transformAttributes(config); - const children = node.transformChildren(config); - - if (node.type === 'image' && '__optimizedSrc' in node.attributes) { - const { __optimizedSrc, ...rest } = node.attributes; - return new Markdoc.Tag('Image', { ...rest, src: __optimizedSrc }, children); - } else { - return new Markdoc.Tag('img', attributes, children); - } - }, - }; } - const content = Markdoc.transform(ast, markdocConfig); + const validationErrors = Markdoc.validate(ast, userMarkdocConfig).filter((e) => { + // Ignore `variable-undefined` errors. + // Variables can be configured at runtime, + // so we cannot validate them at build time. + return e.error.id !== 'variable-undefined'; + }); + if (validationErrors.length) { + throw new MarkdocError({ + message: [ + `**${String(entry.collection)} β†’ ${String(entry.id)}** failed to validate:`, + ...validationErrors.map((e) => e.error.id), + ].join('\n'), + }); + } - 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;`, + const code = { + code: `import { jsx as h } from 'astro/jsx-runtime';${ + configLoadResult + ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};` + : '' + }\nimport { Renderer } from '@astrojs/markdoc/components';\nconst stringifiedAst = ${JSON.stringify( + // Double stringify to encode *as* stringified JSON + JSON.stringify(ast) + )};\nexport async function Content ({ components, variables }) {\n const config = ${ + configLoadResult + ? '{ ...userConfig, variables: { ...userConfig.variables, ...variables } }' + : '{ variables }' + };\n return h(Renderer, { stringifiedAst, config, components }); };`, }; + return code; }, contentModuleTypes: await fs.promises.readFile( new URL('../template/content-module-types.d.ts', import.meta.url), diff --git a/packages/integrations/markdoc/src/load-config.ts b/packages/integrations/markdoc/src/load-config.ts new file mode 100644 index 000000000000..ac5890d8f897 --- /dev/null +++ b/packages/integrations/markdoc/src/load-config.ts @@ -0,0 +1,99 @@ +import type { AstroConfig } from 'astro'; +import type { Config as MarkdocConfig } from '@markdoc/markdoc'; +import { build } from 'esbuild'; +import { fileURLToPath } from 'node:url'; +import * as fs from 'node:fs'; + +const SUPPORTED_MARKDOC_CONFIG_FILES = [ + 'markdoc.config.js', + 'markdoc.config.mjs', + 'markdoc.config.ts', +]; + +export async function loadMarkdocConfig(astroConfig: Pick) { + let markdocConfigUrl: URL | undefined; + for (const filename of SUPPORTED_MARKDOC_CONFIG_FILES) { + const filePath = new URL(filename, astroConfig.root); + if (!fs.existsSync(filePath)) continue; + + markdocConfigUrl = filePath; + break; + } + if (!markdocConfigUrl) return; + + const { code, dependencies } = await bundleConfigFile({ + markdocConfigUrl, + astroConfig, + }); + + console.log({ code }); + + const config: MarkdocConfig = await loadConfigFromBundledFile(astroConfig.root, code); + + console.log({ config }); + return { + config, + fileUrl: markdocConfigUrl, + }; +} + +/** + * Forked from Vite's `bundleConfigFile` function + * with added handling for `.astro` imports, + * and removed unused Deno patches. + */ +async function bundleConfigFile({ + markdocConfigUrl, + astroConfig, +}: { + markdocConfigUrl: URL; + astroConfig: Pick; +}): Promise<{ code: string; dependencies: string[] }> { + const result = await build({ + absWorkingDir: fileURLToPath(astroConfig.root), + entryPoints: [fileURLToPath(markdocConfigUrl)], + outfile: 'out.js', + write: false, + target: ['node16'], + platform: 'node', + packages: 'external', + bundle: true, + format: 'esm', + sourcemap: 'inline', + metafile: true, + plugins: [ + { + name: 'stub-astro-imports', + setup(build) { + build.onResolve({ filter: /.*\.astro$/ }, async ({ path: id, importer, kind }) => { + console.log({ id }); + return { + path: '@astrojs/markdoc/astro-stub', + external: true, + }; + }); + }, + }, + ], + }); + const { text } = result.outputFiles[0]; + return { + code: text, + dependencies: result.metafile ? Object.keys(result.metafile.inputs) : [], + }; +} + +async function loadConfigFromBundledFile(root: URL, code: string): Promise { + // Write it to disk, load it with native Node ESM, then delete the file. + const tmpFileUrl = new URL(`markdoc.config.timestamp-${Date.now()}.mjs`, root); + fs.writeFileSync(tmpFileUrl, code); + try { + return (await import(tmpFileUrl.pathname)).default; + } finally { + try { + fs.unlinkSync(tmpFileUrl); + } catch { + // already removed if this function is called twice simultaneously + } + } +} From 2c7e05580d863f056b910b535f7e0a15cc765af1 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 23 Mar 2023 20:46:49 -0400 Subject: [PATCH 03/33] deps: add devalue back --- packages/integrations/markdoc/package.json | 2 ++ pnpm-lock.yaml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index d477f2d4fd88..dbc2248d821c 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -22,6 +22,7 @@ ".": "./dist/index.js", "./components": "./components/index.ts", "./astro-stub": "./dist/astro-stub.js", + "./experimental-assets-config": "./dist/experimental-assets-config.js", "./package.json": "./package.json" }, "scripts": { @@ -47,6 +48,7 @@ "@types/mocha": "^9.1.1", "astro-scripts": "workspace:*", "chai": "^4.3.6", + "devalue": "^4.2.0", "linkedom": "^0.14.12", "mocha": "^9.2.2", "rollup": "^3.20.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ed940ddc033..0723be6ec783 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3076,6 +3076,7 @@ importers: astro: workspace:* astro-scripts: workspace:* chai: ^4.3.6 + devalue: ^4.2.0 esbuild: ^0.17.12 gray-matter: ^4.0.3 linkedom: ^0.14.12 @@ -3095,6 +3096,7 @@ importers: astro: link:../../astro astro-scripts: link:../../../scripts chai: 4.3.7 + devalue: 4.2.3 linkedom: 0.14.21 mocha: 9.2.2 rollup: 3.20.1 @@ -9822,7 +9824,6 @@ packages: /devalue/4.2.3: resolution: {integrity: sha512-JG6Q248aN0pgFL57e3zqTVeFraBe+5W2ugvv1mLXsJP6YYIYJhRZhAl7QP8haJrqob6X10F9NEkuCvNILZTPeQ==} - dev: false /didyoumean/1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} From d08917b79496c3475affd1a7fd71e1fd4410a765 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 23 Mar 2023 20:48:07 -0400 Subject: [PATCH 04/33] refactor: remove unused components prop --- .../markdoc/components/TreeNode.ts | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/integrations/markdoc/components/TreeNode.ts b/packages/integrations/markdoc/components/TreeNode.ts index f4a192c77e5a..508b6efd0f3c 100644 --- a/packages/integrations/markdoc/components/TreeNode.ts +++ b/packages/integrations/markdoc/components/TreeNode.ts @@ -1,9 +1,7 @@ import type { AstroInstance } from 'astro'; import type { RenderableTreeNode } from '@markdoc/markdoc'; -import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js'; -// @ts-expect-error Cannot find module 'astro:markdoc-assets' or its corresponding type declarations -import { Image } from 'astro:markdoc-assets'; import Markdoc from '@markdoc/markdoc'; +import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js'; import { MarkdocError, isCapitalized } from '../dist/utils.js'; export type TreeNode = @@ -47,16 +45,7 @@ export const ComponentNode = createComponent({ propagation: 'none', }); -const builtInComponents: Record = { - Image, -}; - -export function createTreeNode( - node: RenderableTreeNode, - userComponents: Record = {} -): TreeNode { - const components = { ...userComponents, ...builtInComponents }; - +export function createTreeNode(node: RenderableTreeNode): TreeNode { if (typeof node === 'string' || typeof node === 'number') { return { type: 'text', content: String(node) }; } else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) { @@ -66,7 +55,7 @@ export function createTreeNode( if (typeof node.name === 'function') { const component = node.name; const props = node.attributes; - const children = node.children.map((child) => createTreeNode(child, components)); + const children = node.children.map((child) => createTreeNode(child)); return { type: 'component', @@ -84,7 +73,7 @@ export function createTreeNode( type: 'element', tag: node.name, attributes: node.attributes, - children: node.children.map((child) => createTreeNode(child, components)), + children: node.children.map((child) => createTreeNode(child)), }; } } From e1689b7829fd2a382526842c008860f35973e145 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 23 Mar 2023 20:48:22 -0400 Subject: [PATCH 05/33] refactor: load experimental assets config separately --- ...onfig.ts => experimental-assets-config.ts} | 10 ++- packages/integrations/markdoc/src/index.ts | 85 +++++-------------- 2 files changed, 29 insertions(+), 66 deletions(-) rename packages/integrations/markdoc/src/{experimental-asset-config.ts => experimental-assets-config.ts} (63%) diff --git a/packages/integrations/markdoc/src/experimental-asset-config.ts b/packages/integrations/markdoc/src/experimental-assets-config.ts similarity index 63% rename from packages/integrations/markdoc/src/experimental-asset-config.ts rename to packages/integrations/markdoc/src/experimental-assets-config.ts index 7e621fe9e0e8..edf315de83b8 100644 --- a/packages/integrations/markdoc/src/experimental-asset-config.ts +++ b/packages/integrations/markdoc/src/experimental-assets-config.ts @@ -1,17 +1,21 @@ import type { Config as MarkdocConfig } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; +import { Image } from 'astro:assets'; -export const expermentalAssetConfig: MarkdocConfig = { +export const experimentalAssetsConfig: MarkdocConfig = { nodes: { image: { - ...Markdoc.nodes.image, + attributes: { + ...Markdoc.nodes.image.attributes, + __optimizedSrc: { type: 'Object' }, + }, transform(node, config) { const attributes = node.transformAttributes(config); const children = node.transformChildren(config); if (node.type === 'image' && '__optimizedSrc' in node.attributes) { const { __optimizedSrc, ...rest } = node.attributes; - return new Markdoc.Tag('Image', { ...rest, src: __optimizedSrc }, children); + return new Markdoc.Tag(Image, { ...rest, src: __optimizedSrc }, children); } else { return new Markdoc.Tag('img', attributes, children); } diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index c3af2311ece1..b16ba01d9cee 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -1,8 +1,4 @@ -import type { - Config as ReadonlyMarkdocConfig, - ConfigType as MarkdocConfig, - Node, -} from '@markdoc/markdoc'; +import type { Config as ReadonlyMarkdocConfig, Node } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro'; import fs from 'node:fs'; @@ -17,7 +13,6 @@ import { } from './utils.js'; // @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations. import { emitESMImage } from 'astro/assets'; -import type { Plugin as VitePlugin } from 'vite'; import { loadMarkdocConfig } from './load-config.js'; type SetupHookParams = HookParameters<'astro:config:setup'> & { @@ -33,21 +28,11 @@ export default function markdocIntegration( name: '@astrojs/markdoc', hooks: { 'astro:config:setup': async (params) => { - const { - updateConfig, - config: astroConfig, - addContentEntryType, - } = params as SetupHookParams; + const { config: astroConfig, addContentEntryType } = params as SetupHookParams; const configLoadResult = await loadMarkdocConfig(astroConfig); const userMarkdocConfig = configLoadResult?.config ?? {}; - updateConfig({ - vite: { - plugins: [safeAssetsVirtualModulePlugin({ astroConfig })], - }, - }); - function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) { const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl)); return { @@ -65,14 +50,6 @@ export default function markdocIntegration( const ast = Markdoc.parse(entry.body); const pluginContext = this; - if (astroConfig.experimental?.assets) { - await emitOptimizedImages(ast.children, { - astroConfig, - pluginContext, - filePath: entry._internal.filePath, - }); - } - const validationErrors = Markdoc.validate(ast, userMarkdocConfig).filter((e) => { // Ignore `variable-undefined` errors. // Variables can be configured at runtime, @@ -88,19 +65,35 @@ export default function markdocIntegration( }); } + if (astroConfig.experimental.assets) { + await emitOptimizedImages(ast.children, { + astroConfig, + pluginContext, + filePath: entry._internal.filePath, + }); + } + const code = { code: `import { jsx as h } from 'astro/jsx-runtime';${ configLoadResult ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};` : '' + }${ + astroConfig.experimental.assets + ? `\nimport { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';` + : '' }\nimport { Renderer } from '@astrojs/markdoc/components';\nconst stringifiedAst = ${JSON.stringify( // Double stringify to encode *as* stringified JSON JSON.stringify(ast) - )};\nexport async function Content ({ components, variables }) {\n const config = ${ + )};\nexport async function Content (props) {\n const config = ${ configLoadResult - ? '{ ...userConfig, variables: { ...userConfig.variables, ...variables } }' - : '{ variables }' - };\n return h(Renderer, { stringifiedAst, config, components }); };`, + ? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }' + : '{ variables: props }' + };${ + astroConfig.experimental.assets + ? `config.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };` + : '' + }\n return h(Renderer, { stringifiedAst, config }); };`, }; return code; }, @@ -210,37 +203,3 @@ function validateRenderProperty({ function isCapitalized(str: string) { return str.length > 0 && str[0] === str[0].toUpperCase(); } - -/** - * TODO: remove when `experimental.assets` is baselined. - * - * `astro:assets` will fail to resolve if the `experimental.assets` flag is not enabled. - * This ensures a fallback for the Markdoc renderer to safely import at the top level. - * @see ../components/TreeNode.ts - */ -function safeAssetsVirtualModulePlugin({ - astroConfig, -}: { - astroConfig: Pick; -}): VitePlugin { - const virtualModuleId = 'astro:markdoc-assets'; - const resolvedVirtualModuleId = '\0' + virtualModuleId; - - return { - name: 'astro:markdoc-safe-assets-virtual-module', - resolveId(id) { - if (id === virtualModuleId) { - return resolvedVirtualModuleId; - } - }, - load(id) { - if (id !== resolvedVirtualModuleId) return; - - if (astroConfig.experimental?.assets) { - return `export { Image } from 'astro:assets';`; - } else { - return `export const Image = () => { throw new Error('Cannot use the Image component without the \`experimental.assets\` flag.'); }`; - } - }, - }; -} From 4cc3797999453ee1b46f7284d239880fadba4abf Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Thu, 23 Mar 2023 20:48:57 -0400 Subject: [PATCH 06/33] fix: upate Content type def to support props --- .../integrations/markdoc/template/content-module-types.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/integrations/markdoc/template/content-module-types.d.ts b/packages/integrations/markdoc/template/content-module-types.d.ts index 87c17af24ac6..7b82eb18a1fe 100644 --- a/packages/integrations/markdoc/template/content-module-types.d.ts +++ b/packages/integrations/markdoc/template/content-module-types.d.ts @@ -1,9 +1,7 @@ declare module 'astro:content' { interface Render { '.mdoc': Promise<{ - Content(props: { - components?: Record; - }): import('astro').MarkdownInstance<{}>['Content']; + Content(props: Record): import('astro').MarkdownInstance<{}>['Content']; }>; } } From 228bfd04982f8d841a2aa88489aa24b6c5cf84ca Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:08:05 -0400 Subject: [PATCH 07/33] refactor: replace astro stub with inline data --- packages/integrations/markdoc/package.json | 1 - packages/integrations/markdoc/src/astro-stub.ts | 4 ---- packages/integrations/markdoc/src/load-config.ts | 6 +++--- 3 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 packages/integrations/markdoc/src/astro-stub.ts diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index dbc2248d821c..3f2ba0cef9ce 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -21,7 +21,6 @@ "exports": { ".": "./dist/index.js", "./components": "./components/index.ts", - "./astro-stub": "./dist/astro-stub.js", "./experimental-assets-config": "./dist/experimental-assets-config.js", "./package.json": "./package.json" }, diff --git a/packages/integrations/markdoc/src/astro-stub.ts b/packages/integrations/markdoc/src/astro-stub.ts deleted file mode 100644 index 6f4ab4a830a0..000000000000 --- a/packages/integrations/markdoc/src/astro-stub.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Used by the build-time config loader. -// All `.astro` imports from the `markdoc.config.mjs` file -// are stubbed to return this. -export default true; diff --git a/packages/integrations/markdoc/src/load-config.ts b/packages/integrations/markdoc/src/load-config.ts index ac5890d8f897..fe9636195871 100644 --- a/packages/integrations/markdoc/src/load-config.ts +++ b/packages/integrations/markdoc/src/load-config.ts @@ -65,10 +65,10 @@ async function bundleConfigFile({ { name: 'stub-astro-imports', setup(build) { - build.onResolve({ filter: /.*\.astro$/ }, async ({ path: id, importer, kind }) => { - console.log({ id }); + build.onResolve({ filter: /.*\.astro$/ }, () => { return { - path: '@astrojs/markdoc/astro-stub', + // Stub with an unused default export + path: 'data:text/javascript,export default true', external: true, }; }); From b3216294bbef90f9131a7e462cf78c9da2629777 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:23:25 -0400 Subject: [PATCH 08/33] feat: pass through viteId to getRenderMod --- packages/astro/src/@types/astro.ts | 1 + packages/astro/src/content/vite-plugin-content-imports.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index a496a5aa04bb..e1d8176070ce 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1056,6 +1056,7 @@ export interface ContentEntryType { getRenderModule?( this: rollup.PluginContext, params: { + viteId: string; entry: ContentEntryModule; } ): rollup.LoadResult | Promise; diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index 7ad71b31e56f..4437f4fa0ae4 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -139,7 +139,7 @@ export const _internal = { }); } - return contentRenderer.bind(this)({ entry }); + return contentRenderer.bind(this)({ entry, viteId }); }, }); } From 74d904f48258515bf31d6fb396626cf5ca1ccb94 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:24:12 -0400 Subject: [PATCH 09/33] fix: add back $entry var with defaults convention --- packages/integrations/markdoc/package.json | 1 + .../markdoc/src/default-config.ts | 19 +++++++++++++++++++ packages/integrations/markdoc/src/index.ts | 16 ++++++++++------ 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 packages/integrations/markdoc/src/default-config.ts diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index 3f2ba0cef9ce..58fa9abcf9a1 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -21,6 +21,7 @@ "exports": { ".": "./dist/index.js", "./components": "./components/index.ts", + "./default-config": "./dist/default-config.js", "./experimental-assets-config": "./dist/experimental-assets-config.js", "./package.json": "./package.json" }, diff --git a/packages/integrations/markdoc/src/default-config.ts b/packages/integrations/markdoc/src/default-config.ts new file mode 100644 index 000000000000..336255955436 --- /dev/null +++ b/packages/integrations/markdoc/src/default-config.ts @@ -0,0 +1,19 @@ +import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc'; +import type { ContentEntryModule } from 'astro'; + +export function applyDefaultConfig({ + config, + entry, +}: { + config: MarkdocConfig; + entry: ContentEntryModule; +}): MarkdocConfig { + return { + ...config, + variables: { + entry, + ...config.variables, + }, + // TODO: heading ID calculation, Shiki syntax highlighting + }; +} diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index b16ba01d9cee..79f9df577673 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -14,6 +14,7 @@ import { // @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations. import { emitESMImage } from 'astro/assets'; import { loadMarkdocConfig } from './load-config.js'; +import { applyDefaultConfig } from './default-config.js'; type SetupHookParams = HookParameters<'astro:config:setup'> & { // `contentEntryType` is not a public API @@ -45,12 +46,13 @@ export default function markdocIntegration( addContentEntryType({ extensions: ['.mdoc'], getEntryInfo, - async getRenderModule({ entry }) { + async getRenderModule({ entry, viteId }) { validateRenderProperties(userMarkdocConfig, astroConfig); const ast = Markdoc.parse(entry.body); const pluginContext = this; + const markdocConfig = applyDefaultConfig({ entry, config: userMarkdocConfig }); - const validationErrors = Markdoc.validate(ast, userMarkdocConfig).filter((e) => { + const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => { // Ignore `variable-undefined` errors. // Variables can be configured at runtime, // so we cannot validate them at build time. @@ -74,7 +76,9 @@ export default function markdocIntegration( } const code = { - code: `import { jsx as h } from 'astro/jsx-runtime';${ + code: `import { jsx as h } from 'astro/jsx-runtime';import { applyDefaultConfig } from '@astrojs/markdoc/default-config';\nimport * as entry from ${JSON.stringify( + viteId + '?astroContent' + )};\n${ configLoadResult ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};` : '' @@ -85,13 +89,13 @@ export default function markdocIntegration( }\nimport { Renderer } from '@astrojs/markdoc/components';\nconst stringifiedAst = ${JSON.stringify( // Double stringify to encode *as* stringified JSON JSON.stringify(ast) - )};\nexport async function Content (props) {\n const config = ${ + )};\nexport async function Content (props) {\n const config = applyDefaultConfig({ entry, config: ${ configLoadResult ? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }' : '{ variables: props }' - };${ + } });\n${ astroConfig.experimental.assets - ? `config.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };` + ? `\nconfig.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };` : '' }\n return h(Renderer, { stringifiedAst, config }); };`, }; From 6c88258878bf3bda046f16e3978046b1539493c0 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:24:33 -0400 Subject: [PATCH 10/33] chore: remove unneeded validateRenderProps --- packages/integrations/markdoc/src/index.ts | 51 ---------------------- 1 file changed, 51 deletions(-) diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index 79f9df577673..93d2d968a79f 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -47,7 +47,6 @@ export default function markdocIntegration( extensions: ['.mdoc'], getEntryInfo, async getRenderModule({ entry, viteId }) { - validateRenderProperties(userMarkdocConfig, astroConfig); const ast = Markdoc.parse(entry.body); const pluginContext = this; const markdocConfig = applyDefaultConfig({ entry, config: userMarkdocConfig }); @@ -157,53 +156,3 @@ function shouldOptimizeImage(src: string) { // Optimize anything that is NOT external or an absolute path to `public/` return !isValidUrl(src) && !src.startsWith('/'); } - -function validateRenderProperties(markdocConfig: ReadonlyMarkdocConfig, 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; -}) { - 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(); -} From 3e374a9452622d03142552b87600046853d1cca9 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:25:08 -0400 Subject: [PATCH 11/33] chore: remove uneeded validateComponents --- packages/integrations/markdoc/src/utils.ts | 32 ---------------------- 1 file changed, 32 deletions(-) diff --git a/packages/integrations/markdoc/src/utils.ts b/packages/integrations/markdoc/src/utils.ts index 9d6e5af26665..0b31f6596e47 100644 --- a/packages/integrations/markdoc/src/utils.ts +++ b/packages/integrations/markdoc/src/utils.ts @@ -114,38 +114,6 @@ export function prependForwardSlash(str: string) { return str[0] === '/' ? str : '/' + str; } -export function validateComponentsProp(components: Record) { - try { - componentsPropValidator.parse(components); - } catch (e) { - throw new MarkdocError({ - message: - e instanceof z.ZodError - ? e.issues[0].message - : 'Invalid `components` prop. Ensure you are passing an object of components to ', - }); - } -} - -const componentsPropValidator = z.record( - z - .string() - .min(1, 'Invalid `components` prop. Component names cannot be empty!') - .refine( - (value) => isCapitalized(value), - (value) => ({ - message: `Invalid \`components\` prop: ${JSON.stringify( - value - )}. Component name must be capitalized. If you want to render HTML elements as components, try using a Markdoc node (https://docs.astro.build/en/guides/integrations-guide/markdoc/#render-markdoc-nodes--html-elements-as-astro-components)`, - }) - ), - z.any() -); - -export function isCapitalized(str: string) { - return str.length > 0 && str[0] === str[0].toUpperCase(); -} - export function isValidUrl(str: string): boolean { try { new URL(str); From 86c42fb987f7f0b8a2c62fb896bdabcff302e9d0 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:25:50 -0400 Subject: [PATCH 12/33] fix: remove userMarkdocConfig prop --- packages/integrations/markdoc/src/index.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index 93d2d968a79f..4cf713f24a84 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -1,16 +1,9 @@ -import type { Config as ReadonlyMarkdocConfig, Node } from '@markdoc/markdoc'; +import type { Node } 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 type * as rollup from 'rollup'; -import { - getAstroConfigPath, - isValidUrl, - MarkdocError, - parseFrontmatter, - prependForwardSlash, -} from './utils.js'; +import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from './utils.js'; // @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations. import { emitESMImage } from 'astro/assets'; import { loadMarkdocConfig } from './load-config.js'; @@ -22,9 +15,7 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & { addContentEntryType: (contentEntryType: ContentEntryType) => void; }; -export default function markdocIntegration( - userMarkdocConfig: ReadonlyMarkdocConfig = {} -): AstroIntegration { +export default function markdocIntegration(): AstroIntegration { return { name: '@astrojs/markdoc', hooks: { From 6b93e92a1db7714a79bbd1fc4b6bdf8a93d708dc Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:33:08 -0400 Subject: [PATCH 13/33] chore: add helpful error for legacy config --- packages/integrations/markdoc/src/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index 4cf713f24a84..f3f15a9b0642 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -15,7 +15,14 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & { addContentEntryType: (contentEntryType: ContentEntryType) => void; }; -export default function markdocIntegration(): AstroIntegration { +export default function markdocIntegration(legacyConfig: any): AstroIntegration { + if (legacyConfig) { + throw new MarkdocError({ + message: + 'Passing Markdoc config from your `astro.config` is no longer supported. Configuration should be exported from a `markdoc.config.mjs` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration', + }); + } + return { name: '@astrojs/markdoc', hooks: { From 7667ee5cd87ef3cbc0fb33f8b75b8fa93fd3f4ed Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:38:43 -0400 Subject: [PATCH 14/33] deps: kleur --- examples/with-markdoc/package.json | 3 ++- pnpm-lock.yaml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json index 9ca562fa3757..f3139284042a 100644 --- a/examples/with-markdoc/package.json +++ b/examples/with-markdoc/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@astrojs/markdoc": "^0.0.5", - "astro": "^2.1.7" + "astro": "^2.1.7", + "kleur": "^4.1.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0723be6ec783..4b67994d4f68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -330,9 +330,11 @@ importers: specifiers: '@astrojs/markdoc': ^0.0.5 astro: ^2.1.7 + kleur: ^4.1.5 dependencies: '@astrojs/markdoc': link:../../packages/integrations/markdoc astro: link:../../packages/astro + kleur: 4.1.5 examples/with-markdown-plugins: specifiers: From 639941f6d696ee0387eec2a9eedfb9f04d7a9af0 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:38:54 -0400 Subject: [PATCH 15/33] fix: add back `isCapitalized` --- packages/integrations/markdoc/src/utils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/integrations/markdoc/src/utils.ts b/packages/integrations/markdoc/src/utils.ts index 0b31f6596e47..66a8f0d7b18a 100644 --- a/packages/integrations/markdoc/src/utils.ts +++ b/packages/integrations/markdoc/src/utils.ts @@ -114,6 +114,10 @@ export function prependForwardSlash(str: string) { return str[0] === '/' ? str : '/' + str; } +export function isCapitalized(str: string) { + return str.length > 0 && str[0] === str[0].toUpperCase(); +} + export function isValidUrl(str: string): boolean { try { new URL(str); From 69070b28605bbacfa4c8a64fcbf2d9edb4cea027 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:39:12 -0400 Subject: [PATCH 16/33] fix: log instead of throw to avoid scary stacktrace --- packages/integrations/markdoc/src/index.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index f3f15a9b0642..2fe5820dc0f2 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -8,6 +8,7 @@ import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from import { emitESMImage } from 'astro/assets'; import { loadMarkdocConfig } from './load-config.js'; import { applyDefaultConfig } from './default-config.js'; +import { bold, red } from 'kleur/colors'; type SetupHookParams = HookParameters<'astro:config:setup'> & { // `contentEntryType` is not a public API @@ -17,12 +18,13 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & { export default function markdocIntegration(legacyConfig: any): AstroIntegration { if (legacyConfig) { - throw new MarkdocError({ - message: - 'Passing Markdoc config from your `astro.config` is no longer supported. Configuration should be exported from a `markdoc.config.mjs` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration', - }); + console.log( + `${red( + bold('[Markdoc]') + )} Passing Markdoc config from your \`astro.config\` is no longer supported. Configuration should be exported from a \`markdoc.config.mjs\` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration` + ); + process.exit(0); } - return { name: '@astrojs/markdoc', hooks: { From 8d046aad37a022dbbd9135c13b482a9ee598de0c Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:41:49 -0400 Subject: [PATCH 17/33] chore: delete more old logic (nice) --- .../integrations/markdoc/components/Renderer.astro | 12 ++---------- packages/integrations/markdoc/components/TreeNode.ts | 6 ------ packages/integrations/markdoc/src/utils.ts | 4 ---- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/packages/integrations/markdoc/components/Renderer.astro b/packages/integrations/markdoc/components/Renderer.astro index c407b25a7fa4..5e2b6833a55a 100644 --- a/packages/integrations/markdoc/components/Renderer.astro +++ b/packages/integrations/markdoc/components/Renderer.astro @@ -1,25 +1,17 @@ --- import type { Config } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; -import type { AstroInstance } from 'astro'; -import { validateComponentsProp } from '../dist/utils.js'; import { ComponentNode, createTreeNode } from './TreeNode.js'; type Props = { config: Config; stringifiedAst: string; - components?: Record; }; -const { stringifiedAst, config, components } = Astro.props as Props; +const { stringifiedAst, config } = Astro.props as Props; const ast = Markdoc.Ast.fromJSON(stringifiedAst); const content = Markdoc.transform(ast, config); - -// Will throw if components is invalid -if (components) { - validateComponentsProp(components); -} --- - + diff --git a/packages/integrations/markdoc/components/TreeNode.ts b/packages/integrations/markdoc/components/TreeNode.ts index 508b6efd0f3c..8a315858915e 100644 --- a/packages/integrations/markdoc/components/TreeNode.ts +++ b/packages/integrations/markdoc/components/TreeNode.ts @@ -2,7 +2,6 @@ import type { AstroInstance } from 'astro'; import type { RenderableTreeNode } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js'; -import { MarkdocError, isCapitalized } from '../dist/utils.js'; export type TreeNode = | { @@ -63,11 +62,6 @@ export function createTreeNode(node: RenderableTreeNode): TreeNode { props, children, }; - } else if (isCapitalized(node.name)) { - throw new MarkdocError({ - message: `Unable to render ${JSON.stringify(node.name)}.`, - hint: 'Did you add this to the "components" prop on your component?', - }); } else { return { type: 'element', diff --git a/packages/integrations/markdoc/src/utils.ts b/packages/integrations/markdoc/src/utils.ts index 66a8f0d7b18a..0b31f6596e47 100644 --- a/packages/integrations/markdoc/src/utils.ts +++ b/packages/integrations/markdoc/src/utils.ts @@ -114,10 +114,6 @@ export function prependForwardSlash(str: string) { return str[0] === '/' ? str : '/' + str; } -export function isCapitalized(str: string) { - return str.length > 0 && str[0] === str[0].toUpperCase(); -} - export function isValidUrl(str: string): boolean { try { new URL(str); From 76d1183aad4c4f6edeb76f82b4fb4dacc0cea586 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:43:27 -0400 Subject: [PATCH 18/33] chore: delete MORE unused utils --- packages/integrations/markdoc/src/utils.ts | 24 ---------------------- 1 file changed, 24 deletions(-) diff --git a/packages/integrations/markdoc/src/utils.ts b/packages/integrations/markdoc/src/utils.ts index 0b31f6596e47..95f84700c268 100644 --- a/packages/integrations/markdoc/src/utils.ts +++ b/packages/integrations/markdoc/src/utils.ts @@ -1,5 +1,3 @@ -import type { AstroInstance } from 'astro'; -import z from 'astro/zod'; import matter from 'gray-matter'; import type fsMod from 'node:fs'; import path from 'node:path'; @@ -85,28 +83,6 @@ interface ErrorProperties { frame?: string; } -/** - * Matches `search` function used for resolving `astro.config` files. - * Used by Markdoc for error handling. - * @see 'astro/src/core/config/config.ts' - */ -export function getAstroConfigPath(fs: typeof fsMod, root: string): string | undefined { - const paths = [ - 'astro.config.mjs', - 'astro.config.js', - 'astro.config.ts', - 'astro.config.mts', - 'astro.config.cjs', - 'astro.config.cts', - ].map((p) => path.join(root, p)); - - for (const file of paths) { - if (fs.existsSync(file)) { - return file; - } - } -} - /** * @see 'astro/src/core/path.ts' */ From 2415b246c0d654a74ce8397af5a84b7c647e27c2 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 08:47:03 -0400 Subject: [PATCH 19/33] chore: comment on separate assets config --- .../integrations/markdoc/src/experimental-assets-config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/integrations/markdoc/src/experimental-assets-config.ts b/packages/integrations/markdoc/src/experimental-assets-config.ts index edf315de83b8..f4e9a61e02e9 100644 --- a/packages/integrations/markdoc/src/experimental-assets-config.ts +++ b/packages/integrations/markdoc/src/experimental-assets-config.ts @@ -2,6 +2,9 @@ import type { Config as MarkdocConfig } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; import { Image } from 'astro:assets'; +// Separate module to avoid importing `astro:assets` when +// `experimental.assets` flag is not set in a project. +// TODO: merge with `./default-config.ts` when `experimental.assets` is baselined. export const experimentalAssetsConfig: MarkdocConfig = { nodes: { image: { From c68058754e388dd7b9ce0c65bad458f0fe9a2780 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 10:40:54 -0400 Subject: [PATCH 20/33] chore: remove console.log --- packages/integrations/markdoc/src/load-config.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/integrations/markdoc/src/load-config.ts b/packages/integrations/markdoc/src/load-config.ts index fe9636195871..7def716b417b 100644 --- a/packages/integrations/markdoc/src/load-config.ts +++ b/packages/integrations/markdoc/src/load-config.ts @@ -25,12 +25,8 @@ export async function loadMarkdocConfig(astroConfig: Pick) markdocConfigUrl, astroConfig, }); - - console.log({ code }); - const config: MarkdocConfig = await loadConfigFromBundledFile(astroConfig.root, code); - console.log({ config }); return { config, fileUrl: markdocConfigUrl, From d88a81edb60b66e788a696dd4ae00a0620a9749f Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 13:39:20 -0400 Subject: [PATCH 21/33] chore: general code cleanup --- .../markdoc/src/default-config.ts | 15 +++++----- .../markdoc/src/experimental-assets-config.ts | 4 +-- packages/integrations/markdoc/src/index.ts | 30 +++++++++++-------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/packages/integrations/markdoc/src/default-config.ts b/packages/integrations/markdoc/src/default-config.ts index 336255955436..16bd2c41f8cb 100644 --- a/packages/integrations/markdoc/src/default-config.ts +++ b/packages/integrations/markdoc/src/default-config.ts @@ -1,17 +1,16 @@ import type { ConfigType as MarkdocConfig } from '@markdoc/markdoc'; import type { ContentEntryModule } from 'astro'; -export function applyDefaultConfig({ - config, - entry, -}: { - config: MarkdocConfig; - entry: ContentEntryModule; -}): MarkdocConfig { +export function applyDefaultConfig( + config: MarkdocConfig, + ctx: { + entry: ContentEntryModule; + } +): MarkdocConfig { return { ...config, variables: { - entry, + entry: ctx.entry, ...config.variables, }, // TODO: heading ID calculation, Shiki syntax highlighting diff --git a/packages/integrations/markdoc/src/experimental-assets-config.ts b/packages/integrations/markdoc/src/experimental-assets-config.ts index f4e9a61e02e9..8015ab464559 100644 --- a/packages/integrations/markdoc/src/experimental-assets-config.ts +++ b/packages/integrations/markdoc/src/experimental-assets-config.ts @@ -2,8 +2,8 @@ import type { Config as MarkdocConfig } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; import { Image } from 'astro:assets'; -// Separate module to avoid importing `astro:assets` when -// `experimental.assets` flag is not set in a project. +// Separate module to only import `astro:assets` when +// `experimental.assets` flag is set in a project. // TODO: merge with `./default-config.ts` when `experimental.assets` is baselined. export const experimentalAssetsConfig: MarkdocConfig = { nodes: { diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts index 2fe5820dc0f2..368a43d4f45a 100644 --- a/packages/integrations/markdoc/src/index.ts +++ b/packages/integrations/markdoc/src/index.ts @@ -49,7 +49,7 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration async getRenderModule({ entry, viteId }) { const ast = Markdoc.parse(entry.body); const pluginContext = this; - const markdocConfig = applyDefaultConfig({ entry, config: userMarkdocConfig }); + const markdocConfig = applyDefaultConfig(userMarkdocConfig, { entry }); const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => { // Ignore `variable-undefined` errors. @@ -75,9 +75,10 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration } const code = { - code: `import { jsx as h } from 'astro/jsx-runtime';import { applyDefaultConfig } from '@astrojs/markdoc/default-config';\nimport * as entry from ${JSON.stringify( - viteId + '?astroContent' - )};\n${ + code: `import { jsx as h } from 'astro/jsx-runtime'; +import { applyDefaultConfig } from '@astrojs/markdoc/default-config'; +import { Renderer } from '@astrojs/markdoc/components'; +import * as entry from ${JSON.stringify(viteId + '?astroContent')};${ configLoadResult ? `\nimport userConfig from ${JSON.stringify(configLoadResult.fileUrl.pathname)};` : '' @@ -85,18 +86,21 @@ export default function markdocIntegration(legacyConfig: any): AstroIntegration astroConfig.experimental.assets ? `\nimport { experimentalAssetsConfig } from '@astrojs/markdoc/experimental-assets-config';` : '' - }\nimport { Renderer } from '@astrojs/markdoc/components';\nconst stringifiedAst = ${JSON.stringify( - // Double stringify to encode *as* stringified JSON - JSON.stringify(ast) - )};\nexport async function Content (props) {\n const config = applyDefaultConfig({ entry, config: ${ - configLoadResult - ? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }' - : '{ variables: props }' - } });\n${ + } +const stringifiedAst = ${JSON.stringify( + /* Double stringify to encode *as* stringified JSON */ JSON.stringify(ast) + )}; +export async function Content (props) { + const config = applyDefaultConfig(${ + configLoadResult + ? '{ ...userConfig, variables: { ...userConfig.variables, ...props } }' + : '{ variables: props }' + }, { entry });${ astroConfig.experimental.assets ? `\nconfig.nodes = { ...experimentalAssetsConfig.nodes, ...config.nodes };` : '' - }\n return h(Renderer, { stringifiedAst, config }); };`, + } + return h(Renderer, { stringifiedAst, config }); };`, }; return code; }, From 1826064a41c401edbe62e5ba7e199b0ea5fcbf68 Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 14:31:05 -0400 Subject: [PATCH 22/33] test: new render config --- packages/integrations/markdoc/package.json | 3 +- .../markdoc/test/content-collections.test.js | 196 +++--------------- .../fixtures/content-collections/package.json | 4 - .../src/content/blog/post-1.mdoc | 7 + .../src/content/blog/post-2.mdoc | 7 + .../src/content/blog/post-3.mdoc | 7 + .../src/pages/entry.json.js | 2 +- .../fixtures/render-simple/astro.config.mjs | 7 + .../test/fixtures/render-simple/package.json | 9 + .../src/content/blog/simple.mdoc | 0 .../src/pages/index.astro} | 3 +- .../render-with-components/astro.config.mjs | 7 + .../render-with-components/markdoc.config.mjs | 27 +++ .../render-with-components/package.json | 12 ++ .../src/components/Code.astro | 0 .../src/components/CustomMarquee.astro | 0 .../src/content/blog/with-components.mdoc | 0 .../src/pages/index.astro} | 8 +- .../render-with-config/astro.config.mjs | 7 + .../render-with-config/markdoc.config.mjs | 13 ++ .../fixtures/render-with-config/package.json | 9 + .../src/content/blog/with-config.mdoc | 0 .../src/pages/index.astro} | 4 +- .../integrations/markdoc/test/render.test.js | 118 +++++++++++ pnpm-lock.yaml | 34 ++- 25 files changed, 292 insertions(+), 192 deletions(-) create mode 100644 packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-1.mdoc create mode 100644 packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-2.mdoc create mode 100644 packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-3.mdoc create mode 100644 packages/integrations/markdoc/test/fixtures/render-simple/astro.config.mjs create mode 100644 packages/integrations/markdoc/test/fixtures/render-simple/package.json rename packages/integrations/markdoc/test/fixtures/{content-collections => render-simple}/src/content/blog/simple.mdoc (100%) rename packages/integrations/markdoc/test/fixtures/{content-collections/src/pages/content-simple.astro => render-simple/src/pages/index.astro} (92%) create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-components/astro.config.mjs create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-components/markdoc.config.mjs create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-components/package.json rename packages/integrations/markdoc/test/fixtures/{content-collections => render-with-components}/src/components/Code.astro (100%) rename packages/integrations/markdoc/test/fixtures/{content-collections => render-with-components}/src/components/CustomMarquee.astro (100%) rename packages/integrations/markdoc/test/fixtures/{content-collections => render-with-components}/src/content/blog/with-components.mdoc (100%) rename packages/integrations/markdoc/test/fixtures/{content-collections/src/pages/content-with-components.astro => render-with-components/src/pages/index.astro} (65%) create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-config/astro.config.mjs create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-config/markdoc.config.mjs create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-config/package.json rename packages/integrations/markdoc/test/fixtures/{content-collections => render-with-config}/src/content/blog/with-config.mdoc (100%) rename packages/integrations/markdoc/test/fixtures/{content-collections/src/pages/content-with-config.astro => render-with-config/src/pages/index.astro} (88%) create mode 100644 packages/integrations/markdoc/test/render.test.js diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index 58fa9abcf9a1..b532debbface 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -36,16 +36,17 @@ "@markdoc/markdoc": "^0.2.2", "esbuild": "^0.17.12", "gray-matter": "^4.0.3", + "kleur": "^4.1.5", "zod": "^3.17.3" }, "peerDependencies": { "astro": "workspace:*" }, "devDependencies": { - "astro": "workspace:*", "@types/chai": "^4.3.1", "@types/html-escaper": "^3.0.0", "@types/mocha": "^9.1.1", + "astro": "workspace:*", "astro-scripts": "workspace:*", "chai": "^4.3.6", "devalue": "^4.2.0", diff --git a/packages/integrations/markdoc/test/content-collections.test.js b/packages/integrations/markdoc/test/content-collections.test.js index 5822c181a651..aad389e0ccd2 100644 --- a/packages/integrations/markdoc/test/content-collections.test.js +++ b/packages/integrations/markdoc/test/content-collections.test.js @@ -1,4 +1,3 @@ -import { parseHTML } from 'linkedom'; import { parse as parseDevalue } from 'devalue'; import { expect } from 'chai'; import { loadFixture, fixLineEndings } from '../../../astro/test/test-utils.js'; @@ -37,70 +36,20 @@ describe('Markdoc - Content Collections', () => { it('loads entry', async () => { const res = await baseFixture.fetch('/entry.json'); const post = parseDevalue(await res.text()); - expect(formatPost(post)).to.deep.equal(simplePostEntry); + expect(formatPost(post)).to.deep.equal(post1Entry); }); it('loads collection', async () => { const res = await baseFixture.fetch('/collection.json'); const posts = parseDevalue(await res.text()); expect(posts).to.not.be.null; + expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([ - simplePostEntry, - withComponentsEntry, - withConfigEntry, + post1Entry, + post2Entry, + post3Entry, ]); }); - - it('renders content - simple', async () => { - const res = await baseFixture.fetch('/content-simple'); - const html = await res.text(); - const { document } = parseHTML(html); - const h2 = document.querySelector('h2'); - expect(h2.textContent).to.equal('Simple post'); - const p = document.querySelector('p'); - expect(p.textContent).to.equal('This is a simple Markdoc post.'); - }); - - it('renders content - with config', async () => { - const fixture = await getFixtureWithConfig(); - const server = await fixture.startDevServer(); - - const res = await fixture.fetch('/content-with-config'); - const html = await res.text(); - const { document } = parseHTML(html); - const h2 = document.querySelector('h2'); - expect(h2.textContent).to.equal('Post with config'); - const textContent = html; - - expect(textContent).to.not.include('Hello'); - expect(textContent).to.include('Hola'); - expect(textContent).to.include(`Konnichiwa`); - - await server.stop(); - }); - - it('renders content - with components', async () => { - const fixture = await getFixtureWithComponents(); - const server = await fixture.startDevServer(); - - const res = await fixture.fetch('/content-with-components'); - const html = await res.text(); - const { document } = parseHTML(html); - const h2 = document.querySelector('h2'); - expect(h2.textContent).to.equal('Post with components'); - - // Renders custom shortcode component - const marquee = document.querySelector('marquee'); - expect(marquee).to.not.be.null; - expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true); - - // Renders Astro Code component - const pre = document.querySelector('pre'); - expect(pre).to.not.be.null; - expect(pre.className).to.equal('astro-code'); - - await server.stop(); - }); }); describe('build', () => { @@ -111,7 +60,7 @@ describe('Markdoc - Content Collections', () => { it('loads entry', async () => { const res = await baseFixture.readFile('/entry.json'); const post = parseDevalue(res); - expect(formatPost(post)).to.deep.equal(simplePostEntry); + expect(formatPost(post)).to.deep.equal(post1Entry); }); it('loads collection', async () => { @@ -119,140 +68,43 @@ describe('Markdoc - Content Collections', () => { const posts = parseDevalue(res); expect(posts).to.not.be.null; expect(posts.sort().map((post) => formatPost(post))).to.deep.equal([ - simplePostEntry, - withComponentsEntry, - withConfigEntry, + post1Entry, + post2Entry, + post3Entry, ]); }); - - it('renders content - simple', async () => { - const html = await baseFixture.readFile('/content-simple/index.html'); - const { document } = parseHTML(html); - const h2 = document.querySelector('h2'); - expect(h2.textContent).to.equal('Simple post'); - const p = document.querySelector('p'); - expect(p.textContent).to.equal('This is a simple Markdoc post.'); - }); - - it('renders content - with config', async () => { - const fixture = await getFixtureWithConfig(); - await fixture.build(); - - const html = await fixture.readFile('/content-with-config/index.html'); - const { document } = parseHTML(html); - const h2 = document.querySelector('h2'); - expect(h2.textContent).to.equal('Post with config'); - const textContent = html; - - expect(textContent).to.not.include('Hello'); - expect(textContent).to.include('Hola'); - expect(textContent).to.include(`Konnichiwa`); - }); - - it('renders content - with components', async () => { - const fixture = await getFixtureWithComponents(); - await fixture.build(); - - const html = await fixture.readFile('/content-with-components/index.html'); - const { document } = parseHTML(html); - const h2 = document.querySelector('h2'); - expect(h2.textContent).to.equal('Post with components'); - - // Renders custom shortcode component - const marquee = document.querySelector('marquee'); - expect(marquee).to.not.be.null; - expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true); - - // Renders Astro Code component - const pre = document.querySelector('pre'); - expect(pre).to.not.be.null; - expect(pre.className).to.equal('astro-code'); - }); }); }); -function getFixtureWithConfig() { - return loadFixture({ - root, - integrations: [ - markdoc({ - variables: { - countries: ['ES', 'JP'], - }, - functions: { - includes: { - transform(parameters) { - const [array, value] = Object.values(parameters); - return Array.isArray(array) ? array.includes(value) : false; - }, - }, - }, - }), - ], - }); -} - -function getFixtureWithComponents() { - return loadFixture({ - root, - integrations: [ - markdoc({ - nodes: { - fence: { - render: 'Code', - attributes: { - language: { type: String }, - content: { type: String }, - }, - }, - }, - tags: { - mq: { - render: 'CustomMarquee', - attributes: { - direction: { - type: String, - default: 'left', - matches: ['left', 'right', 'up', 'down'], - errorLevel: 'critical', - }, - }, - }, - }, - }), - ], - }); -} - -const simplePostEntry = { - id: 'simple.mdoc', - slug: 'simple', +const post1Entry = { + id: 'post-1.mdoc', + slug: 'post-1', collection: 'blog', data: { schemaWorks: true, - title: 'Simple post', + title: 'Post 1', }, - body: '\n## Simple post\n\nThis is a simple Markdoc post.\n', + body: '\n## Post 1\n\nThis is the contents of post 1.\n', }; -const withComponentsEntry = { - id: 'with-components.mdoc', - slug: 'with-components', +const post2Entry = { + id: 'post-2.mdoc', + slug: 'post-2', collection: 'blog', data: { schemaWorks: true, - title: 'Post with components', + title: 'Post 2', }, - body: '\n## Post with components\n\nThis uses a custom marquee component with a shortcode:\n\n{% mq direction="right" %}\nI\'m a marquee too!\n{% /mq %}\n\nAnd a code component for code blocks:\n\n```js\nconst isRenderedWithShiki = true;\n```\n', + body: '\n## Post 2\n\nThis is the contents of post 2.\n', }; -const withConfigEntry = { - id: 'with-config.mdoc', - slug: 'with-config', +const post3Entry = { + id: 'post-3.mdoc', + slug: 'post-3', collection: 'blog', data: { schemaWorks: true, - title: 'Post with config', + title: 'Post 3', }, - body: '\n## Post with config\n\n{% if includes($countries, "EN") %} Hello {% /if %}\n{% if includes($countries, "ES") %} Hola {% /if %}\n{% if includes($countries, "JP") %} Konnichiwa {% /if %}\n', + body: '\n## Post 3\n\nThis is the contents of post 3.\n', }; diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/package.json b/packages/integrations/markdoc/test/fixtures/content-collections/package.json index a6403f49b22d..370b87957d24 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/package.json +++ b/packages/integrations/markdoc/test/fixtures/content-collections/package.json @@ -4,10 +4,6 @@ "private": true, "dependencies": { "@astrojs/markdoc": "workspace:*", - "@markdoc/markdoc": "^0.2.2", "astro": "workspace:*" - }, - "devDependencies": { - "shiki": "^0.11.1" } } diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-1.mdoc b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-1.mdoc new file mode 100644 index 000000000000..06c900963174 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-1.mdoc @@ -0,0 +1,7 @@ +--- +title: Post 1 +--- + +## Post 1 + +This is the contents of post 1. diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-2.mdoc b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-2.mdoc new file mode 100644 index 000000000000..cf4dc162fbf7 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-2.mdoc @@ -0,0 +1,7 @@ +--- +title: Post 2 +--- + +## Post 2 + +This is the contents of post 2. diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-3.mdoc b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-3.mdoc new file mode 100644 index 000000000000..6c601eb6508d --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/post-3.mdoc @@ -0,0 +1,7 @@ +--- +title: Post 3 +--- + +## Post 3 + +This is the contents of post 3. diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js index 842291826ad5..7899a757a929 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js +++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js @@ -3,7 +3,7 @@ import { stringify } from 'devalue'; import { stripRenderFn } from '../../utils.js'; export async function get() { - const post = await getEntryBySlug('blog', 'simple'); + const post = await getEntryBySlug('blog', 'post-1'); return { body: stringify(stripRenderFn(post)), }; diff --git a/packages/integrations/markdoc/test/fixtures/render-simple/astro.config.mjs b/packages/integrations/markdoc/test/fixtures/render-simple/astro.config.mjs new file mode 100644 index 000000000000..29d846359bb2 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-simple/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import markdoc from '@astrojs/markdoc'; + +// https://astro.build/config +export default defineConfig({ + integrations: [markdoc()], +}); diff --git a/packages/integrations/markdoc/test/fixtures/render-simple/package.json b/packages/integrations/markdoc/test/fixtures/render-simple/package.json new file mode 100644 index 000000000000..9354cdc58d9b --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-simple/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/markdoc-render-simple", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/markdoc": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/simple.mdoc b/packages/integrations/markdoc/test/fixtures/render-simple/src/content/blog/simple.mdoc similarity index 100% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/simple.mdoc rename to packages/integrations/markdoc/test/fixtures/render-simple/src/content/blog/simple.mdoc diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-simple.astro b/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro similarity index 92% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-simple.astro rename to packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro index effbbee1ce7b..940eef154005 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-simple.astro +++ b/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro @@ -1,5 +1,6 @@ --- import { getEntryBySlug } from "astro:content"; + const post = await getEntryBySlug('blog', 'simple'); const { Content } = await post.render(); --- @@ -10,7 +11,7 @@ const { Content } = await post.render(); - Content - Simple + Content diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/astro.config.mjs b/packages/integrations/markdoc/test/fixtures/render-with-components/astro.config.mjs new file mode 100644 index 000000000000..29d846359bb2 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-components/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import markdoc from '@astrojs/markdoc'; + +// https://astro.build/config +export default defineConfig({ + integrations: [markdoc()], +}); diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/markdoc.config.mjs b/packages/integrations/markdoc/test/fixtures/render-with-components/markdoc.config.mjs new file mode 100644 index 000000000000..dd190c3dd76e --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-components/markdoc.config.mjs @@ -0,0 +1,27 @@ +import Code from './src/components/Code.astro'; +import CustomMarquee from './src/components/CustomMarquee.astro'; + +export default { + nodes: { + fence: { + render: Code, + attributes: { + language: { type: String }, + content: { type: String }, + }, + }, + }, + tags: { + mq: { + render: CustomMarquee, + attributes: { + direction: { + type: String, + default: 'left', + matches: ['left', 'right', 'up', 'down'], + errorLevel: 'critical', + }, + }, + }, + }, +} diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/package.json b/packages/integrations/markdoc/test/fixtures/render-with-components/package.json new file mode 100644 index 000000000000..f14c97f0fd20 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-components/package.json @@ -0,0 +1,12 @@ +{ + "name": "@test/markdoc-render-with-components", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/markdoc": "workspace:*", + "astro": "workspace:*" + }, + "devDependencies": { + "shiki": "^0.11.1" + } +} diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/components/Code.astro b/packages/integrations/markdoc/test/fixtures/render-with-components/src/components/Code.astro similarity index 100% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/components/Code.astro rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/components/Code.astro diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/components/CustomMarquee.astro b/packages/integrations/markdoc/test/fixtures/render-with-components/src/components/CustomMarquee.astro similarity index 100% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/components/CustomMarquee.astro rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/components/CustomMarquee.astro diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/with-components.mdoc b/packages/integrations/markdoc/test/fixtures/render-with-components/src/content/blog/with-components.mdoc similarity index 100% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/with-components.mdoc rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/content/blog/with-components.mdoc diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-components.astro b/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro similarity index 65% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-components.astro rename to packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro index dfb9b1de5671..52239acce73e 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-components.astro +++ b/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro @@ -1,7 +1,5 @@ --- import { getEntryBySlug } from "astro:content"; -import Code from '../components/Code.astro'; -import CustomMarquee from '../components/CustomMarquee.astro'; const post = await getEntryBySlug('blog', 'with-components'); const { Content } = await post.render(); @@ -13,11 +11,9 @@ const { Content } = await post.render(); - Content - with components + Content - + diff --git a/packages/integrations/markdoc/test/fixtures/render-with-config/astro.config.mjs b/packages/integrations/markdoc/test/fixtures/render-with-config/astro.config.mjs new file mode 100644 index 000000000000..29d846359bb2 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-config/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import markdoc from '@astrojs/markdoc'; + +// https://astro.build/config +export default defineConfig({ + integrations: [markdoc()], +}); diff --git a/packages/integrations/markdoc/test/fixtures/render-with-config/markdoc.config.mjs b/packages/integrations/markdoc/test/fixtures/render-with-config/markdoc.config.mjs new file mode 100644 index 000000000000..4fe07c1c87ab --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-config/markdoc.config.mjs @@ -0,0 +1,13 @@ +export default { + variables: { + countries: ['ES', 'JP'], + }, + functions: { + includes: { + transform(parameters) { + const [array, value] = Object.values(parameters); + return Array.isArray(array) ? array.includes(value) : false; + }, + }, + }, +} diff --git a/packages/integrations/markdoc/test/fixtures/render-with-config/package.json b/packages/integrations/markdoc/test/fixtures/render-with-config/package.json new file mode 100644 index 000000000000..d4751388c603 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-config/package.json @@ -0,0 +1,9 @@ +{ + "name": "@test/markdoc-render-with-config", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/markdoc": "workspace:*", + "astro": "workspace:*" + } +} diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/with-config.mdoc b/packages/integrations/markdoc/test/fixtures/render-with-config/src/content/blog/with-config.mdoc similarity index 100% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/content/blog/with-config.mdoc rename to packages/integrations/markdoc/test/fixtures/render-with-config/src/content/blog/with-config.mdoc diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-config.astro b/packages/integrations/markdoc/test/fixtures/render-with-config/src/pages/index.astro similarity index 88% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-config.astro rename to packages/integrations/markdoc/test/fixtures/render-with-config/src/pages/index.astro index f37217a62fb8..e133706e8f9c 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/content-with-config.astro +++ b/packages/integrations/markdoc/test/fixtures/render-with-config/src/pages/index.astro @@ -11,9 +11,9 @@ const { Content } = await post.render(); - Content - with config + Content - + diff --git a/packages/integrations/markdoc/test/render.test.js b/packages/integrations/markdoc/test/render.test.js new file mode 100644 index 000000000000..985063784e66 --- /dev/null +++ b/packages/integrations/markdoc/test/render.test.js @@ -0,0 +1,118 @@ +import { parseHTML } from 'linkedom'; +import { expect } from 'chai'; +import { loadFixture } from '../../../astro/test/test-utils.js'; + +async function getFixture(name) { + return await loadFixture({ + root: new URL(`./fixtures/${name}/`, import.meta.url), + }); +} + +describe('Markdoc - render', () => { + describe('dev', () => { + it('renders content - simple', async () => { + const fixture = await getFixture('render-simple'); + const server = await fixture.startDevServer(); + + const res = await fixture.fetch('/'); + const html = await res.text(); + const { document } = parseHTML(html); + const h2 = document.querySelector('h2'); + expect(h2.textContent).to.equal('Simple post'); + const p = document.querySelector('p'); + expect(p.textContent).to.equal('This is a simple Markdoc post.'); + + await server.stop(); + }); + + it('renders content - with config', async () => { + const fixture = await getFixture('render-with-config'); + const server = await fixture.startDevServer(); + + const res = await fixture.fetch('/'); + const html = await res.text(); + const { document } = parseHTML(html); + const h2 = document.querySelector('h2'); + expect(h2.textContent).to.equal('Post with config'); + const textContent = html; + + expect(textContent).to.not.include('Hello'); + expect(textContent).to.include('Hola'); + expect(textContent).to.include(`Konnichiwa`); + + await server.stop(); + }); + + it('renders content - with components', async () => { + const fixture = await getFixture('render-with-components'); + const server = await fixture.startDevServer(); + + const res = await fixture.fetch('/'); + const html = await res.text(); + const { document } = parseHTML(html); + const h2 = document.querySelector('h2'); + expect(h2.textContent).to.equal('Post with components'); + + // Renders custom shortcode component + const marquee = document.querySelector('marquee'); + expect(marquee).to.not.be.null; + expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true); + + // Renders Astro Code component + const pre = document.querySelector('pre'); + expect(pre).to.not.be.null; + expect(pre.className).to.equal('astro-code'); + + await server.stop(); + }); + }); + + describe('build', () => { + it('renders content - simple', async () => { + const fixture = await getFixture('render-simple'); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + const h2 = document.querySelector('h2'); + expect(h2.textContent).to.equal('Simple post'); + const p = document.querySelector('p'); + expect(p.textContent).to.equal('This is a simple Markdoc post.'); + }); + + it('renders content - with config', async () => { + const fixture = await getFixture('render-with-config'); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + const h2 = document.querySelector('h2'); + expect(h2.textContent).to.equal('Post with config'); + const textContent = html; + + expect(textContent).to.not.include('Hello'); + expect(textContent).to.include('Hola'); + expect(textContent).to.include(`Konnichiwa`); + }); + + it('renders content - with components', async () => { + const fixture = await getFixture('render-with-components'); + await fixture.build(); + + const html = await fixture.readFile('/index.html'); + const { document } = parseHTML(html); + const h2 = document.querySelector('h2'); + expect(h2.textContent).to.equal('Post with components'); + + // Renders custom shortcode component + const marquee = document.querySelector('marquee'); + expect(marquee).to.not.be.null; + expect(marquee.hasAttribute('data-custom-marquee')).to.equal(true); + + // Renders Astro Code component + const pre = document.querySelector('pre'); + expect(pre).to.not.be.null; + expect(pre.className).to.equal('astro-code'); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b67994d4f68..9348dab690de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3081,6 +3081,7 @@ importers: devalue: ^4.2.0 esbuild: ^0.17.12 gray-matter: ^4.0.3 + kleur: ^4.1.5 linkedom: ^0.14.12 mocha: ^9.2.2 rollup: ^3.20.1 @@ -3090,6 +3091,7 @@ importers: '@markdoc/markdoc': 0.2.2 esbuild: 0.17.12 gray-matter: 4.0.3 + kleur: 4.1.5 zod: 3.20.6 devDependencies: '@types/chai': 4.3.4 @@ -3107,15 +3109,10 @@ importers: packages/integrations/markdoc/test/fixtures/content-collections: specifiers: '@astrojs/markdoc': workspace:* - '@markdoc/markdoc': ^0.2.2 astro: workspace:* - shiki: ^0.11.1 dependencies: '@astrojs/markdoc': link:../../.. - '@markdoc/markdoc': 0.2.2 astro: link:../../../../../astro - devDependencies: - shiki: 0.11.1 packages/integrations/markdoc/test/fixtures/entry-prop: specifiers: @@ -3133,6 +3130,33 @@ importers: '@astrojs/markdoc': link:../../.. astro: link:../../../../../astro + packages/integrations/markdoc/test/fixtures/render-simple: + specifiers: + '@astrojs/markdoc': workspace:* + astro: workspace:* + dependencies: + '@astrojs/markdoc': link:../../.. + astro: link:../../../../../astro + + packages/integrations/markdoc/test/fixtures/render-with-components: + specifiers: + '@astrojs/markdoc': workspace:* + astro: workspace:* + shiki: ^0.11.1 + dependencies: + '@astrojs/markdoc': link:../../.. + astro: link:../../../../../astro + devDependencies: + shiki: 0.11.1 + + packages/integrations/markdoc/test/fixtures/render-with-config: + specifiers: + '@astrojs/markdoc': workspace:* + astro: workspace:* + dependencies: + '@astrojs/markdoc': link:../../.. + astro: link:../../../../../astro + packages/integrations/mdx: specifiers: '@astrojs/markdown-remark': ^2.1.2 From b0a791afe547b815faa5bb0a93ec57779fc7a6af Mon Sep 17 00:00:00 2001 From: bholmesdev Date: Fri, 24 Mar 2023 15:02:52 -0400 Subject: [PATCH 23/33] docs: new README --- packages/integrations/markdoc/README.md | 256 ++++++++++-------------- 1 file changed, 110 insertions(+), 146 deletions(-) diff --git a/packages/integrations/markdoc/README.md b/packages/integrations/markdoc/README.md index 76291e763dbf..eb332d497f95 100644 --- a/packages/integrations/markdoc/README.md +++ b/packages/integrations/markdoc/README.md @@ -99,53 +99,46 @@ const { Content } = await entry.render(); ### Using components -You can add Astro and UI framework components (React, Vue, Svelte, etc.) to your Markdoc using both [Markdoc tags][markdoc-tags] and HTML element [nodes][markdoc-nodes]. +You can add Astro components to your Markdoc using both [Markdoc tags][markdoc-tags] and HTML element [nodes][markdoc-nodes]. #### Render Markdoc tags as Astro components -You may configure [Markdoc tags][markdoc-tags] that map to components. You can configure a new tag from your `astro.config` using the `tags` attribute. +You may configure [Markdoc tags][markdoc-tags] that map to components. You can configure a new tag by creating a `markdoc.config.mjs|ts` file at the root of your project and configuring the `tag` attribute. +This example renders an `Aside` component, and allows a `type` prop to be passed as a string: ```js -// astro.config.mjs -import { defineConfig } from 'astro/config'; -import markdoc from '@astrojs/markdoc'; - -// https://astro.build/config -export default defineConfig({ - integrations: [ - markdoc({ - tags: { - aside: { - render: 'Aside', - attributes: { - // Component props as attribute definitions - // See Markdoc's documentation on defining attributes - // https://markdoc.dev/docs/attributes#defining-attributes - type: { type: String }, - } - }, - }, - }), - ], -}); +// markdoc.config.mjs +import { defineMarkdocConfig } from '@astrojs/markdoc'; +import Aside from './src/components/Aside.astro'; + +export default defineMarkdocConfig({ + tags: { + aside: { + render: Aside, + attributes: { + // Markdoc requires type defs for each attribute. + // These should mirror the `Props` type of the component + // you are rendering. + // See Markdoc's documentation on defining attributes + // https://markdoc.dev/docs/attributes#defining-attributes + type: { type: String }, + } + }, + }, +}) ``` -Then, you can wire this render name (`'Aside'`) to a component from the `components` prop via the `` component. Note the object key name (`Aside` in this case) should match the render name: +This component can now be used in your Markdoc files with the `{% aside %}` tag. Children will be passed to your component's default slot: +```md +# Welcome to Markdoc πŸ‘‹ -```astro ---- -import { getEntryBySlug } from 'astro:content'; -import Aside from '../components/Aside.astro'; +{% aside type="tip" %} -const entry = await getEntryBySlug('docs', 'why-markdoc'); -const { Content } = await entry.render(); ---- +Use tags like this fancy "aside" to add some *flair* to your docs. - +{% /aside %} ``` #### Render Markdoc nodes / HTML elements as Astro components @@ -153,46 +146,22 @@ const { Content } = await entry.render(); You may also want to map standard HTML elements like headings and paragraphs to components. For this, you can configure a custom [Markdoc node][markdoc-nodes]. This example overrides Markdoc's `heading` node to render a `Heading` component, passing the built-in `level` attribute as a prop: ```js -// astro.config.mjs -import { defineConfig } from 'astro/config'; -import markdoc from '@astrojs/markdoc'; - -// https://astro.build/config -export default defineConfig({ - integrations: [ - markdoc({ - nodes: { - heading: { - render: 'Heading', - // Markdoc requires type defs for each attribute. - // These should mirror the `Props` type of the component - // you are rendering. - // See Markdoc's documentation on defining attributes - // https://markdoc.dev/docs/attributes#defining-attributes - attributes: { - level: { type: String }, - } - }, - }, - }), - ], -}); -``` - -Now, you can map the string passed to render (`'Heading'` in this example) to a component import. This is configured from the `` component used to render your Markdoc using the `components` prop: - -```astro ---- -import { getEntryBySlug } from 'astro:content'; -import Heading from '../components/Heading.astro'; - -const entry = await getEntryBySlug('docs', 'why-markdoc'); -const { Content } = await entry.render(); ---- - - +// markdoc.config.mjs +import { defineMarkdocConfig } from '@astrojs/markdoc'; +import Heading from './src/components/Heading.astro'; + +export default defineMarkdocConfig({ + nodes: { + heading: { + render: Heading, + attributes: { + // Pass the attributes from Markdoc's default heading node + // as component props. + level: { type: String }, + } + }, + }, +}) ``` Now, all Markdown headings will render with the `Heading.astro` component. This example uses a level 3 heading, automatically passing `level: 3` as the component prop: @@ -215,26 +184,26 @@ This example wraps a `Aside.tsx` component with a `ClientAside.astro` wrapper: import Aside from './Aside'; --- -