diff options
author | 2023-05-31 19:18:07 -0400 | |
---|---|---|
committer | 2023-05-31 19:18:07 -0400 | |
commit | 339529fc820bac2d514b63198ecf54a1d88c0917 (patch) | |
tree | 8b553c0df30c1aa60a037fb915bcf7fdfaea945f /packages/integrations/markdoc/src | |
parent | c4c086e5e70bdd347e1c7601212a04b4f523578e (diff) | |
download | astro-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.ts | 7 | ||||
-rw-r--r-- | packages/integrations/markdoc/src/index.ts | 110 | ||||
-rw-r--r-- | packages/integrations/markdoc/src/runtime.ts | 8 | ||||
-rw-r--r-- | packages/integrations/markdoc/src/utils.ts | 31 |
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; +} |