summaryrefslogtreecommitdiff
path: root/packages/integrations/markdoc/src
diff options
context:
space:
mode:
authorGravatar Ben Holmes <hey@bholmes.dev> 2023-05-31 19:18:07 -0400
committerGravatar GitHub <noreply@github.com> 2023-05-31 19:18:07 -0400
commit339529fc820bac2d514b63198ecf54a1d88c0917 (patch)
tree8b553c0df30c1aa60a037fb915bcf7fdfaea945f /packages/integrations/markdoc/src
parentc4c086e5e70bdd347e1c7601212a04b4f523578e (diff)
downloadastro-339529fc820bac2d514b63198ecf54a1d88c0917.tar.gz
astro-339529fc820bac2d514b63198ecf54a1d88c0917.tar.zst
astro-339529fc820bac2d514b63198ecf54a1d88c0917.zip
Markdoc asset bleed, second try (#7185)
* Revert "revert: markdoc asset bleed (#7178)" This reverts commit 57e65d247f67de61bcc3a585c2254feb61ed2e74. * fix: missing result param on `renderUniqueStylesheet` * test: bundled styles (fails!) * fix: use `type: 'external'` for links * fix: split Astro components from markdoc config * test: style bleed (it fails...) * chore: remove unused util * fix: revert entry change * Stop traversing the graph when you encounter a propagated asset * chore: cleanup unused `entry` prop * refactor: add isPropagatedAssetsMod check * chore: remove unused import * chore: changeset * Normalize path using vite * Update packages/integrations/markdoc/src/index.ts Co-authored-by: Ben Holmes <hey@bholmes.dev> --------- Co-authored-by: Matthew Phillips <matthew@skypack.dev> Co-authored-by: bholmesdev <bholmesdev@gmail.com> Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
Diffstat (limited to 'packages/integrations/markdoc/src')
-rw-r--r--packages/integrations/markdoc/src/heading-ids.ts7
-rw-r--r--packages/integrations/markdoc/src/index.ts110
-rw-r--r--packages/integrations/markdoc/src/runtime.ts8
-rw-r--r--packages/integrations/markdoc/src/utils.ts31
4 files changed, 126 insertions, 30 deletions
diff --git a/packages/integrations/markdoc/src/heading-ids.ts b/packages/integrations/markdoc/src/heading-ids.ts
index 5c2f197f2..5e54af9a7 100644
--- a/packages/integrations/markdoc/src/heading-ids.ts
+++ b/packages/integrations/markdoc/src/heading-ids.ts
@@ -47,13 +47,14 @@ export const heading: Schema = {
const slug = getSlug(attributes, children, config.ctx.headingSlugger);
const render = config.nodes?.heading?.render ?? `h${level}`;
+
const tagProps =
// For components, pass down `level` as a prop,
// alongside `__collectHeading` for our `headings` collector.
// Avoid accidentally rendering `level` as an HTML attribute otherwise!
- typeof render === 'function'
- ? { ...attributes, id: slug, __collectHeading: true, level }
- : { ...attributes, id: slug };
+ typeof render === 'string'
+ ? { ...attributes, id: slug }
+ : { ...attributes, id: slug, __collectHeading: true, level };
return new Markdoc.Tag(render, tagProps, children);
},
diff --git a/packages/integrations/markdoc/src/index.ts b/packages/integrations/markdoc/src/index.ts
index f33c3a1be..5bcd8dff3 100644
--- a/packages/integrations/markdoc/src/index.ts
+++ b/packages/integrations/markdoc/src/index.ts
@@ -4,7 +4,15 @@ import Markdoc from '@markdoc/markdoc';
import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
import fs from 'node:fs';
import { fileURLToPath, pathToFileURL } from 'node:url';
-import { isValidUrl, MarkdocError, parseFrontmatter, prependForwardSlash } from './utils.js';
+import {
+ createNameHash,
+ hasContentFlag,
+ isValidUrl,
+ MarkdocError,
+ parseFrontmatter,
+ prependForwardSlash,
+ PROPAGATED_ASSET_FLAG,
+} from './utils.js';
// @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations.
import { emitESMImage } from 'astro/assets';
import { bold, red, yellow } from 'kleur/colors';
@@ -12,6 +20,7 @@ import path from 'node:path';
import type * as rollup from 'rollup';
import { loadMarkdocConfig, type MarkdocConfigResult } from './load-config.js';
import { setupConfig } from './runtime.js';
+import { normalizePath } from 'vite';
type SetupHookParams = HookParameters<'astro:config:setup'> & {
// `contentEntryType` is not a public API
@@ -35,6 +44,7 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
process.exit(0);
}
let markdocConfigResult: MarkdocConfigResult | undefined;
+ let markdocConfigResultId = '';
return {
name: '@astrojs/markdoc',
hooks: {
@@ -45,15 +55,10 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
addContentEntryType,
} = params as SetupHookParams;
- updateConfig({
- vite: {
- ssr: {
- external: ['@astrojs/markdoc/prism', '@astrojs/markdoc/shiki'],
- },
- },
- });
-
markdocConfigResult = await loadMarkdocConfig(astroConfig);
+ if(markdocConfigResult) {
+ markdocConfigResultId = normalizePath(fileURLToPath(markdocConfigResult.fileUrl));
+ }
const userMarkdocConfig = markdocConfigResult?.config ?? {};
function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
@@ -68,6 +73,9 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
addContentEntryType({
extensions: ['.mdoc'],
getEntryInfo,
+ // Markdoc handles script / style propagation
+ // for Astro components internally
+ handlePropagation: false,
async getRenderModule({ contents, fileUrl, viteId }) {
const entry = getEntryInfo({ contents, fileUrl });
const tokens = markdocTokenizer.tokenize(entry.body);
@@ -112,14 +120,16 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
});
}
- const res = `import { jsx as h } from 'astro/jsx-runtime';
+ const res = `import {
+ createComponent,
+ renderComponent,
+ } from 'astro/runtime/server/index.js';
import { Renderer } from '@astrojs/markdoc/components';
import { collectHeadings, setupConfig, setupConfigSync, Markdoc } from '@astrojs/markdoc/runtime';
-import * as entry from ${JSON.stringify(viteId + '?astroContentCollectionEntry')};
${
markdocConfigResult
? `import _userConfig from ${JSON.stringify(
- markdocConfigResult.fileUrl.pathname
+ markdocConfigResultId
)};\nconst userConfig = _userConfig ?? {};`
: 'const userConfig = {};'
}${
@@ -138,19 +148,29 @@ export function getHeadings() {
''
}
const headingConfig = userConfig.nodes?.heading;
- const config = setupConfigSync(headingConfig ? { nodes: { heading: headingConfig } } : {}, entry);
+ const config = setupConfigSync(headingConfig ? { nodes: { heading: headingConfig } } : {});
const ast = Markdoc.Ast.fromJSON(stringifiedAst);
const content = Markdoc.transform(ast, config);
return collectHeadings(Array.isArray(content) ? content : content.children);
}
-export async function Content (props) {
- const config = await setupConfig({
- ...userConfig,
- variables: { ...userConfig.variables, ...props },
- }, entry);
- return h(Renderer, { config, stringifiedAst });
-}`;
+export const Content = createComponent({
+ async factory(result, props) {
+ const config = await setupConfig({
+ ...userConfig,
+ variables: { ...userConfig.variables, ...props },
+ });
+
+ return renderComponent(
+ result,
+ Renderer.name,
+ Renderer,
+ { stringifiedAst, config },
+ {}
+ );
+ },
+ propagation: 'self',
+});`;
return { code: res };
},
contentModuleTypes: await fs.promises.readFile(
@@ -158,10 +178,58 @@ export async function Content (props) {
'utf-8'
),
});
+
+ let rollupOptions: rollup.RollupOptions = {};
+ if (markdocConfigResult) {
+ rollupOptions = {
+ output: {
+ // Split Astro components from your `markdoc.config`
+ // to only inject component styles and scripts at runtime.
+ manualChunks(id, { getModuleInfo }) {
+ if (
+ markdocConfigResult &&
+ hasContentFlag(id, PROPAGATED_ASSET_FLAG) &&
+ getModuleInfo(id)?.importers?.includes(markdocConfigResultId)
+ ) {
+ return createNameHash(id, [id]);
+ }
+ },
+ },
+ };
+ }
+
+ updateConfig({
+ vite: {
+ vite: {
+ ssr: {
+ external: ['@astrojs/markdoc/prism', '@astrojs/markdoc/shiki'],
+ },
+ },
+ build: {
+ rollupOptions,
+ },
+ plugins: [
+ {
+ name: '@astrojs/markdoc:astro-propagated-assets',
+ enforce: 'pre',
+ // Astro component styles and scripts should only be injected
+ // When a given Markdoc file actually uses that component.
+ // Add the `astroPropagatedAssets` flag to inject only when rendered.
+ resolveId(this: rollup.TransformPluginContext, id: string, importer: string) {
+ if (importer === markdocConfigResultId && id.endsWith('.astro')) {
+ return this.resolve(id + '?astroPropagatedAssets', importer, {
+ skipSelf: true,
+ });
+ }
+ },
+ },
+ ],
+ },
+ });
},
'astro:server:setup': async ({ server }) => {
server.watcher.on('all', (event, entry) => {
- if (pathToFileURL(entry).pathname === markdocConfigResult?.fileUrl.pathname) {
+ if (prependForwardSlash(pathToFileURL(entry).pathname) === markdocConfigResultId) {
console.log(
yellow(
`${bold('[Markdoc]')} Restart the dev server for config changes to take effect.`
diff --git a/packages/integrations/markdoc/src/runtime.ts b/packages/integrations/markdoc/src/runtime.ts
index a1861c68c..bbbd85739 100644
--- a/packages/integrations/markdoc/src/runtime.ts
+++ b/packages/integrations/markdoc/src/runtime.ts
@@ -32,13 +32,9 @@ export async function setupConfig(
/** Used for synchronous `getHeadings()` function */
export function setupConfigSync(
- userConfig: AstroMarkdocConfig,
- entry: ContentEntryModule
+ userConfig: AstroMarkdocConfig
): Omit<AstroMarkdocConfig, 'extends'> {
- let defaultConfig: AstroMarkdocConfig = {
- ...setupHeadingConfig(),
- variables: { entry },
- };
+ const defaultConfig: AstroMarkdocConfig = setupHeadingConfig();
return mergeConfig(defaultConfig, userConfig);
}
diff --git a/packages/integrations/markdoc/src/utils.ts b/packages/integrations/markdoc/src/utils.ts
index ea5dda6db..ad964f56c 100644
--- a/packages/integrations/markdoc/src/utils.ts
+++ b/packages/integrations/markdoc/src/utils.ts
@@ -1,3 +1,5 @@
+import crypto from 'node:crypto';
+import path from 'node:path';
import matter from 'gray-matter';
import type { ErrorPayload as ViteErrorPayload } from 'vite';
@@ -96,3 +98,32 @@ export function isValidUrl(str: string): boolean {
return false;
}
}
+
+/**
+ * Identifies Astro components with propagated assets
+ * @see 'packages/astro/src/content/consts.ts'
+ */
+export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';
+
+/**
+ * @see 'packages/astro/src/content/utils.ts'
+ */
+export function hasContentFlag(viteId: string, flag: string): boolean {
+ const flags = new URLSearchParams(viteId.split('?')[1] ?? '');
+ return flags.has(flag);
+}
+
+/**
+ * Create build hash for manual Rollup chunks.
+ * @see 'packages/astro/src/core/build/plugins/plugin-css.ts'
+ */
+export function createNameHash(baseId: string, hashIds: string[]): string {
+ const baseName = baseId ? path.parse(baseId).name : 'index';
+ const hash = crypto.createHash('sha256');
+ for (const id of hashIds) {
+ hash.update(id, 'utf-8');
+ }
+ const h = hash.digest('hex').slice(0, 8);
+ const proposedName = baseName + '.' + h;
+ return proposedName;
+}