Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revert: markdoc asset bleed #7178

Merged
merged 2 commits into from May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tiny-phones-reply.md
@@ -0,0 +1,7 @@
---
'@astrojs/markdoc': patch
'@astrojs/mdx': patch
'astro': patch
---

Fix: revert Markdoc asset bleed changes. Production build issues were discovered that deserve a different fix.
9 changes: 9 additions & 0 deletions examples/with-markdoc/src/content/config.ts
@@ -0,0 +1,9 @@
import { defineCollection, z } from 'astro:content';

const docs = defineCollection({
schema: z.object({
title: z.string(),
}),
});

export const collections = { docs };
6 changes: 0 additions & 6 deletions packages/astro/src/@types/astro.ts
Expand Up @@ -1275,12 +1275,6 @@ export interface ContentEntryType {
}
): rollup.LoadResult | Promise<rollup.LoadResult>;
contentModuleTypes?: string;
/**
* Handle asset propagation for rendered content to avoid bleed.
* Ex. MDX content can import styles and scripts, so `handlePropagation` should be true.
* @default true
*/
handlePropagation?: boolean;
}

type GetContentEntryInfoReturnType = {
Expand Down
9 changes: 1 addition & 8 deletions packages/astro/src/content/consts.ts
@@ -1,18 +1,11 @@
export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';
export const CONTENT_RENDER_FLAG = 'astroRenderContent';
export const CONTENT_FLAG = 'astroContentCollectionEntry';
export const DATA_FLAG = 'astroDataCollectionEntry';
export const CONTENT_FLAGS = [CONTENT_FLAG, DATA_FLAG, PROPAGATED_ASSET_FLAG] as const;

export const VIRTUAL_MODULE_ID = 'astro:content';
export const LINKS_PLACEHOLDER = '@@ASTRO-LINKS@@';
export const STYLES_PLACEHOLDER = '@@ASTRO-STYLES@@';
export const SCRIPTS_PLACEHOLDER = '@@ASTRO-SCRIPTS@@';

export const CONTENT_FLAGS = [
CONTENT_FLAG,
CONTENT_RENDER_FLAG,
DATA_FLAG,
PROPAGATED_ASSET_FLAG,
] as const;

export const CONTENT_TYPES_FILE = 'types.d.ts';
124 changes: 52 additions & 72 deletions packages/astro/src/content/runtime.ts
Expand Up @@ -270,82 +270,62 @@ async function render({
const baseMod = await renderEntryImport();
if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError;

if (
baseMod.default != null &&
typeof baseMod.default === 'object' &&
baseMod.default.__astroPropagation === true
) {
const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod.default;
if (typeof getMod !== 'function') throw UnexpectedRenderError;
const propagationMod = await getMod();
if (propagationMod == null || typeof propagationMod !== 'object') throw UnexpectedRenderError;
const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod;
if (typeof getMod !== 'function') throw UnexpectedRenderError;
const mod = await getMod();
if (mod == null || typeof mod !== 'object') throw UnexpectedRenderError;

const Content = createComponent({
factory(result, baseProps, slots) {
let styles = '',
links = '',
scripts = '';
if (Array.isArray(collectedStyles)) {
styles = collectedStyles
.map((style: any) => {
return renderUniqueStylesheet(result, {
type: 'inline',
content: style,
});
})
.join('');
}
if (Array.isArray(collectedLinks)) {
links = collectedLinks
.map((link: any) => {
return renderUniqueStylesheet(result, {
type: 'external',
src: prependForwardSlash(link),
});
})
.join('');
}
if (Array.isArray(collectedScripts)) {
scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join('');
}
const Content = createComponent({
factory(result, baseProps, slots) {
let styles = '',
links = '',
scripts = '';
if (Array.isArray(collectedStyles)) {
styles = collectedStyles
.map((style: any) => {
return renderUniqueStylesheet(result, {
type: 'inline',
content: style,
});
})
.join('');
}
if (Array.isArray(collectedLinks)) {
links = collectedLinks
.map((link: any) => {
return renderUniqueStylesheet(result, {
type: 'external',
src: prependForwardSlash(link),
});
})
.join('');
}
if (Array.isArray(collectedScripts)) {
scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join('');
}

let props = baseProps;
// Auto-apply MDX components export
if (id.endsWith('mdx')) {
props = {
components: propagationMod.components ?? {},
...baseProps,
};
}
let props = baseProps;
// Auto-apply MDX components export
if (id.endsWith('mdx')) {
props = {
components: mod.components ?? {},
...baseProps,
};
}

return createHeadAndContent(
unescapeHTML(styles + links + scripts) as any,
renderTemplate`${renderComponent(
result,
'Content',
propagationMod.Content,
props,
slots
)}`
);
},
propagation: 'self',
});
return createHeadAndContent(
unescapeHTML(styles + links + scripts) as any,
renderTemplate`${renderComponent(result, 'Content', mod.Content, props, slots)}`
);
},
propagation: 'self',
});

return {
Content,
headings: propagationMod.getHeadings?.() ?? [],
remarkPluginFrontmatter: propagationMod.frontmatter ?? {},
};
} else if (baseMod.Content && typeof baseMod.Content === 'function') {
return {
Content: baseMod.Content,
headings: baseMod.getHeadings?.() ?? [],
remarkPluginFrontmatter: baseMod.frontmatter ?? {},
};
} else {
throw UnexpectedRenderError;
}
return {
Content,
headings: mod.getHeadings?.() ?? [],
remarkPluginFrontmatter: mod.frontmatter ?? {},
};
}

export function createReference({ lookupMap }: { lookupMap: ContentLookupMap }) {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/content/template/virtual-mod.mjs
Expand Up @@ -46,7 +46,7 @@ function createGlobLookup(glob) {
}

const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', {
query: { astroRenderContent: true },
query: { astroPropagatedAssets: true },
});
const collectionToRenderEntryMap = createCollectionToGlobResultMap({
globResult: renderEntryGlob,
Expand Down
3 changes: 1 addition & 2 deletions packages/astro/src/content/utils.ts
Expand Up @@ -14,7 +14,6 @@ import type {
} from '../@types/astro.js';
import { VALID_INPUT_FORMATS } from '../assets/consts.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';

import { formatYAMLException, isYAMLException } from '../core/errors/utils.js';
import { CONTENT_FLAGS, CONTENT_TYPES_FILE } from './consts.js';
import { errorMap } from './error-map.js';
Expand Down Expand Up @@ -329,7 +328,7 @@ export function parseFrontmatter(fileContents: string, filePath: string) {
*/
export const globalContentConfigObserver = contentObservable({ status: 'init' });

export function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]): boolean {
export function hasContentFlag(viteId: string, flag: (typeof CONTENT_FLAGS)[number]) {
const flags = new URLSearchParams(viteId.split('?')[1] ?? '');
return flags.has(flag);
}
Expand Down
43 changes: 12 additions & 31 deletions packages/astro/src/content/vite-plugin-content-assets.ts
@@ -1,5 +1,4 @@
import { extname } from 'node:path';
import { pathToFileURL } from 'node:url';
import { pathToFileURL } from 'url';
import type { Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js';
Expand All @@ -12,13 +11,16 @@ import { joinPaths, prependForwardSlash } from '../core/path.js';
import { getStylesForURL } from '../core/render/dev/css.js';
import { getScriptsForURL } from '../core/render/dev/scripts.js';
import {
CONTENT_RENDER_FLAG,
LINKS_PLACEHOLDER,
PROPAGATED_ASSET_FLAG,
SCRIPTS_PLACEHOLDER,
STYLES_PLACEHOLDER,
} from './consts.js';
import { hasContentFlag } from './utils.js';

function isPropagatedAsset(viteId: string) {
const flags = new URLSearchParams(viteId.split('?')[1]);
return flags.has(PROPAGATED_ASSET_FLAG);
}

export function astroContentAssetPropagationPlugin({
mode,
Expand All @@ -30,31 +32,13 @@ export function astroContentAssetPropagationPlugin({
let devModuleLoader: ModuleLoader;
return {
name: 'astro:content-asset-propagation',
enforce: 'pre',
async resolveId(id, importer, opts) {
if (hasContentFlag(id, CONTENT_RENDER_FLAG)) {
const base = id.split('?')[0];

for (const { extensions, handlePropagation = true } of settings.contentEntryTypes) {
if (handlePropagation && extensions.includes(extname(base))) {
return this.resolve(`${base}?${PROPAGATED_ASSET_FLAG}`, importer, {
skipSelf: true,
...opts,
});
}
}
// Resolve to the base id (no content flags)
// if Astro doesn't need to handle propagation.
return this.resolve(base, importer, { skipSelf: true, ...opts });
}
},
configureServer(server) {
if (mode === 'dev') {
devModuleLoader = createViteLoader(server);
}
},
async transform(_, id, options) {
if (hasContentFlag(id, PROPAGATED_ASSET_FLAG)) {
if (isPropagatedAsset(id)) {
const basePath = id.split('?')[0];
let stringifiedLinks: string, stringifiedStyles: string, stringifiedScripts: string;

Expand Down Expand Up @@ -89,17 +73,14 @@ export function astroContentAssetPropagationPlugin({
}

const code = `
async function getMod() {
export async function getMod() {
return import(${JSON.stringify(basePath)});
}
const collectedLinks = ${stringifiedLinks};
const collectedStyles = ${stringifiedStyles};
const collectedScripts = ${stringifiedScripts};
const defaultMod = { __astroPropagation: true, getMod, collectedLinks, collectedStyles, collectedScripts };
export default defaultMod;
export const collectedLinks = ${stringifiedLinks};
export const collectedStyles = ${stringifiedStyles};
export const collectedScripts = ${stringifiedScripts};
`;
// ^ Use a default export for tools like Markdoc
// to catch the `__astroPropagation` identifier

return { code, map: { mappings: '' } };
}
},
Expand Down
20 changes: 11 additions & 9 deletions packages/astro/src/core/render/dev/vite.ts
@@ -1,6 +1,7 @@
import type { ModuleLoader, ModuleNode } from '../../module-loader/index';

import npath from 'path';
import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js';
import { unwrapId } from '../../util.js';
import { isCSSRequest } from './util.js';
Expand All @@ -9,10 +10,9 @@ import { isCSSRequest } from './util.js';
* List of file extensions signalling we can (and should) SSR ahead-of-time
* See usage below
*/
const fileExtensionsToSSR = new Set(['.astro', '.mdoc', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]);
const fileExtensionsToSSR = new Set(['.astro', ...SUPPORTED_MARKDOWN_FILE_EXTENSIONS]);

const STRIP_QUERY_PARAMS_REGEX = /\?.*$/;
const ASTRO_PROPAGATED_ASSET_REGEX = /\?astroPropagatedAssets/;

/** recursively crawl the module graph to get all style files imported by parent id */
export async function* crawlGraph(
Expand All @@ -23,6 +23,7 @@ export async function* crawlGraph(
): AsyncGenerator<ModuleNode, void, unknown> {
const id = unwrapId(_id);
const importedModules = new Set<ModuleNode>();
if (new URL(id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) return;

const moduleEntriesForId = isRootFile
? // "getModulesByFile" pulls from a delayed module cache (fun implementation detail),
Expand All @@ -43,7 +44,6 @@ export async function* crawlGraph(
if (id === entry.id) {
scanned.add(id);
const entryIsStyle = isCSSRequest(id);

for (const importedModule of entry.importedModules) {
// some dynamically imported modules are *not* server rendered in time
// to only SSR modules that we can safely transform, we check against
Expand All @@ -60,13 +60,15 @@ export async function* crawlGraph(
if (entryIsStyle && !isCSSRequest(importedModulePathname)) {
continue;
}
const isFileTypeNeedingSSR = fileExtensionsToSSR.has(
npath.extname(importedModulePathname)
);
if (
isFileTypeNeedingSSR &&
// Should not SSR a module with ?astroPropagatedAssets
!ASTRO_PROPAGATED_ASSET_REGEX.test(importedModule.id)
fileExtensionsToSSR.has(
npath.extname(
// Use `id` instead of `pathname` to preserve query params.
// Should not SSR a module with an unexpected query param,
// like "?astroPropagatedAssets"
importedModule.id
)
)
) {
const mod = loader.getModuleById(importedModule.id);
if (!mod?.ssrModule) {
Expand Down
4 changes: 1 addition & 3 deletions packages/astro/src/vite-plugin-head/index.ts
Expand Up @@ -9,8 +9,7 @@ import { getTopLevelPages, walkParentInfos } from '../core/build/graph.js';
import type { BuildInternals } from '../core/build/internal.js';
import { getAstroMetadata } from '../vite-plugin-astro/index.js';

// Detect this in comments, both in .astro components and in js/ts files.
const injectExp = /(^\/\/|\/\/!)\s*astro-head-inject/;
const injectExp = /^\/\/\s*astro-head-inject/;

export default function configHeadVitePlugin({
settings,
Expand All @@ -33,7 +32,6 @@ export default function configHeadVitePlugin({
seen.add(id);
const mod = server.moduleGraph.getModuleById(id);
const info = this.getModuleInfo(id);

if (info?.meta.astro) {
const astroMetadata = getAstroMetadata(info);
if (astroMetadata) {
Expand Down
5 changes: 0 additions & 5 deletions packages/astro/src/vite-plugin-markdown/content-entry-type.ts
Expand Up @@ -13,8 +13,6 @@ export const markdownContentEntryType: ContentEntryType = {
rawData: parsed.matter,
};
},
// We need to handle propagation for Markdown because they support layouts which will bring in styles.
handlePropagation: true,
};

/**
Expand All @@ -32,9 +30,6 @@ export const mdxContentEntryType: ContentEntryType = {
rawData: parsed.matter,
};
},
// MDX can import scripts and styles,
// so wrap all MDX files with script / style propagation checks
handlePropagation: true,
contentModuleTypes: `declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Expand Down