summaryrefslogtreecommitdiff
path: root/packages/integrations
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations')
-rw-r--r--packages/integrations/mdx/README.md87
-rw-r--r--packages/integrations/mdx/package.json2
-rw-r--r--packages/integrations/mdx/src/index.ts28
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js9
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx6
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js9
-rw-r--r--packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx6
-rw-r--r--packages/integrations/mdx/test/mdx-frontmatter.js43
8 files changed, 185 insertions, 5 deletions
diff --git a/packages/integrations/mdx/README.md b/packages/integrations/mdx/README.md
index 541529f9f..13eb94187 100644
--- a/packages/integrations/mdx/README.md
+++ b/packages/integrations/mdx/README.md
@@ -78,6 +78,62 @@ To write your first MDX page in Astro, head to our [UI framework documentation][
Also check our [Astro Integration Documentation][astro-integration] for more on integrations.
+### Variables
+
+MDX supports `export` statements to add variables to your templates. These variables are accessible both from the template itself _and_ as named properties when importing the template somewhere else.
+
+For instance, you can export a `title` field from an MDX page or component to use as a heading with `{JSX expressions}`:
+
+```mdx
+export const title = 'My first MDX post'
+
+# {title}
+```
+
+This `title` will be accessible from `import` and [glob](https://docs.astro.build/en/reference/api-reference/#astroglob) statements as well:
+
+```astro
+---
+// src/pages/index.astro
+const posts = await Astro.glob('./*.mdx');
+---
+
+{posts.map(post => <p>{post.title}</p>)}
+```
+
+See [the official "how MDX works" guide](https://mdxjs.com/docs/using-mdx/#how-mdx-works) for more on MDX variables.
+
+### Frontmatter
+
+Astro also supports YAML-based frontmatter out-of-the-box using the [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter) plugin. By default, all variables declared in a frontmatter fence (`---`) will be accessible via the `frontmatter` export. See the `frontmatterOptions` configuration to customize this behavior.
+
+For example, we can add a `title` and `publishDate` to an MDX page or component like so:
+
+```mdx
+---
+title: 'My first MDX post'
+publishDate: '21 September 2022'
+---
+
+# {frontmatter.title}
+```
+
+Now, this `title` and `publishDate` will be accessible from `import` and [glob](https://docs.astro.build/en/reference/api-reference/#astroglob) statements via the `frontmatter` property. This matches the behavior of [plain markdown in Astro](https://docs.astro.build/en/reference/api-reference/#markdown-files) as well!
+
+```astro
+---
+// src/pages/index.astro
+const posts = await Astro.glob('./*.mdx');
+---
+
+{posts.map(post => (
+ <Fragment>
+ <h2>{post.frontmatter.title}</h2>
+ <time>{post.frontmatter.publishDate}</time>
+ </Fragment>
+))}
+```
+
## Configuration
<details>
@@ -140,6 +196,37 @@ export default {
```
</details>
+<details>
+ <summary><strong>frontmatterOptions</strong></summary>
+
+**Default:** `{ name: 'frontmatter' }`
+
+We use [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter) to parse YAML-based frontmatter in your MDX files. If you want to override our default configuration or extend remark-mdx-frontmatter (ex. to [apply a custom frontmatter parser](https://github.com/remcohaszing/remark-mdx-frontmatter#parsers)), you can supply a `frontmatterOptions` configuration.
+
+For example, say you want to access frontmatter as root-level variables without a nested `frontmatter` object. You can override the [`name` configuration option](https://github.com/remcohaszing/remark-mdx-frontmatter#name) like so:
+
+```js
+// astro.config.mjs
+export default {
+ integrations: [mdx({
+ frontmatterOptions: {
+ name: '',
+ }
+ })],
+}
+```
+
+```mdx
+---
+title: I'm just a variable now!
+---
+
+# {title}
+```
+
+See the [remark-mdx-frontmatter README](https://github.com/remcohaszing/remark-mdx-frontmatter#options) for a complete list of options.
+</details>
+
## Examples
- The [Astro MDX example](https://github.com/withastro/astro/tree/latest/examples/with-mdx) shows how to use MDX files in your Astro project.
diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json
index 51036b0e1..8084495f8 100644
--- a/packages/integrations/mdx/package.json
+++ b/packages/integrations/mdx/package.json
@@ -32,7 +32,9 @@
"dependencies": {
"@mdx-js/rollup": "^2.1.1",
"es-module-lexer": "^0.10.5",
+ "remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
+ "remark-mdx-frontmatter": "^2.0.2",
"remark-smartypants": "^2.0.0"
},
"devDependencies": {
diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts
index d2525b6e9..d91f71c4b 100644
--- a/packages/integrations/mdx/src/index.ts
+++ b/packages/integrations/mdx/src/index.ts
@@ -1,8 +1,11 @@
import mdxPlugin, { Options as MdxRollupPluginOptions } from '@mdx-js/rollup';
import type { AstroIntegration } from 'astro';
+import type { RemarkMdxFrontmatterOptions } from 'remark-mdx-frontmatter';
import { parse as parseESM } from 'es-module-lexer';
import remarkGfm from 'remark-gfm';
import remarkSmartypants from 'remark-smartypants';
+import remarkFrontmatter from 'remark-frontmatter';
+import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
import { getFileInfo } from './utils.js';
type WithExtends<T> = T | { extends: T };
@@ -10,14 +13,20 @@ type WithExtends<T> = T | { extends: T };
type MdxOptions = {
remarkPlugins?: WithExtends<MdxRollupPluginOptions['remarkPlugins']>;
rehypePlugins?: WithExtends<MdxRollupPluginOptions['rehypePlugins']>;
-};
+ /**
+ * Configure the remark-mdx-frontmatter plugin
+ * @see https://github.com/remcohaszing/remark-mdx-frontmatter#options for a full list of options
+ * @default {{ name: 'frontmatter' }}
+ */
+ frontmatterOptions?: RemarkMdxFrontmatterOptions;
+}
const DEFAULT_REMARK_PLUGINS = [remarkGfm, remarkSmartypants];
function handleExtends<T>(
config: WithExtends<T[] | undefined>,
- defaults: T[] = []
-): T[] | undefined {
+ defaults: T[] = [],
+): T[] {
if (Array.isArray(config)) return config;
return [...defaults, ...(config?.extends ?? [])];
@@ -35,9 +44,18 @@ export default function mdx(mdxOptions: MdxOptions = {}): AstroIntegration {
{
enforce: 'pre',
...mdxPlugin({
- remarkPlugins: handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS),
+ remarkPlugins: [
+ ...handleExtends(mdxOptions.remarkPlugins, DEFAULT_REMARK_PLUGINS),
+ // Frontmatter plugins should always be applied!
+ // We can revisit this if a strong use case to *remove*
+ // YAML frontmatter via config is reported.
+ remarkFrontmatter,
+ [remarkMdxFrontmatter, {
+ name: 'frontmatter',
+ ...mdxOptions.frontmatterOptions,
+ }],
+ ],
rehypePlugins: handleExtends(mdxOptions.rehypePlugins),
- // place these after so the user can't override
jsx: true,
jsxImportSource: 'astro',
// Note: disable `.md` support
diff --git a/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js
new file mode 100644
index 000000000..2f8155ada
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/glob.json.js
@@ -0,0 +1,9 @@
+export async function get() {
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
+
+ return {
+ body: JSON.stringify({
+ titles: Object.values(mdxPages ?? {}).map(v => v?.customFrontmatter?.title),
+ })
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx
new file mode 100644
index 000000000..e3c789149
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-custom-frontmatter-name/src/pages/index.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Using YAML frontmatter'
+illThrowIfIDontExist: "Oh no, that's scary!"
+---
+
+# {customFrontmatter.illThrowIfIDontExist}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js
new file mode 100644
index 000000000..a1d6a4ac4
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/glob.json.js
@@ -0,0 +1,9 @@
+export async function get() {
+ const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
+
+ return {
+ body: JSON.stringify({
+ titles: Object.values(mdxPages ?? {}).map(v => v?.frontmatter?.title),
+ })
+ }
+}
diff --git a/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx
new file mode 100644
index 000000000..333026f85
--- /dev/null
+++ b/packages/integrations/mdx/test/fixtures/mdx-frontmatter/src/pages/index.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Using YAML frontmatter'
+illThrowIfIDontExist: "Oh no, that's scary!"
+---
+
+# {frontmatter.illThrowIfIDontExist}
diff --git a/packages/integrations/mdx/test/mdx-frontmatter.js b/packages/integrations/mdx/test/mdx-frontmatter.js
new file mode 100644
index 000000000..1d8ec36f6
--- /dev/null
+++ b/packages/integrations/mdx/test/mdx-frontmatter.js
@@ -0,0 +1,43 @@
+import mdx from '@astrojs/mdx';
+
+import { expect } from 'chai';
+import { loadFixture } from '../../../astro/test/test-utils.js';
+
+const FIXTURE_ROOT = new URL('./fixtures/mdx-frontmatter/', import.meta.url);
+
+describe('MDX frontmatter', () => {
+ it('builds when "frontmatter.property" is in JSX expression', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ await fixture.build();
+ expect(true).to.equal(true);
+ });
+
+ it('extracts frontmatter to "frontmatter" export', async () => {
+ const fixture = await loadFixture({
+ root: FIXTURE_ROOT,
+ integrations: [mdx()],
+ });
+ await fixture.build();
+
+ const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
+ expect(titles).to.include('Using YAML frontmatter');
+ });
+
+ it('extracts frontmatter to "customFrontmatter" export when configured', async () => {
+ const fixture = await loadFixture({
+ root: new URL('./fixtures/mdx-custom-frontmatter-name/', import.meta.url),
+ integrations: [mdx({
+ frontmatterOptions: {
+ name: 'customFrontmatter',
+ },
+ })],
+ });
+ await fixture.build();
+
+ const { titles } = JSON.parse(await fixture.readFile('/glob.json'));
+ expect(titles).to.include('Using YAML frontmatter');
+ });
+});