Fix: components in imported markdown (#3398)

* test: add with-components to astro-markdown fixture

* fix: markdown pathname with /@fs prefix

* feat: add loadMetadata helper for md

* feat: fix components in Astro.glob results!

* fix: md import path

* Revert "feat: add loadMetadata helper for md"

This reverts commit 76cf96f4be3d0e19589f84025c0131352d0b6cc8.

* fix: add back $$loadMetadata helper

* feat: add second comp framework to md test

* chore: core/render/dev lint

* chore: changeset

* fix: short circuit if mod doesn't have metadata

* fix: skip mod graph preloading in dev

* refactor: make md metadata check recursive

* refactor: extract metadata helper to util

* fix: remove unecessary mod graph query

* fix: move md import flag to util for deno bundling issue

* fix: remove 'dev' mode from test utils build

* feat: add global hashset for seen metadata

* refactor: flip Promise.all to for await for perf!

* Revert bc I was wrong! "refactor: flip Promise.all to for await for perf!"

This reverts commit da8a6873f5.
This commit is contained in:
Ben Holmes 2022-05-20 15:03:21 -04:00 committed by GitHub
parent 195f3f7eb7
commit fb5572bebd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 128 additions and 11 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix component usage in imported markdown files

View file

@ -1,5 +1,5 @@
import { fileURLToPath } from 'url';
import type * as vite from 'vite';
import type { HtmlTagDescriptor, ViteDevServer } from 'vite';
import type {
AstroConfig,
AstroRenderer,
@ -15,9 +15,9 @@ import { prependForwardSlash } from '../../../core/path.js';
import { RouteCache } from '../route-cache.js';
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
import { getStylesForURL } from './css.js';
import { getHmrScript } from './hmr.js';
import { injectTags } from './html.js';
import { isBuildingToSSR } from '../../util.js';
import { collectMdMetadata } from '../util.js';
export interface SSROptions {
/** an instance of the AstroConfig */
@ -37,7 +37,7 @@ export interface SSROptions {
/** pass in route cache because SSR cant manage cache-busting */
routeCache: RouteCache;
/** Vite instance */
viteServer: vite.ViteDevServer;
viteServer: ViteDevServer;
/** Request */
request: Request;
}
@ -51,7 +51,7 @@ export type RenderResponse =
const svelteStylesRE = /svelte\?svelte&type=style/;
async function loadRenderer(
viteServer: vite.ViteDevServer,
viteServer: ViteDevServer,
renderer: AstroRenderer
): Promise<SSRLoadedRenderer> {
// Vite modules can be out-of-date when using an un-resolved url
@ -65,7 +65,7 @@ async function loadRenderer(
}
export async function loadRenderers(
viteServer: vite.ViteDevServer,
viteServer: ViteDevServer,
astroConfig: AstroConfig
): Promise<SSRLoadedRenderer[]> {
return Promise.all(astroConfig._ctx.renderers.map((r) => loadRenderer(viteServer, r)));
@ -80,6 +80,15 @@ export async function preload({
const renderers = await loadRenderers(viteServer, astroConfig);
// Load the module from the Vite SSR Runtime.
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
if (viteServer.config.mode === 'development' || !mod?.$$metadata) {
return [renderers, mod];
}
// append all nested markdown metadata to mod.$$metadata
const modGraph = await viteServer.moduleGraph.getModuleByUrl(fileURLToPath(filePath));
if (modGraph) {
await collectMdMetadata(mod.$$metadata, modGraph, viteServer);
}
return [renderers, mod];
}
@ -179,7 +188,7 @@ export async function render(
}
// inject tags
const tags: vite.HtmlTagDescriptor[] = [];
const tags: HtmlTagDescriptor[] = [];
// add injected tags
let html = injectTags(content.html, tags);

View file

@ -1,4 +1,6 @@
import npath from 'path-browserify';
import type { ModuleNode, ViteDevServer } from 'vite';
import type { Metadata } from '../../runtime/server/metadata.js';
/** Normalize URL to its canonical form */
export function createCanonicalURL(url: string, base?: string): URL {
@ -30,9 +32,59 @@ export const STYLE_EXTENSIONS = new Set([
'.less',
]);
// duplicate const from vite-plugin-markdown
// can't import directly due to Deno bundling issue
// (node fs import failing during prod builds)
const MARKDOWN_IMPORT_FLAG = '?mdImport';
const cssRe = new RegExp(
`\\.(${Array.from(STYLE_EXTENSIONS)
.map((s) => s.slice(1))
.join('|')})($|\\?)`
);
export const isCSSRequest = (request: string): boolean => cssRe.test(request);
// During prod builds, some modules have dependencies we should preload by hand
// Ex. markdown files imported asynchronously or via Astro.glob(...)
// This calls each md file's $$loadMetadata to discover those dependencies
// and writes all results to the input `metadata` object
const seenMdMetadata = new Set<string>();
export async function collectMdMetadata(
metadata: Metadata,
modGraph: ModuleNode,
viteServer: ViteDevServer,
) {
const importedModules = [...(modGraph?.importedModules ?? [])];
await Promise.all(
importedModules.map(async (importedModule) => {
// recursively check for importedModules
if (!importedModule.id || seenMdMetadata.has(importedModule.id)) return;
seenMdMetadata.add(importedModule.id);
await collectMdMetadata(metadata, importedModule, viteServer);
if (!importedModule?.id?.endsWith(MARKDOWN_IMPORT_FLAG)) return;
const mdSSRMod = await viteServer.ssrLoadModule(importedModule.id);
const mdMetadata = (await mdSSRMod.$$loadMetadata?.()) as Metadata;
if (!mdMetadata) return;
for (let mdMod of mdMetadata.modules) {
mdMod.specifier = mdMetadata.resolvePath(mdMod.specifier);
metadata.modules.push(mdMod);
}
for (let mdHoisted of mdMetadata.hoisted) {
metadata.hoisted.push(mdHoisted);
}
for (let mdHydrated of mdMetadata.hydratedComponents) {
metadata.hydratedComponents.push(mdHydrated);
}
for (let mdClientOnly of mdMetadata.clientOnlyComponents) {
metadata.clientOnlyComponents.push(mdClientOnly);
}
for (let mdHydrationDirective of mdMetadata.hydrationDirectives) {
metadata.hydrationDirectives.add(mdHydrationDirective);
}
})
);
}

View file

@ -89,6 +89,10 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
export const frontmatter = ${JSON.stringify(frontmatter)};
export const file = ${JSON.stringify(fileId)};
export const url = ${JSON.stringify(fileUrl)};
export function $$loadMetadata() {
return load().then((m) => m.$$metadata)
}
// Deferred
export default async function load() {
@ -109,10 +113,10 @@ export default function markdown({ config }: AstroPluginOptions): Plugin {
// directly as a page in Vite, or it was a deferred render from a JS module.
// This returns the compiled markdown -> astro component that renders to HTML.
if (id.endsWith('.md')) {
const source = await fs.promises.readFile(id, 'utf8');
const filename = normalizeFilename(id);
const source = await fs.promises.readFile(filename, 'utf8');
const renderOpts = config.markdown;
const filename = normalizeFilename(id);
const fileUrl = new URL(`file://${filename}`);
const isPage = fileUrl.pathname.startsWith(resolvePages(config).pathname);
const hasInjectedScript = isPage && config._ctx.scripts.some((s) => s.stage === 'page-ssr');
@ -142,7 +146,7 @@ ${setup}`.trim();
// Transform from `.astro` to valid `.ts`
let { code: tsResult } = await transform(astroResult, {
pathname: fileUrl.pathname.slice(config.root.pathname.length - 1),
pathname: '/@fs' + prependForwardSlash(fileUrl.pathname),
projectRoot: config.root.toString(),
site: config.site ? new URL(config.base, config.site).toString() : undefined,
sourcefile: id,

View file

@ -1,7 +1,8 @@
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import svelte from "@astrojs/svelte";
// https://astro.build/config
export default defineConfig({
integrations: [preact()],
integrations: [preact(), svelte()]
});

View file

@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@astrojs/preact": "workspace:*",
"@astrojs/svelte": "workspace:*",
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,11 @@
<script>
let cool = false
</script>
<button on:click={() => cool = true}>This is cool right? {cool}</button>
<style>
button {
background: green;
}
</style>

View file

@ -0,0 +1,6 @@
---
---
## Plain jane
I am plain markdown!

View file

@ -0,0 +1,17 @@
---
setup: |
import Counter from '../components/Counter.jsx'
import Hello from '../components/Hello.jsx'
import SvelteButton from '../components/SvelteButton.svelte'
---
## With components
### Non-hydrated
<Hello name="Astro Naut" />
### Hydrated
<Counter client:load />
<SvelteButton client:load />

View file

@ -0,0 +1,9 @@
---
import Layout from '../../layouts/content.astro'
const posts = await Astro.glob('../../imported-md/*.md')
---
<Layout>
{posts.map(({ Content }) => <Content />)}
</Layout>

View file

@ -118,7 +118,7 @@ export async function loadFixture(inlineConfig) {
let devServer;
return {
build: (opts = {}) => build(config, { mode: 'development', logging, telemetry, ...opts }),
build: (opts = {}) => build(config, { logging, telemetry, ...opts }),
startDevServer: async (opts = {}) => {
devServer = await dev(config, { logging, telemetry, ...opts });
config.server.port = devServer.address.port; // update port

View file

@ -854,9 +854,11 @@ importers:
packages/astro/test/fixtures/astro-markdown:
specifiers:
'@astrojs/preact': workspace:*
'@astrojs/svelte': workspace:*
astro: workspace:*
dependencies:
'@astrojs/preact': link:../../../../integrations/preact
'@astrojs/svelte': link:../../../../integrations/svelte
astro: link:../../..
packages/astro/test/fixtures/astro-markdown-css: