-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Sarah Rainsberger <[email protected]>
- Loading branch information
1 parent
20a9792
commit ea16570
Showing
15 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@astrojs/mdx': patch | ||
--- | ||
|
||
Add `optimize` option for faster builds and rendering |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
packages/integrations/mdx/src/rehype-optimize-static.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { visit } from 'estree-util-visit'; | ||
import { toHtml } from 'hast-util-to-html'; | ||
|
||
// accessing untyped hast and mdx types | ||
type Node = any; | ||
|
||
export interface OptimizeOptions { | ||
customComponentNames?: string[]; | ||
} | ||
|
||
const exportConstComponentsRe = /export\s+const\s+components\s*=/; | ||
|
||
/** | ||
* For MDX only, collapse static subtrees of the hast into `set:html`. Subtrees | ||
* do not include any MDX elements. | ||
* | ||
* This optimization reduces the JS output as more content are represented as a | ||
* string instead, which also reduces the AST size that Rollup holds in memory. | ||
*/ | ||
export function rehypeOptimizeStatic(options?: OptimizeOptions) { | ||
return (tree: any) => { | ||
// A set of non-static components to avoid collapsing when walking the tree | ||
// as they need to be preserved as JSX to be rendered dynamically. | ||
const customComponentNames = new Set<string>(options?.customComponentNames); | ||
|
||
// Find `export const components = { ... }` and get it's object's keys to be | ||
// populated into `customComponentNames`. This configuration is used to render | ||
// some HTML elements as custom components, and we also want to avoid collapsing them. | ||
for (const child of tree.children) { | ||
if (child.type === 'mdxjsEsm' && exportConstComponentsRe.test(child.value)) { | ||
// Try to loosely get the object property nodes | ||
const objectPropertyNodes = child.data.estree.body[0]?.declarations?.[0]?.init?.properties; | ||
if (objectPropertyNodes) { | ||
for (const objectPropertyNode of objectPropertyNodes) { | ||
const componentName = objectPropertyNode.key?.name ?? objectPropertyNode.key?.value; | ||
if (componentName) { | ||
customComponentNames.add(componentName); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// All possible elements that could be the root of a subtree | ||
const allPossibleElements = new Set<Node>(); | ||
// The current collapsible element stack while traversing the tree | ||
const elementStack: Node[] = []; | ||
|
||
visit(tree, { | ||
enter(node) { | ||
// @ts-expect-error read tagName naively | ||
const isCustomComponent = node.tagName && customComponentNames.has(node.tagName); | ||
// For nodes that can't be optimized, eliminate all elements in the | ||
// `elementStack` from the `allPossibleElements` set. | ||
if (node.type.startsWith('mdx') || isCustomComponent) { | ||
for (const el of elementStack) { | ||
allPossibleElements.delete(el); | ||
} | ||
// Micro-optimization: While this destroys the meaning of an element | ||
// stack for this node, things will still work but we won't repeatedly | ||
// run the above for other nodes anymore. If this is confusing, you can | ||
// comment out the code below when reading. | ||
elementStack.length = 0; | ||
} | ||
// For possible subtree root nodes, record them in `elementStack` and | ||
// `allPossibleElements` to be used in the "leave" hook below. | ||
if (node.type === 'element' || node.type === 'mdxJsxFlowElement') { | ||
elementStack.push(node); | ||
allPossibleElements.add(node); | ||
} | ||
}, | ||
leave(node, _, __, parents) { | ||
// Do the reverse of the if condition above, popping the `elementStack`, | ||
// and consolidating `allPossibleElements` as a subtree root. | ||
if (node.type === 'element' || node.type === 'mdxJsxFlowElement') { | ||
elementStack.pop(); | ||
// Many possible elements could be part of a subtree, in order to find | ||
// the root, we check the parent of the element we're popping. If the | ||
// parent exists in `allPossibleElements`, then we're definitely not | ||
// the root, so remove ourselves. This will work retroactively as we | ||
// climb back up the tree. | ||
const parent = parents[parents.length - 1]; | ||
if (allPossibleElements.has(parent)) { | ||
allPossibleElements.delete(node); | ||
} | ||
} | ||
}, | ||
}); | ||
|
||
// For all possible subtree roots, collapse them into `set:html` and | ||
// strip of their children | ||
for (const el of allPossibleElements) { | ||
if (el.type === 'mdxJsxFlowElement') { | ||
el.attributes.push({ | ||
type: 'mdxJsxAttribute', | ||
name: 'set:html', | ||
value: toHtml(el.children), | ||
}); | ||
} else { | ||
el.properties['set:html'] = toHtml(el.children); | ||
} | ||
el.children = []; | ||
} | ||
}; | ||
} |
9 changes: 9 additions & 0 deletions
9
packages/integrations/mdx/test/fixtures/mdx-optimize/astro.config.mjs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import mdx from '@astrojs/mdx'; | ||
|
||
export default { | ||
integrations: [mdx({ | ||
optimize: { | ||
customComponentNames: ['strong'] | ||
} | ||
})] | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/integrations/mdx/test/fixtures/mdx-optimize/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"name": "@test/mdx-optimize", | ||
"private": true, | ||
"dependencies": { | ||
"@astrojs/mdx": "workspace:*", | ||
"astro": "workspace:*" | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Blockquote.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<blockquote {...Astro.props} class="custom-blockquote"> | ||
<slot /> | ||
</blockquote> |
3 changes: 3 additions & 0 deletions
3
packages/integrations/mdx/test/fixtures/mdx-optimize/src/components/Strong.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<strong {...Astro.props} class="custom-strong"> | ||
<slot /> | ||
</strong> |
3 changes: 3 additions & 0 deletions
3
packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/_imported.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
I once heard a very **inspirational** quote: | ||
|
||
> I like pancakes |
15 changes: 15 additions & 0 deletions
15
packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/import.astro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
--- | ||
import { Content, components } from './index.mdx' | ||
import Strong from '../components/Strong.astro' | ||
--- | ||
|
||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Import MDX component</title> | ||
</head> | ||
<body> | ||
<h1>Astro page</h1> | ||
<Content components={{ ...components, strong: Strong }} /> | ||
</body> | ||
</html> |
15 changes: 15 additions & 0 deletions
15
packages/integrations/mdx/test/fixtures/mdx-optimize/src/pages/index.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import Blockquote from '../components/Blockquote.astro' | ||
|
||
export const components = { | ||
blockquote: Blockquote | ||
} | ||
|
||
# MDX page | ||
|
||
I once heard a very inspirational quote: | ||
|
||
> I like pancakes | ||
```js | ||
const pancakes = 'yummy' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { expect } from 'chai'; | ||
import { parseHTML } from 'linkedom'; | ||
import { loadFixture } from '../../../astro/test/test-utils.js'; | ||
|
||
const FIXTURE_ROOT = new URL('./fixtures/mdx-optimize/', import.meta.url); | ||
|
||
describe('MDX optimize', () => { | ||
let fixture; | ||
before(async () => { | ||
fixture = await loadFixture({ | ||
root: FIXTURE_ROOT, | ||
}); | ||
await fixture.build(); | ||
}); | ||
|
||
it('renders an MDX page fine', async () => { | ||
const html = await fixture.readFile('/index.html'); | ||
const { document } = parseHTML(html); | ||
|
||
expect(document.querySelector('h1').textContent).include('MDX page'); | ||
expect(document.querySelector('p').textContent).include( | ||
'I once heard a very inspirational quote:' | ||
); | ||
|
||
const blockquote = document.querySelector('blockquote.custom-blockquote'); | ||
expect(blockquote).to.not.be.null; | ||
expect(blockquote.textContent).to.include('I like pancakes'); | ||
|
||
const code = document.querySelector('pre.astro-code'); | ||
expect(code).to.not.be.null; | ||
expect(code.textContent).to.include(`const pancakes = 'yummy'`); | ||
}); | ||
|
||
it('renders an Astro page that imports MDX fine', async () => { | ||
const html = await fixture.readFile('/import/index.html'); | ||
const { document } = parseHTML(html); | ||
|
||
expect(document.querySelector('h1').textContent).include('Astro page'); | ||
expect(document.querySelector('p').textContent).include( | ||
'I once heard a very inspirational quote:' | ||
); | ||
|
||
const blockquote = document.querySelector('blockquote.custom-blockquote'); | ||
expect(blockquote).to.not.be.null; | ||
expect(blockquote.textContent).to.include('I like pancakes'); | ||
}); | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.