Merge branch 'main' into redirects-ssg

This commit is contained in:
Matthew Phillips 2023-05-23 15:47:04 -04:00 committed by GitHub
commit 2904ceddf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 348 additions and 509 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
fix a bug when Fragment is as a slot

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Add types for `import.meta.env.ASSETS_PREFIX` and `import.meta.env.SITE`

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
value of var can be undefined when using `define:vars`

View file

@ -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.

View file

@ -0,0 +1,9 @@
import { defineCollection, z } from 'astro:content';
const docs = defineCollection({
schema: z.object({
title: z.string(),
}),
});
export const collections = { docs };

View file

@ -1,5 +1,21 @@
# astro
## 2.5.4
### Patch Changes
- [#7125](https://github.com/withastro/astro/pull/7125) [`4ce8bf7c6`](https://github.com/withastro/astro/commit/4ce8bf7c62d2b19ff7bd3dd0fbad88fcac10feaa) Thanks [@bluwy](https://github.com/bluwy)! - Make vite-plugin-content-virtual-mod run `getEntrySlug` 10 at a time to prevent `EMFILE: too many open files` error
- [#7166](https://github.com/withastro/astro/pull/7166) [`626dd41d0`](https://github.com/withastro/astro/commit/626dd41d0a80155f59962e3a1b70d8dfd2719d25) Thanks [@ematipico](https://github.com/ematipico)! - Move generation of renderers code into their own file
- [#7174](https://github.com/withastro/astro/pull/7174) [`92d1f017e`](https://github.com/withastro/astro/commit/92d1f017e5c0a921973e028b90c7975e74dce433) Thanks [@ematipico](https://github.com/ematipico)! - Remove restriction around serialisable data for `Astro.locals`
- [#7172](https://github.com/withastro/astro/pull/7172) [`2ca94269e`](https://github.com/withastro/astro/commit/2ca94269ed0b5046033c47985ef50b7e7a637caf) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Add types for `import.meta.env.ASSETS_PREFIX` and `import.meta.env.SITE`
- [#7134](https://github.com/withastro/astro/pull/7134) [`5b6a0312a`](https://github.com/withastro/astro/commit/5b6a0312a822565404a6334576677fc574cfcd56) Thanks [@alexvuka1](https://github.com/alexvuka1)! - value of var can be undefined when using `define:vars`
- [#7171](https://github.com/withastro/astro/pull/7171) [`79ba74832`](https://github.com/withastro/astro/commit/79ba74832fc46e6946c8235c33e9acfbb3a4139b) Thanks [@bluwy](https://github.com/bluwy)! - Prevent Vite watching on Astro config load
## 2.5.3
### Patch Changes

View file

@ -1,6 +1,6 @@
{
"name": "astro",
"version": "2.5.3",
"version": "2.5.4",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",
@ -149,6 +149,7 @@
"magic-string": "^0.27.0",
"mime": "^3.0.0",
"ora": "^6.1.0",
"p-limit": "^4.0.0",
"path-to-regexp": "^6.2.1",
"preferred-pm": "^3.0.3",
"prompts": "^2.4.2",

View file

@ -1280,12 +1280,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 = {

View file

@ -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';

View file

@ -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 }) {

View file

@ -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,

View file

@ -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';
@ -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);
}

View file

@ -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';
@ -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,
@ -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;
@ -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: '' } };
}
},

View file

@ -2,6 +2,7 @@ import glob from 'fast-glob';
import fsMod from 'node:fs';
import { extname } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import pLimit from 'p-limit';
import type { Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
@ -114,59 +115,68 @@ export async function getStringifiedLookupMap({
}
);
await Promise.all(
contentGlob.map(async (filePath) => {
const entryType = getEntryType(filePath, contentPaths, contentEntryExts, dataEntryExts);
// Globbed ignored or unsupported entry.
// Logs warning during type generation, should ignore in lookup map.
if (entryType !== 'content' && entryType !== 'data') return;
// Run 10 at a time to prevent `await getEntrySlug` from accessing the filesystem all at once.
// Each await shouldn't take too long for the work to be noticably slow too.
const limit = pLimit(10);
const promises: Promise<void>[] = [];
const collection = getEntryCollectionName({ contentDir, entry: pathToFileURL(filePath) });
if (!collection) throw UnexpectedLookupMapError;
for (const filePath of contentGlob) {
promises.push(
limit(async () => {
const entryType = getEntryType(filePath, contentPaths, contentEntryExts, dataEntryExts);
// Globbed ignored or unsupported entry.
// Logs warning during type generation, should ignore in lookup map.
if (entryType !== 'content' && entryType !== 'data') return;
if (lookupMap[collection]?.type && lookupMap[collection].type !== entryType) {
throw new AstroError({
...AstroErrorData.MixedContentDataCollectionError,
message: AstroErrorData.MixedContentDataCollectionError.message(collection),
});
}
const collection = getEntryCollectionName({ contentDir, entry: pathToFileURL(filePath) });
if (!collection) throw UnexpectedLookupMapError;
if (entryType === 'content') {
const contentEntryType = contentEntryConfigByExt.get(extname(filePath));
if (!contentEntryType) throw UnexpectedLookupMapError;
if (lookupMap[collection]?.type && lookupMap[collection].type !== entryType) {
throw new AstroError({
...AstroErrorData.MixedContentDataCollectionError,
message: AstroErrorData.MixedContentDataCollectionError.message(collection),
});
}
const { id, slug: generatedSlug } = await getContentEntryIdAndSlug({
entry: pathToFileURL(filePath),
contentDir,
collection,
});
const slug = await getEntrySlug({
id,
collection,
generatedSlug,
fs,
fileUrl: pathToFileURL(filePath),
contentEntryType,
});
lookupMap[collection] = {
type: 'content',
entries: {
...lookupMap[collection]?.entries,
[slug]: rootRelativePath(root, filePath),
},
};
} else {
const id = getDataEntryId({ entry: pathToFileURL(filePath), contentDir, collection });
lookupMap[collection] = {
type: 'data',
entries: {
...lookupMap[collection]?.entries,
[id]: rootRelativePath(root, filePath),
},
};
}
})
);
if (entryType === 'content') {
const contentEntryType = contentEntryConfigByExt.get(extname(filePath));
if (!contentEntryType) throw UnexpectedLookupMapError;
const { id, slug: generatedSlug } = await getContentEntryIdAndSlug({
entry: pathToFileURL(filePath),
contentDir,
collection,
});
const slug = await getEntrySlug({
id,
collection,
generatedSlug,
fs,
fileUrl: pathToFileURL(filePath),
contentEntryType,
});
lookupMap[collection] = {
type: 'content',
entries: {
...lookupMap[collection]?.entries,
[slug]: rootRelativePath(root, filePath),
},
};
} else {
const id = getDataEntryId({ entry: pathToFileURL(filePath), contentDir, collection });
lookupMap[collection] = {
type: 'data',
entries: {
...lookupMap[collection]?.entries,
[id]: rootRelativePath(root, filePath),
},
};
}
})
);
}
await Promise.all(promises);
return JSON.stringify(lookupMap);
}

View file

@ -10,6 +10,7 @@ import { pluginInternals } from './plugin-internals.js';
import { pluginMiddleware } from './plugin-middleware.js';
import { pluginPages } from './plugin-pages.js';
import { pluginPrerender } from './plugin-prerender.js';
import { pluginRenderers } from './plugin-renderers.js';
import { pluginSSR } from './plugin-ssr.js';
export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) {
@ -17,6 +18,7 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
register(pluginAliasResolve(internals));
register(pluginAnalyzer(internals));
register(pluginInternals(internals));
register(pluginRenderers(options, internals));
register(pluginMiddleware(options, internals));
register(pluginPages(options, internals));
register(pluginCSS(options, internals));

View file

@ -6,6 +6,7 @@ import type { AstroBuildPlugin } from '../plugin';
import type { StaticBuildOptions } from '../types';
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
import { routeIsRedirect } from '../../redirects/index.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
return {
@ -26,8 +27,12 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
async load(id) {
if (id === resolvedPagesVirtualModuleId) {
let importMap = '';
let imports = [];
const imports: string[] = [];
const exports: string[] = [];
const content: string[] = [];
let i = 0;
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
exports.push(`export { renderers };`);
for (const pageData of eachPageData(internals)) {
if(routeIsRedirect(pageData.route)) {
continue;
@ -40,31 +45,14 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
i++;
}
i = 0;
let rendererItems = '';
for (const renderer of opts.settings.renderers) {
const variable = `_renderer${i}`;
// Use unshift so that renderers are imported before user code, in case they set globals
// that user code depends on.
imports.unshift(`import ${variable} from '${renderer.serverEntrypoint}';`);
rendererItems += `Object.assign(${JSON.stringify(renderer)}, { ssr: ${variable} }),`;
i++;
if (opts.settings.config.experimental.middleware) {
imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`);
exports.push(`export const middleware = _middleware;`);
}
const def = `${imports.join('\n')}
content.push(`export const pageMap = new Map([${importMap}]);`);
${
opts.settings.config.experimental.middleware
? `import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`
: ''
}
export const pageMap = new Map([${importMap}]);
export const renderers = [${rendererItems}];
${opts.settings.config.experimental.middleware ? `export const middleware = _middleware;` : ''}
`;
return def;
return `${imports.join('\n')}${content.join('\n')}${exports.join('\n')}`;
}
},
};

View file

@ -0,0 +1,66 @@
import type { Plugin as VitePlugin } from 'vite';
import { addRollupInput } from '../add-rollup-input.js';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin';
import type { StaticBuildOptions } from '../types';
export const RENDERERS_MODULE_ID = '@astro-renderers';
export const RESOLVED_RENDERERS_MODULE_ID = `\0${RENDERERS_MODULE_ID}`;
let inputs: Set<string> = new Set();
export function vitePluginRenderers(
opts: StaticBuildOptions,
_internals: BuildInternals
): VitePlugin {
return {
name: '@astro/plugin-renderers',
options(options) {
return addRollupInput(options, [RENDERERS_MODULE_ID]);
},
resolveId(id) {
if (id === RENDERERS_MODULE_ID) {
return RESOLVED_RENDERERS_MODULE_ID;
}
},
async load(id) {
if (id === RESOLVED_RENDERERS_MODULE_ID) {
if (opts.settings.renderers.length > 0) {
const imports: string[] = [];
const exports: string[] = [];
let i = 0;
let rendererItems = '';
for (const renderer of opts.settings.renderers) {
const variable = `_renderer${i}`;
imports.push(`import ${variable} from '${renderer.serverEntrypoint}';`);
rendererItems += `Object.assign(${JSON.stringify(renderer)}, { ssr: ${variable} }),`;
i++;
}
exports.push(`export const renderers = [${rendererItems}];`);
return `${imports.join('\n')}\n${exports.join('\n')}`;
}
}
},
};
}
export function pluginRenderers(
opts: StaticBuildOptions,
internals: BuildInternals
): AstroBuildPlugin {
return {
build: 'ssr',
hooks: {
'build:before': () => {
return {
vitePlugin: vitePluginRenderers(opts, internals),
};
},
},
};
}

View file

@ -15,6 +15,7 @@ import { addRollupInput } from '../add-rollup-input.js';
import { getOutFile, getOutFolder } from '../common.js';
import { cssOrder, mergeInlineCss, type BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
export const virtualModuleId = '@astrojs-ssr-virtual-entry';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
@ -44,6 +45,7 @@ function vitePluginSSR(
middleware = 'middleware: _main.middleware';
}
return `import * as adapter from '${adapter.serverEntrypoint}';
import { renderers } from '${RENDERERS_MODULE_ID}';
import * as _main from '${pagesVirtualModuleId}';
import { deserializeManifest as _deserializeManifest } from 'astro/app';
import { _privateSetManifestDontUseThis } from 'astro:ssr-manifest';

View file

@ -26,6 +26,7 @@ import { trackPageData } from './internal.js';
import { createPluginContainer, type AstroBuildPluginContainer } from './plugin.js';
import { registerAllPlugins } from './plugins/index.js';
import { RESOLVED_MIDDLEWARE_MODULE_ID } from './plugins/plugin-middleware.js';
import { RESOLVED_RENDERERS_MODULE_ID } from './plugins/plugin-renderers.js';
import type { PageBuildData, StaticBuildOptions } from './types';
import { getTimeStat } from './util.js';
@ -175,6 +176,8 @@ async function ssrBuild(
return opts.buildConfig.serverEntry;
} else if (chunkInfo.facadeModuleId === RESOLVED_MIDDLEWARE_MODULE_ID) {
return 'middleware.mjs';
} else if (chunkInfo.facadeModuleId === RESOLVED_RENDERERS_MODULE_ID) {
return 'renderers.mjs';
} else {
return '[name].mjs';
}

View file

@ -10,7 +10,7 @@ export interface ViteLoader {
async function createViteLoader(root: string, fs: typeof fsType): Promise<ViteLoader> {
const viteServer = await vite.createServer({
server: { middlewareMode: true, hmr: false },
server: { middlewareMode: true, hmr: false, watch: { ignored: ['**'] } },
optimizeDeps: { disabled: true },
clearScreen: false,
appType: 'custom',

View file

@ -16,8 +16,6 @@ import { AstroCookies, attachToResponse } from '../cookies/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { warn, type LogOptions } from '../logger/core.js';
import { callMiddleware } from '../middleware/callMiddleware.js';
import { isValueSerializable } from '../render/core.js';
const clientAddressSymbol = Symbol.for('astro.clientAddress');
const clientLocalsSymbol = Symbol.for('astro.locals');
@ -117,12 +115,6 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
onRequest,
context,
async () => {
if (env.mode === 'development' && !isValueSerializable(context.locals)) {
throw new AstroError({
...AstroErrorData.LocalsNotSerializable,
message: AstroErrorData.LocalsNotSerializable.message(ctx.pathname),
});
}
return await renderEndpoint(mod, context, env.ssr);
}
);

View file

@ -694,32 +694,6 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
'`locals` can only be assigned to an object. Other values like numbers, strings, etc. are not accepted.',
hint: 'If you tried to remove some information from the `locals` object, try to use `delete` or set the property to `undefined`.',
},
/**
* @docs
* @description
* Thrown in development mode when a user attempts to store something that is not serializable in `locals`.
*
* For example:
* ```ts
* import {defineMiddleware} from "astro/middleware";
* export const onRequest = defineMiddleware((context, next) => {
* context.locals = {
* foo() {
* alert("Hello world!")
* }
* };
* return next();
* });
* ```
*/
LocalsNotSerializable: {
title: '`Astro.locals` is not serializable',
code: 3034,
message: (href: string) => {
return `The information stored in \`Astro.locals\` for the path "${href}" is not serializable.\nMake sure you store only serializable data.`;
},
},
/**
* @docs
* @see
@ -728,7 +702,6 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
* A redirect must be given a location with the `Location` header.
*/
RedirectWithNoLocation: {
// TODO remove
title: 'A redirect must be given a location with the `Location` header.',
code: 3035,
},

View file

@ -126,16 +126,8 @@ export async function renderPage({ mod, renderContext, env, apiContext }: Render
if (!Component)
throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
let locals = {};
if (apiContext) {
if (env.mode === 'development' && !isValueSerializable(apiContext.locals)) {
throw new AstroError({
...AstroErrorData.LocalsNotSerializable,
message: AstroErrorData.LocalsNotSerializable.message(renderContext.pathname),
});
}
locals = apiContext.locals;
}
let locals = apiContext?.locals ?? {};
const result = createResult({
adapterName: env.adapterName,
links: renderContext.links,
@ -181,57 +173,3 @@ export async function renderPage({ mod, renderContext, env, apiContext }: Render
return response;
}
/**
* Checks whether any value can is serializable.
*
* A serializable value contains plain values. For example, `Proxy`, `Set`, `Map`, functions, etc.
* are not serializable objects.
*
* @param object
*/
export function isValueSerializable(value: unknown): boolean {
let type = typeof value;
let plainObject = true;
if (type === 'object' && isPlainObject(value)) {
for (const [, nestedValue] of Object.entries(value)) {
if (!isValueSerializable(nestedValue)) {
plainObject = false;
break;
}
}
} else {
plainObject = false;
}
let result =
value === null ||
type === 'string' ||
type === 'number' ||
type === 'boolean' ||
Array.isArray(value) ||
plainObject;
return result;
}
/**
*
* From [redux-toolkit](https://github.com/reduxjs/redux-toolkit/blob/master/packages/toolkit/src/isPlainObject.ts)
*
* Returns true if the passed value is "plain" object, i.e. an object whose
* prototype is the root `Object.prototype`. This includes objects created
* using object literals, but not for instance for class instances.
*/
function isPlainObject(value: unknown): value is object {
if (typeof value !== 'object' || value === null) return false;
let proto = Object.getPrototypeOf(value);
if (proto === null) return true;
let baseProto = proto;
while (Object.getPrototypeOf(baseProto) !== null) {
baseProto = Object.getPrototypeOf(baseProto);
}
return proto === baseProto;
}

View file

@ -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';
@ -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(
@ -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),
@ -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
@ -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) {

View file

@ -9,6 +9,7 @@ import type {
SSRLoadedRenderer,
SSRResult,
} from '../../@types/astro';
import { isHTMLString } from '../../runtime/server/escape.js';
import {
renderSlotToString,
stringifyChunk,
@ -110,10 +111,11 @@ class Slots {
// Astro
const expression = getFunctionExpression(component);
if (expression) {
const slot = () => expression(...args);
return await renderSlotToString(result, slot).then((res) =>
res != null ? String(res) : res
);
const slot = async () =>
isHTMLString(await expression) ? expression : expression(...args);
return await renderSlotToString(result, slot).then((res) => {
return res != null ? String(res) : res;
});
}
// JSX
if (typeof component === 'function') {

View file

@ -65,7 +65,7 @@ export async function sync(
const tempViteServer = await createServer(
await createVite(
{
server: { middlewareMode: true, hmr: false },
server: { middlewareMode: true, hmr: false, watch: { ignored: ['**'] } },
optimizeDeps: { disabled: true },
ssr: { external: [] },
logLevel: 'silent',

View file

@ -7,7 +7,9 @@ import { renderChild } from './any.js';
type RenderTemplateResult = ReturnType<typeof renderTemplate>;
export type ComponentSlots = Record<string, ComponentSlotValue>;
export type ComponentSlotValue = (result: SSRResult) => RenderTemplateResult;
export type ComponentSlotValue = (
result: SSRResult
) => RenderTemplateResult | Promise<RenderTemplateResult>;
const slotString = Symbol.for('astro:slot-string');

View file

@ -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,
@ -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) {

View file

@ -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,
};
/**
@ -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<{

View file

@ -0,0 +1,2 @@
<Fragment set:html={Astro.slots.render('name', ['arg'])} />

View file

@ -0,0 +1,22 @@
---
import Slot from '../../components/Slot.astro';
---
<html>
<body>
<h3>Bug: Astro.slots.render() with arguments does not work with &lt;Fragment&gt; slot</h3>
<p>Comment out working example and uncomment non working exmaples</p>
<hr>
<Slot>
<Fragment slot="name">
Test
</Fragment>
</Slot>
<Slot>
<!-- <Fragment slot="name">
{arg => <p>{arg}</p>}
</Fragment> -->
</Slot>
</body>
</html>

View file

@ -61,12 +61,6 @@ describe('Middleware in DEV mode', () => {
expect($('p').html()).to.equal('Not interested');
});
it('should throw an error when locals are not serializable', async () => {
let html = await fixture.fetch('/broken-locals').then((res) => res.text());
let $ = cheerio.load(html);
expect($('title').html()).to.equal('LocalsNotSerializable');
});
it("should throw an error when the middleware doesn't call next or doesn't return a response", async () => {
let html = await fixture.fetch('/does-nothing').then((res) => res.text());
let $ = cheerio.load(html);
@ -180,15 +174,6 @@ describe('Middleware API in PROD mode, SSR', () => {
expect($('p').html()).to.equal('Not interested');
});
it('should NOT throw an error when locals are not serializable', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/broken-locals');
const response = await app.render(request);
const html = await response.text();
const $ = cheerio.load(html);
expect($('title').html()).to.not.equal('LocalsNotSerializable');
});
it("should throws an error when the middleware doesn't call next or doesn't return a response", async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/does-nothing');

View file

@ -35,6 +35,12 @@ describe('set:html', () => {
expect($('#fetched-html')).to.have.a.lengthOf(1);
expect($('#fetched-html').text()).to.equal('works');
});
it('test Fragment when Fragment is as a slot', async () => {
let res = await fixture.fetch('/children');
expect(res.status).to.equal(200);
let html = await res.text();
expect(html).include('Test');
});
});
describe('Build', () => {
@ -77,5 +83,10 @@ describe('set:html', () => {
const $ = cheerio.load(html);
expect($('#readable-inner')).to.have.a.lengthOf(1);
});
it('test Fragment when Fragment is as a slot', async () => {
let res = await fixture.readFile('/children/index.html');
expect(res).include('Test');
});
});
});

View file

@ -16,7 +16,7 @@ describe('Sourcemap', async () => {
});
it('Builds non-empty sourcemap', async () => {
const map = await fixture.readFile('entry.mjs.map');
const map = await fixture.readFile('renderers.mjs.map');
expect(map).to.not.include('"sources":[]');
});
});

View file

@ -42,7 +42,7 @@
"tiny-glob": "^0.2.9"
},
"peerDependencies": {
"astro": "workspace:^2.5.3"
"astro": "workspace:^2.5.4"
},
"devDependencies": {
"astro": "workspace:*",

View file

@ -36,7 +36,7 @@
"esbuild": "^0.15.18"
},
"peerDependencies": {
"astro": "workspace:^2.5.3"
"astro": "workspace:^2.5.4"
},
"devDependencies": {
"astro": "workspace:*",

View file

@ -62,7 +62,7 @@
"vite": "^4.3.1"
},
"peerDependencies": {
"astro": "workspace:^2.5.3",
"astro": "workspace:^2.5.4",
"sharp": ">=0.31.0"
},
"peerDependenciesMeta": {

View file

@ -1,5 +1,4 @@
---
//! astro-head-inject
import type { Config } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import { ComponentNode, createTreeNode } from './TreeNode.js';
@ -15,4 +14,4 @@ const ast = Markdoc.Ast.fromJSON(stringifiedAst);
const content = Markdoc.transform(ast, config);
---
<ComponentNode treeNode={await createTreeNode(content)} />
<ComponentNode treeNode={createTreeNode(content)} />

View file

@ -2,16 +2,7 @@ import type { AstroInstance } from 'astro';
import { Fragment } from 'astro/jsx-runtime';
import type { RenderableTreeNode } from '@markdoc/markdoc';
import Markdoc from '@markdoc/markdoc';
import {
createComponent,
renderComponent,
render,
renderScriptElement,
renderUniqueStylesheet,
createHeadAndContent,
unescapeHTML,
renderTemplate,
} from 'astro/runtime/server/index.js';
import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js';
export type TreeNode =
| {
@ -21,9 +12,6 @@ export type TreeNode =
| {
type: 'component';
component: AstroInstance['default'];
collectedLinks?: string[];
collectedStyles?: string[];
collectedScripts?: string[];
props: Record<string, any>;
children: TreeNode[];
}
@ -44,67 +32,20 @@ export const ComponentNode = createComponent({
)}`,
};
if (treeNode.type === 'component') {
let styles = '',
links = '',
scripts = '';
if (Array.isArray(treeNode.collectedStyles)) {
styles = treeNode.collectedStyles
.map((style: any) =>
renderUniqueStylesheet({
type: 'inline',
content: style,
})
)
.join('');
}
if (Array.isArray(treeNode.collectedLinks)) {
links = treeNode.collectedLinks
.map((link: any) => {
return renderUniqueStylesheet(result, {
href: link[0] === '/' ? link : '/' + link,
});
})
.join('');
}
if (Array.isArray(treeNode.collectedScripts)) {
scripts = treeNode.collectedScripts
.map((script: any) => renderScriptElement(script))
.join('');
}
const head = unescapeHTML(styles + links + scripts);
let headAndContent = createHeadAndContent(
head,
renderTemplate`${renderComponent(
result,
treeNode.component.name,
treeNode.component,
treeNode.props,
slots
)}`
return renderComponent(
result,
treeNode.component.name,
treeNode.component,
treeNode.props,
slots
);
// Let the runtime know that this component is being used.
result.propagators.set(
{},
{
init() {
return headAndContent;
},
}
);
return headAndContent;
}
return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots);
},
propagation: 'self',
propagation: 'none',
});
export async function createTreeNode(
node: RenderableTreeNode | RenderableTreeNode[]
): Promise<TreeNode> {
export function createTreeNode(node: RenderableTreeNode | RenderableTreeNode[]): TreeNode {
if (typeof node === 'string' || typeof node === 'number') {
return { type: 'text', content: String(node) };
} else if (Array.isArray(node)) {
@ -112,17 +53,16 @@ export async function createTreeNode(
type: 'component',
component: Fragment,
props: {},
children: await Promise.all(node.map((child) => createTreeNode(child))),
children: node.map((child) => createTreeNode(child)),
};
} else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) {
return { type: 'text', content: '' };
}
const children = await Promise.all(node.children.map((child) => createTreeNode(child)));
if (typeof node.name === 'function') {
const component = node.name;
const props = node.attributes;
const children = node.children.map((child) => createTreeNode(child));
return {
type: 'component',
@ -130,38 +70,12 @@ export async function createTreeNode(
props,
children,
};
} else if (isPropagatedAssetsModule(node.name)) {
const { collectedStyles, collectedLinks, collectedScripts } = node.name;
const component = (await node.name.getMod())?.default ?? Fragment;
const props = node.attributes;
return {
type: 'component',
component,
collectedStyles,
collectedLinks,
collectedScripts,
props,
children,
};
} else {
return {
type: 'element',
tag: node.name,
attributes: node.attributes,
children,
children: node.children.map((child) => createTreeNode(child)),
};
}
}
type PropagatedAssetsModule = {
__astroPropagation: true;
getMod: () => Promise<AstroInstance['default']>;
collectedStyles: string[];
collectedLinks: string[];
collectedScripts: string[];
};
function isPropagatedAssetsModule(module: any): module is PropagatedAssetsModule {
return typeof module === 'object' && module != null && '__astroPropagation' in module;
}

View file

@ -47,7 +47,7 @@
"zod": "^3.17.3"
},
"peerDependencies": {
"astro": "workspace:^2.5.3"
"astro": "workspace:^2.5.4"
},
"devDependencies": {
"@astrojs/markdown-remark": "^2.2.1",

View file

@ -32,11 +32,7 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
name: '@astrojs/markdoc',
hooks: {
'astro:config:setup': async (params) => {
const {
config: astroConfig,
updateConfig,
addContentEntryType,
} = params as SetupHookParams;
const { config: astroConfig, addContentEntryType } = params as SetupHookParams;
markdocConfigResult = await loadMarkdocConfig(astroConfig);
const userMarkdocConfig = markdocConfigResult?.config ?? {};
@ -53,9 +49,6 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
addContentEntryType({
extensions: ['.mdoc'],
getEntryInfo,
// Markdoc handles script / style propagation
// for Astro components internally
handlePropagation: false,
async getRenderModule({ entry, viteId }) {
const ast = Markdoc.parse(entry.body);
const pluginContext = this;
@ -95,10 +88,7 @@ export default function markdocIntegration(legacyConfig?: any): AstroIntegration
});
}
const res = `import {
createComponent,
renderComponent,
} from 'astro/runtime/server/index.js';
const res = `import { jsx as h } from 'astro/jsx-runtime';
import { Renderer } from '@astrojs/markdoc/components';
import { collectHeadings, setupConfig, Markdoc } from '@astrojs/markdoc/runtime';
import * as entry from ${JSON.stringify(viteId + '?astroContentCollectionEntry')};
@ -129,24 +119,14 @@ export function getHeadings() {
const content = Markdoc.transform(ast, config);
return collectHeadings(Array.isArray(content) ? content : content.children);
}
export async function Content (props) {
const config = setupConfig({
...userConfig,
variables: { ...userConfig.variables, ...props },
}, entry);
export const Content = createComponent({
factory(result, props) {
const config = setupConfig({
...userConfig,
variables: { ...userConfig.variables, ...props },
}, entry);
return renderComponent(
result,
Renderer.name,
Renderer,
{ stringifiedAst, config },
{}
);
},
propagation: 'self',
});`;
return h(Renderer, { config, stringifiedAst });
}`;
return { code: res };
},
contentModuleTypes: await fs.promises.readFile(
@ -154,27 +134,6 @@ export const Content = createComponent({
'utf-8'
),
});
updateConfig({
vite: {
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 === markdocConfigResult?.fileUrl.pathname && id.endsWith('.astro')) {
return this.resolve(id + '?astroPropagatedAssets', importer, {
skipSelf: true,
});
}
},
},
],
},
});
},
'astro:server:setup': async ({ server }) => {
server.watcher.on('all', (event, entry) => {

View file

@ -37,14 +37,13 @@ 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 === 'string'
? { ...attributes, id: slug }
: { ...attributes, id: slug, __collectHeading: true, level };
typeof render === 'function'
? { ...attributes, id: slug, __collectHeading: true, level }
: { ...attributes, id: slug };
return new Markdoc.Tag(render, tagProps, children);
},

View file

@ -55,9 +55,6 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI
new URL('../template/content-module-types.d.ts', import.meta.url),
'utf-8'
),
// MDX can import scripts and styles,
// so wrap all MDX files with script / style propagation checks
handlePropagation: true,
});
const extendMarkdownConfig =

View file

@ -42,7 +42,7 @@
"esbuild": "^0.15.18"
},
"peerDependencies": {
"astro": "workspace:^2.5.3"
"astro": "workspace:^2.5.4"
},
"devDependencies": {
"@netlify/edge-functions": "^2.0.0",

View file

@ -38,7 +38,7 @@
"server-destroy": "^1.0.1"
},
"peerDependencies": {
"astro": "workspace:^2.5.3"
"astro": "workspace:^2.5.4"
},
"devDependencies": {
"@types/send": "^0.17.1",

View file

@ -48,7 +48,7 @@
"vite": "^4.3.1"
},
"peerDependencies": {
"astro": "workspace:^2.5.3",
"astro": "workspace:^2.5.4",
"svelte": "^3.54.0"
},
"engines": {

View file

@ -44,7 +44,7 @@
"vite": "^4.3.1"
},
"peerDependencies": {
"astro": "workspace:^2.5.3",
"astro": "workspace:^2.5.4",
"tailwindcss": "^3.0.24"
},
"pnpm": {

View file

@ -60,7 +60,7 @@
"web-vitals": "^3.1.1"
},
"peerDependencies": {
"astro": "workspace:^2.5.3"
"astro": "workspace:^2.5.4"
},
"devDependencies": {
"@types/set-cookie-parser": "^2.4.2",

View file

@ -56,7 +56,7 @@
"vue": "^3.2.37"
},
"peerDependencies": {
"astro": "workspace:^2.5.3",
"astro": "workspace:^2.5.4",
"vue": "^3.2.30"
},
"engines": {

View file

@ -642,6 +642,9 @@ importers:
ora:
specifier: ^6.1.0
version: 6.1.0
p-limit:
specifier: ^4.0.0
version: 4.0.0
path-to-regexp:
specifier: ^6.2.1
version: 6.2.1