Skip to content

Commit

Permalink
[Content collections] Load MDX hoisted scripts in dev (#6035)
Browse files Browse the repository at this point in the history
* chore: script, rename delayed -> propagated

* fix: consistent propagatedAssets flag

* feat: inject those scripts in dev!

* test: scripts included in dev and build

* chore: add TODO for prod build fix

* chore: changeset
  • Loading branch information
bholmesdev committed Jan 30, 2023
1 parent 9bb0bfa commit b4432cd
Show file tree
Hide file tree
Showing 21 changed files with 264 additions and 115 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-kids-attack.md
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix: Astro component scripts now load in development when using MDX + Content Collections
3 changes: 2 additions & 1 deletion packages/astro/src/content/consts.ts
@@ -1,8 +1,9 @@
export const contentFileExts = ['.md', '.mdx'];
export const DELAYED_ASSET_FLAG = 'astroAssetSsr';
export const PROPAGATED_ASSET_FLAG = 'astroPropagatedAssets';
export const CONTENT_FLAG = 'astroContent';
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_TYPES_FILE = 'types.d.ts';
4 changes: 2 additions & 2 deletions packages/astro/src/content/index.ts
@@ -1,8 +1,8 @@
export { createContentTypesGenerator } from './types-generator.js';
export { contentObservable, getContentPaths, getDotAstroTypeReference } from './utils.js';
export {
astroBundleDelayedAssetPlugin,
astroDelayedAssetPlugin,
astroContentProdBundlePlugin,
astroContentAssetPropagationPlugin,
} from './vite-plugin-content-assets.js';
export { astroContentServerPlugin } from './vite-plugin-content-server.js';
export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
9 changes: 7 additions & 2 deletions packages/astro/src/content/internal.ts
Expand Up @@ -5,6 +5,7 @@ import {
createHeadAndContent,
renderComponent,
renderStyleElement,
renderScriptElement,
renderTemplate,
renderUniqueStylesheet,
unescapeHTML,
Expand Down Expand Up @@ -127,7 +128,8 @@ async function render({
const Content = createComponent({
factory(result, props, slots) {
let styles = '',
links = '';
links = '',
scripts = '';
if (Array.isArray(mod?.collectedStyles)) {
styles = mod.collectedStyles.map((style: any) => renderStyleElement(style)).join('');
}
Expand All @@ -140,9 +142,12 @@ async function render({
})
.join('');
}
if (Array.isArray(mod?.collectedScripts)) {
scripts = mod.collectedScripts.map((script: any) => renderScriptElement(script)).join('');
}

return createHeadAndContent(
unescapeHTML(styles + links) as any,
unescapeHTML(styles + links + scripts) as any,
renderTemplate`${renderComponent(result, 'Content', mod.Content, props, slots)}`
);
},
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/content/template/virtual-mod.mjs
Expand Up @@ -22,7 +22,7 @@ const collectionToEntryMap = createCollectionToGlobResultMap({
});

const renderEntryGlob = import.meta.glob('@@RENDER_ENTRY_GLOB_PATH@@', {
query: { astroAssetSsr: true },
query: { astroPropagatedAssets: true },
});
const collectionToRenderEntryMap = createCollectionToGlobResultMap({
globResult: renderEntryGlob,
Expand Down
30 changes: 16 additions & 14 deletions packages/astro/src/content/vite-plugin-content-assets.ts
Expand Up @@ -5,45 +5,48 @@ import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js';
import type { ModuleLoader } from '../core/module-loader/loader.js';
import { createViteLoader } from '../core/module-loader/vite.js';
import { getStylesForURL } from '../core/render/dev/css.js';
import { getScriptsForURL } from '../core/render/dev/scripts.js';
import {
contentFileExts,
DELAYED_ASSET_FLAG,
PROPAGATED_ASSET_FLAG,
LINKS_PLACEHOLDER,
SCRIPTS_PLACEHOLDER,
STYLES_PLACEHOLDER,
} from './consts.js';

function isDelayedAsset(viteId: string): boolean {
function isPropagatedAsset(viteId: string): boolean {
const url = new URL(viteId, 'file://');
return (
url.searchParams.has(DELAYED_ASSET_FLAG) &&
url.searchParams.has(PROPAGATED_ASSET_FLAG) &&
contentFileExts.some((ext) => url.pathname.endsWith(ext))
);
}

export function astroDelayedAssetPlugin({ mode }: { mode: string }): Plugin {
export function astroContentAssetPropagationPlugin({ mode }: { mode: string }): Plugin {
let devModuleLoader: ModuleLoader;
return {
name: 'astro-delayed-asset-plugin',
name: 'astro:content-asset-propagation',
enforce: 'pre',
configureServer(server) {
if (mode === 'dev') {
devModuleLoader = createViteLoader(server);
}
},
load(id) {
if (isDelayedAsset(id)) {
if (isPropagatedAsset(id)) {
const basePath = id.split('?')[0];
const code = `
export { Content, getHeadings, frontmatter } from ${JSON.stringify(basePath)};
export const collectedLinks = ${JSON.stringify(LINKS_PLACEHOLDER)};
export const collectedStyles = ${JSON.stringify(STYLES_PLACEHOLDER)};
export const collectedScripts = ${JSON.stringify(SCRIPTS_PLACEHOLDER)};
`;
return { code };
}
},
async transform(code, id, options) {
if (!options?.ssr) return;
if (devModuleLoader && isDelayedAsset(id)) {
if (devModuleLoader && isPropagatedAsset(id)) {
const basePath = id.split('?')[0];
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
await devModuleLoader.import(basePath);
Expand All @@ -54,23 +57,22 @@ export function astroDelayedAssetPlugin({ mode }: { mode: string }): Plugin {
'development'
);

const hoistedScripts = await getScriptsForURL(pathToFileURL(basePath), devModuleLoader);

return {
code: code
.replace(JSON.stringify(LINKS_PLACEHOLDER), JSON.stringify([...urls]))
.replace(JSON.stringify(STYLES_PLACEHOLDER), JSON.stringify([...stylesMap.values()])),
.replace(JSON.stringify(STYLES_PLACEHOLDER), JSON.stringify([...stylesMap.values()]))
.replace(JSON.stringify(SCRIPTS_PLACEHOLDER), JSON.stringify([...hoistedScripts])),
};
}
},
};
}

export function astroBundleDelayedAssetPlugin({
internals,
}: {
internals: BuildInternals;
}): Plugin {
export function astroContentProdBundlePlugin({ internals }: { internals: BuildInternals }): Plugin {
return {
name: 'astro-bundle-delayed-asset-plugin',
name: 'astro:content-prod-bundle',
async generateBundle(_options, bundle) {
for (const [_, chunk] of Object.entries(bundle)) {
if (chunk.type === 'chunk' && chunk.code.includes(LINKS_PLACEHOLDER)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/build/static-build.ts
Expand Up @@ -4,7 +4,7 @@ import fs from 'fs';
import { bgGreen, bgMagenta, black, dim } from 'kleur/colors';
import { fileURLToPath } from 'url';
import * as vite from 'vite';
import { astroBundleDelayedAssetPlugin } from '../../content/index.js';
import { astroContentProdBundlePlugin } from '../../content/index.js';
import {
BuildInternals,
createBuildInternals,
Expand Down Expand Up @@ -165,7 +165,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
}),
vitePluginPrerender(opts, internals),
...(viteConfig.plugins || []),
astroBundleDelayedAssetPlugin({ internals }),
astroContentProdBundlePlugin({ internals }),
// SSR needs to be last
ssr && vitePluginSSR(internals, settings.adapter!),
],
Expand Down
8 changes: 4 additions & 4 deletions packages/astro/src/core/build/vite-plugin-css.ts
Expand Up @@ -6,7 +6,7 @@ import { isCSSRequest } from '../render/util.js';
import type { BuildInternals } from './internal';
import type { PageBuildData, StaticBuildOptions } from './types';

import { DELAYED_ASSET_FLAG } from '../../content/consts.js';
import { PROPAGATED_ASSET_FLAG } from '../../content/consts.js';
import * as assetName from './css-asset-name.js';
import { moduleIsTopLevelPage, walkParentInfos } from './graph.js';
import {
Expand Down Expand Up @@ -79,7 +79,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
for (const [pageInfo] of walkParentInfos(id, {
getModuleInfo: args[0].getModuleInfo,
})) {
if (new URL(pageInfo.id, 'file://').searchParams.has(DELAYED_ASSET_FLAG)) {
if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) {
// Split delayed assets to separate modules
// so they can be injected where needed
return createNameHash(id, [id]);
Expand Down Expand Up @@ -172,10 +172,10 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
id,
this,
function until(importer) {
return new URL(importer, 'file://').searchParams.has(DELAYED_ASSET_FLAG);
return new URL(importer, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG);
}
)) {
if (new URL(pageInfo.id, 'file://').searchParams.has(DELAYED_ASSET_FLAG)) {
if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) {
for (const parent of walkParentInfos(id, this)) {
const parentInfo = parent[0];
if (moduleIsTopLevelPage(parentInfo)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/create-vite.ts
Expand Up @@ -8,7 +8,7 @@ import { crawlFrameworkPkgs } from 'vitefu';
import {
astroContentServerPlugin,
astroContentVirtualModPlugin,
astroDelayedAssetPlugin,
astroContentAssetPropagationPlugin,
} from '../content/index.js';
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js';
Expand Down Expand Up @@ -106,7 +106,7 @@ export async function createVite(
astroInjectEnvTsPlugin({ settings, logging, fs }),
astroContentVirtualModPlugin({ settings }),
astroContentServerPlugin({ fs, settings, logging, mode }),
astroDelayedAssetPlugin({ mode }),
astroContentAssetPropagationPlugin({ mode }),
],
publicDir: fileURLToPath(settings.config.publicDir),
root: fileURLToPath(settings.config.root),
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/render/dev/vite.ts
@@ -1,7 +1,7 @@
import type { ModuleLoader, ModuleNode } from '../../module-loader/index';

import npath from 'path';
import { DELAYED_ASSET_FLAG } from '../../../content/consts.js';
import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../constants.js';
import { unwrapId } from '../../util.js';
import { STYLE_EXTENSIONS } from '../util.js';
Expand All @@ -23,7 +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(DELAYED_ASSET_FLAG)) return;
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 Down
1 change: 1 addition & 0 deletions packages/astro/src/runtime/server/index.ts
Expand Up @@ -18,6 +18,7 @@ export {
renderPage,
renderSlot,
renderStyleElement,
renderScriptElement,
renderTemplate as render,
renderTemplate,
renderToString,
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/render/index.ts
Expand Up @@ -16,6 +16,6 @@ export { renderHTMLElement } from './dom.js';
export { maybeRenderHead, renderHead } from './head.js';
export { renderPage } from './page.js';
export { renderSlot } from './slot.js';
export { renderStyleElement, renderUniqueStylesheet } from './tags.js';
export { renderStyleElement, renderScriptElement, renderUniqueStylesheet } from './tags.js';
export type { RenderInstruction } from './types';
export { addAttribute, defineScriptVars, voidElementNames } from './util.js';
9 changes: 8 additions & 1 deletion packages/astro/src/runtime/server/render/tags.ts
@@ -1,4 +1,4 @@
import { SSRResult } from '../../../@types/astro';
import { SSRElement, SSRResult } from '../../../@types/astro';
import { renderElement } from './util.js';

const stylesheetRel = 'stylesheet';
Expand All @@ -10,6 +10,13 @@ export function renderStyleElement(children: string) {
});
}

export function renderScriptElement({ props, children }: SSRElement) {
return renderElement('script', {
props,
children,
});
}

export function renderStylesheet({ href }: { href: string }) {
return renderElement(
'link',
Expand Down

0 comments on commit b4432cd

Please sign in to comment.