From 0cd8d41b7d9d22b9d8b141880c819fb22c721ef6 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 9 Aug 2024 16:46:42 +0100 Subject: [PATCH 1/2] Add markdoc support to content layer --- packages/astro/package.json | 1 + packages/astro/src/content/loaders/glob.ts | 22 ++--- .../astro/test/content-layer-markdoc.test.js | 87 +++++++++++++++++++ .../astro/test/content-layer-render.test.js | 6 +- .../content-layer-markdoc/astro.config.mjs | 9 ++ .../content/_nested.mdoc | 3 + .../content/blog/_counter.mdoc | 7 ++ .../content/blog/with-components.mdoc | 19 ++++ .../content-layer-markdoc/markdoc.config.ts | 32 +++++++ .../content-layer-markdoc/package.json | 11 +++ .../src/components/Code.astro | 12 +++ .../src/components/Counter.tsx | 10 +++ .../src/components/CounterWrapper.astro | 5 ++ .../src/components/CustomMarquee.astro | 1 + .../src/components/DeeplyNested.astro | 5 ++ .../src/content/config.ts | 11 +++ .../src/pages/index.astro | 19 ++++ pnpm-lock.yaml | 23 +++++ 18 files changed, 268 insertions(+), 15 deletions(-) create mode 100644 packages/astro/test/content-layer-markdoc.test.js create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/astro.config.mjs create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/content/_nested.mdoc create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/content/blog/_counter.mdoc create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/content/blog/with-components.mdoc create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/markdoc.config.ts create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/package.json create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/src/components/Code.astro create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/src/components/Counter.tsx create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/src/components/CounterWrapper.astro create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/src/components/CustomMarquee.astro create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/src/components/DeeplyNested.astro create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/src/content/config.ts create mode 100644 packages/astro/test/fixtures/content-layer-markdoc/src/pages/index.astro diff --git a/packages/astro/package.json b/packages/astro/package.json index 474faa1f410d..40e16eff5532 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -163,6 +163,7 @@ "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", + "linkedom": "^0.18.4", "magic-string": "^0.30.11", "micromatch": "^4.0.7", "mrmime": "^2.0.0", diff --git a/packages/astro/src/content/loaders/glob.ts b/packages/astro/src/content/loaders/glob.ts index 578f13057baf..4a4ecbcac8d3 100644 --- a/packages/astro/src/content/loaders/glob.ts +++ b/packages/astro/src/content/loaders/glob.ts @@ -128,17 +128,7 @@ export function glob(globOptions: GlobOptions): Loader { data, filePath, }); - - if (entryType.extensions.includes('.mdx')) { - store.set({ - id, - data: parsedData, - body, - filePath: relativePath, - digest, - deferredRender: true, - }); - } else if (entryType.getRenderFunction) { + if (entryType.getRenderFunction) { let render = renderFunctionByContentType.get(entryType); if (!render) { render = await entryType.getRenderFunction(settings); @@ -170,6 +160,16 @@ export function glob(globOptions: GlobOptions): Loader { if (rendered?.metadata?.imagePaths?.length) { store.addAssetImports(rendered.metadata.imagePaths, relativePath); } + // todo: add an explicit way to opt in to deferred rendering + } else if ('contentModuleTypes' in entryType) { + store.set({ + id, + data: parsedData, + body, + filePath: relativePath, + digest, + deferredRender: true, + }); } else { store.set({ id, data: parsedData, body, filePath: relativePath, digest }); } diff --git a/packages/astro/test/content-layer-markdoc.test.js b/packages/astro/test/content-layer-markdoc.test.js new file mode 100644 index 000000000000..0e741603a76b --- /dev/null +++ b/packages/astro/test/content-layer-markdoc.test.js @@ -0,0 +1,87 @@ +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import { parseHTML } from 'linkedom'; +import { loadFixture } from './test-utils.js'; + +describe('Content layer markdoc', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/content-layer-markdoc/', + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('renders content - with components', async () => { + const res = await fixture.fetch('/'); + const html = await res.text(); + + renderComponentsChecks(html); + }); + + it('renders content - with components inside partials', async () => { + const res = await fixture.fetch('/'); + const html = await res.text(); + + renderComponentsInsidePartialsChecks(html); + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('renders content - with components', async () => { + const html = await fixture.readFile('/index.html'); + + renderComponentsChecks(html); + }); + + it('renders content - with components inside partials', async () => { + const html = await fixture.readFile('/index.html'); + + renderComponentsInsidePartialsChecks(html); + }); + }); +}); + +/** @param {string} html */ +function renderComponentsChecks(html) { + const { document } = parseHTML(html); + const h2 = document.querySelector('h2'); + assert.equal(h2.textContent, 'Post with components'); + + // Renders custom shortcode component + const marquee = document.querySelector('marquee'); + assert.notEqual(marquee, null); + assert.equal(marquee.hasAttribute('data-custom-marquee'), true); + + // Renders Astro Code component + const pre = document.querySelector('pre'); + assert.notEqual(pre, null); + assert.equal(pre.className, 'astro-code github-dark'); +} + +/** @param {string} html */ +function renderComponentsInsidePartialsChecks(html) { + const { document } = parseHTML(html); + // renders Counter.tsx + const button = document.querySelector('#counter'); + assert.equal(button.textContent, '1'); + + // renders DeeplyNested.astro + const deeplyNested = document.querySelector('#deeply-nested'); + assert.equal(deeplyNested.textContent, 'Deeply nested partial'); +} diff --git a/packages/astro/test/content-layer-render.test.js b/packages/astro/test/content-layer-render.test.js index ab38bb1da2b4..fa743e719ca4 100644 --- a/packages/astro/test/content-layer-render.test.js +++ b/packages/astro/test/content-layer-render.test.js @@ -2,7 +2,7 @@ import assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { loadFixture } from './test-utils.js'; -describe('Content Layer dev', () => { +describe('Content Layer MDX rendering dev', () => { /** @type {import("./test-utils.js").Fixture} */ let fixture; @@ -10,7 +10,6 @@ describe('Content Layer dev', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/content-layer-rendering/', - cacheDir: './fixtures/content-layer-rendering/.cache', }); devServer = await fixture.startDevServer(); }); @@ -27,13 +26,12 @@ describe('Content Layer dev', () => { }); }); -describe('Content Layer build', () => { +describe('Content Layer MDX rendering build', () => { /** @type {import("./test-utils.js").Fixture} */ let fixture; before(async () => { fixture = await loadFixture({ root: './fixtures/content-layer-rendering/', - cacheDir: './fixtures/content-layer-rendering/.cache', }); await fixture.build(); }); diff --git a/packages/astro/test/fixtures/content-layer-markdoc/astro.config.mjs b/packages/astro/test/fixtures/content-layer-markdoc/astro.config.mjs new file mode 100644 index 000000000000..bbb19ad302db --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/astro.config.mjs @@ -0,0 +1,9 @@ +import markdoc from '@astrojs/markdoc'; +import preact from '@astrojs/preact'; +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({ + integrations: [markdoc(), preact()], + experimental: { contentLayer: true } +}); diff --git a/packages/astro/test/fixtures/content-layer-markdoc/content/_nested.mdoc b/packages/astro/test/fixtures/content-layer-markdoc/content/_nested.mdoc new file mode 100644 index 000000000000..68f529280124 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/content/_nested.mdoc @@ -0,0 +1,3 @@ +Render components from a deeply nested partial: + +{% deeply-nested /%} diff --git a/packages/astro/test/fixtures/content-layer-markdoc/content/blog/_counter.mdoc b/packages/astro/test/fixtures/content-layer-markdoc/content/blog/_counter.mdoc new file mode 100644 index 000000000000..4a015695c68e --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/content/blog/_counter.mdoc @@ -0,0 +1,7 @@ +# Hello from a partial! + +Render a component from a partial: + +{% counter /%} + +{% partial file="../_nested.mdoc" /%} diff --git a/packages/astro/test/fixtures/content-layer-markdoc/content/blog/with-components.mdoc b/packages/astro/test/fixtures/content-layer-markdoc/content/blog/with-components.mdoc new file mode 100644 index 000000000000..eb7d20426e59 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/content/blog/with-components.mdoc @@ -0,0 +1,19 @@ +--- +title: Post with components +--- + +## Post with components + +This uses a custom marquee component with a shortcode: + +{% marquee-element direction="right" %} +I'm a marquee too! +{% /marquee-element %} + +{% partial file="_counter.mdoc" /%} + +And a code component for code blocks: + +```js +const isRenderedWithShiki = true; +``` diff --git a/packages/astro/test/fixtures/content-layer-markdoc/markdoc.config.ts b/packages/astro/test/fixtures/content-layer-markdoc/markdoc.config.ts new file mode 100644 index 000000000000..6093ec5938a5 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/markdoc.config.ts @@ -0,0 +1,32 @@ +import { Markdoc, component, defineMarkdocConfig } from '@astrojs/markdoc/config'; + +export default defineMarkdocConfig({ + nodes: { + fence: { + render: component('./src/components/Code.astro'), + attributes: { + language: { type: String }, + content: { type: String }, + }, + }, + }, + tags: { + 'marquee-element': { + render: component('./src/components/CustomMarquee.astro'), + attributes: { + direction: { + type: String, + default: 'left', + matches: ['left', 'right', 'up', 'down'], + errorLevel: 'critical', + }, + }, + }, + counter: { + render: component('./src/components/CounterWrapper.astro'), + }, + 'deeply-nested': { + render: component('./src/components/DeeplyNested.astro'), + }, + }, +}); diff --git a/packages/astro/test/fixtures/content-layer-markdoc/package.json b/packages/astro/test/fixtures/content-layer-markdoc/package.json new file mode 100644 index 000000000000..91ca2f8c9c00 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/package.json @@ -0,0 +1,11 @@ +{ + "name": "@test/content-layer-markdoc", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/markdoc": "workspace:*", + "@astrojs/preact": "workspace:*", + "astro": "workspace:*", + "preact": "^10.23.1" + } +} \ No newline at end of file diff --git a/packages/astro/test/fixtures/content-layer-markdoc/src/components/Code.astro b/packages/astro/test/fixtures/content-layer-markdoc/src/components/Code.astro new file mode 100644 index 000000000000..18bf1399f22e --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/src/components/Code.astro @@ -0,0 +1,12 @@ +--- +import { Code } from 'astro/components'; + +type Props = { + content: string; + language: string; +} + +const { content, language } = Astro.props as Props; +--- + + diff --git a/packages/astro/test/fixtures/content-layer-markdoc/src/components/Counter.tsx b/packages/astro/test/fixtures/content-layer-markdoc/src/components/Counter.tsx new file mode 100644 index 000000000000..f1e239718cf8 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/src/components/Counter.tsx @@ -0,0 +1,10 @@ +import { useState } from 'preact/hooks'; + +export default function Counter() { + const [count, setCount] = useState(1); + return ( + + ); +} diff --git a/packages/astro/test/fixtures/content-layer-markdoc/src/components/CounterWrapper.astro b/packages/astro/test/fixtures/content-layer-markdoc/src/components/CounterWrapper.astro new file mode 100644 index 000000000000..e45ac6438152 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/src/components/CounterWrapper.astro @@ -0,0 +1,5 @@ +--- +import Counter from './Counter'; +--- + + diff --git a/packages/astro/test/fixtures/content-layer-markdoc/src/components/CustomMarquee.astro b/packages/astro/test/fixtures/content-layer-markdoc/src/components/CustomMarquee.astro new file mode 100644 index 000000000000..3108b997354c --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/src/components/CustomMarquee.astro @@ -0,0 +1 @@ + diff --git a/packages/astro/test/fixtures/content-layer-markdoc/src/components/DeeplyNested.astro b/packages/astro/test/fixtures/content-layer-markdoc/src/components/DeeplyNested.astro new file mode 100644 index 000000000000..eb23f675a0f6 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/src/components/DeeplyNested.astro @@ -0,0 +1,5 @@ +--- + +--- + +

Deeply nested partial

diff --git a/packages/astro/test/fixtures/content-layer-markdoc/src/content/config.ts b/packages/astro/test/fixtures/content-layer-markdoc/src/content/config.ts new file mode 100644 index 000000000000..18ead9aa161f --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/src/content/config.ts @@ -0,0 +1,11 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ + pattern: '*.mdoc', + base: 'content/blog', + }), +}); + +export const collections = { blog }; diff --git a/packages/astro/test/fixtures/content-layer-markdoc/src/pages/index.astro b/packages/astro/test/fixtures/content-layer-markdoc/src/pages/index.astro new file mode 100644 index 000000000000..5c7747eef923 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer-markdoc/src/pages/index.astro @@ -0,0 +1,19 @@ +--- +import { getEntry, render } from "astro:content"; + +const post = await getEntry('blog', 'with-components'); +const { Content } = await render(post); +--- + + + + + + + + Content + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4045a204d104..e664b2635550 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -675,6 +675,9 @@ importers: kleur: specifier: ^4.1.5 version: 4.1.5 + linkedom: + specifier: ^0.18.4 + version: 0.18.4 magic-string: specifier: ^0.30.11 version: 0.30.11 @@ -2722,6 +2725,21 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/content-layer-markdoc: + dependencies: + '@astrojs/markdoc': + specifier: workspace:* + version: link:../../../../integrations/markdoc + '@astrojs/preact': + specifier: workspace:* + version: link:../../../../integrations/preact + astro: + specifier: workspace:* + version: link:../../.. + preact: + specifier: ^10.23.1 + version: 10.23.1 + packages/astro/test/fixtures/content-layer-rendering: dependencies: '@astrojs/mdx': @@ -9423,6 +9441,7 @@ packages: libsql@0.3.12: resolution: {integrity: sha512-to30hj8O3DjS97wpbKN6ERZ8k66MN1IaOfFLR6oHqd25GMiPJ/ZX0VaZ7w+TsPmxcFS3p71qArj/hiedCyvXCg==} + cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lilconfig@2.1.0: @@ -11434,6 +11453,9 @@ packages: resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==} peerDependencies: vue: '>=3.2.13' + peerDependenciesMeta: + vue: + optional: true vite@5.3.5: resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} @@ -18091,6 +18113,7 @@ snapshots: vite-svg-loader@5.1.0(vue@3.4.35(typescript@5.5.4)): dependencies: svgo: 3.2.0 + optionalDependencies: vue: 3.4.35(typescript@5.5.4) vite@5.3.5(@types/node@18.19.31)(sass@1.77.8): From 39f6fd380eed0b3d274cd4a20246992e7481a4fa Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Fri, 9 Aug 2024 17:19:02 +0100 Subject: [PATCH 2/2] Switch test to cheerio --- packages/astro/package.json | 3 +-- .../astro/test/content-layer-markdoc.test.js | 27 ++++++++++--------- pnpm-lock.yaml | 8 ------ 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 40e16eff5532..5d227208d346 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -163,7 +163,6 @@ "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", - "linkedom": "^0.18.4", "magic-string": "^0.30.11", "micromatch": "^4.0.7", "mrmime": "^2.0.0", @@ -240,4 +239,4 @@ "publishConfig": { "provenance": true } -} +} \ No newline at end of file diff --git a/packages/astro/test/content-layer-markdoc.test.js b/packages/astro/test/content-layer-markdoc.test.js index 0e741603a76b..c5279b9e7522 100644 --- a/packages/astro/test/content-layer-markdoc.test.js +++ b/packages/astro/test/content-layer-markdoc.test.js @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; -import { parseHTML } from 'linkedom'; +import * as cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; describe('Content layer markdoc', () => { @@ -59,29 +59,30 @@ describe('Content layer markdoc', () => { /** @param {string} html */ function renderComponentsChecks(html) { - const { document } = parseHTML(html); - const h2 = document.querySelector('h2'); - assert.equal(h2.textContent, 'Post with components'); + const $ = cheerio.load(html); + const h2 = $('h2'); + assert.equal(h2.text(), 'Post with components'); // Renders custom shortcode component - const marquee = document.querySelector('marquee'); + const marquee = $('marquee'); assert.notEqual(marquee, null); - assert.equal(marquee.hasAttribute('data-custom-marquee'), true); + assert.equal(marquee.attr('data-custom-marquee'), ''); // Renders Astro Code component - const pre = document.querySelector('pre'); + const pre = $('pre'); assert.notEqual(pre, null); - assert.equal(pre.className, 'astro-code github-dark'); + assert.ok(pre.hasClass('github-dark')); + assert.ok(pre.hasClass('astro-code')); } /** @param {string} html */ function renderComponentsInsidePartialsChecks(html) { - const { document } = parseHTML(html); + const $ = cheerio.load(html); // renders Counter.tsx - const button = document.querySelector('#counter'); - assert.equal(button.textContent, '1'); + const button = $('#counter'); + assert.equal(button.text(), '1'); // renders DeeplyNested.astro - const deeplyNested = document.querySelector('#deeply-nested'); - assert.equal(deeplyNested.textContent, 'Deeply nested partial'); + const deeplyNested = $('#deeply-nested'); + assert.equal(deeplyNested.text(), 'Deeply nested partial'); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e664b2635550..274af7480447 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -675,9 +675,6 @@ importers: kleur: specifier: ^4.1.5 version: 4.1.5 - linkedom: - specifier: ^0.18.4 - version: 0.18.4 magic-string: specifier: ^0.30.11 version: 0.30.11 @@ -9441,7 +9438,6 @@ packages: libsql@0.3.12: resolution: {integrity: sha512-to30hj8O3DjS97wpbKN6ERZ8k66MN1IaOfFLR6oHqd25GMiPJ/ZX0VaZ7w+TsPmxcFS3p71qArj/hiedCyvXCg==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lilconfig@2.1.0: @@ -11453,9 +11449,6 @@ packages: resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==} peerDependencies: vue: '>=3.2.13' - peerDependenciesMeta: - vue: - optional: true vite@5.3.5: resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==} @@ -18113,7 +18106,6 @@ snapshots: vite-svg-loader@5.1.0(vue@3.4.35(typescript@5.5.4)): dependencies: svgo: 3.2.0 - optionalDependencies: vue: 3.4.35(typescript@5.5.4) vite@5.3.5(@types/node@18.19.31)(sass@1.77.8):