summaryrefslogtreecommitdiff
path: root/packages/astro
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro')
-rw-r--r--packages/astro/src/@types/astro.ts25
-rw-r--r--packages/astro/src/core/build/index.ts22
-rw-r--r--packages/astro/src/core/build/internal.ts1
-rw-r--r--packages/astro/src/core/build/plugins/index.ts2
-rw-r--r--packages/astro/src/core/build/plugins/plugin-middleware.ts33
-rw-r--r--packages/astro/src/core/build/plugins/plugin-pages.ts11
-rw-r--r--packages/astro/src/core/build/plugins/plugin-ssr.ts2
-rw-r--r--packages/astro/src/core/build/static-build.ts3
-rw-r--r--packages/astro/src/core/config/schema.ts9
-rw-r--r--packages/astro/src/core/endpoint/index.ts21
-rw-r--r--packages/astro/src/core/middleware/index.ts102
-rw-r--r--packages/astro/src/integrations/index.ts33
-rw-r--r--packages/astro/test/middleware.test.js49
-rw-r--r--packages/astro/test/ssr-split-manifest.test.js4
-rw-r--r--packages/astro/test/test-adapter.js3
15 files changed, 281 insertions, 39 deletions
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 185401a89..12f309f1a 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -849,6 +849,27 @@ export interface AstroUserConfig {
* ```
*/
split?: boolean;
+
+ /**
+ * @docs
+ * @name build.excludeMiddleware
+ * @type {boolean}
+ * @default {false}
+ * @version 2.8.0
+ * @description
+ * Defines whether or not any SSR middleware code will be bundled when built.
+ *
+ * When enabled, middleware code is not bundled and imported by all pages during the build. To instead execute and import middleware code manually, set `build.excludeMiddleware: true`:
+ *
+ * ```js
+ * {
+ * build: {
+ * excludeMiddleware: true
+ * }
+ * }
+ * ```
+ */
+ excludeMiddleware?: boolean;
};
/**
@@ -1842,6 +1863,10 @@ export interface AstroIntegration {
* the physical file you should import.
*/
entryPoints: Map<RouteData, URL>;
+ /**
+ * File path of the emitted middleware
+ */
+ middlewareEntryPoint: URL | undefined;
}) => void | Promise<void>;
'astro:build:start'?: () => void | Promise<void>;
'astro:build:setup'?: (options: {
diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts
index 037c462fd..11e2b1fa9 100644
--- a/packages/astro/src/core/build/index.ts
+++ b/packages/astro/src/core/build/index.ts
@@ -1,5 +1,4 @@
import type { AstroConfig, AstroSettings, ManifestData, RuntimeMode } from '../../@types/astro';
-
import fs from 'fs';
import * as colors from 'kleur/colors';
import { performance } from 'perf_hooks';
@@ -12,7 +11,7 @@ import {
runHookConfigSetup,
} from '../../integrations/index.js';
import { createVite } from '../create-vite.js';
-import { debug, info, levels, timerMessage, type LogOptions } from '../logger/core.js';
+import { debug, info, warn, levels, timerMessage, type LogOptions } from '../logger/core.js';
import { printHelp } from '../messages.js';
import { apply as applyPolyfill } from '../polyfill.js';
import { RouteCache } from '../render/route-cache.js';
@@ -211,6 +210,25 @@ class AstroBuilder {
`the outDir cannot be the root folder. Please build to a folder such as dist.`
);
}
+
+ if (config.build.split === true) {
+ if (config.output === 'static') {
+ warn(
+ this.logging,
+ 'configuration',
+ 'The option `build.split` won\'t take effect, because `output` is not `"server"` or `"hybrid"`.'
+ );
+ }
+ }
+ if (config.build.excludeMiddleware === true) {
+ if (config.output === 'static') {
+ warn(
+ this.logging,
+ 'configuration',
+ 'The option `build.excludeMiddleware` won\'t take effect, because `output` is not `"server"` or `"hybrid"`.'
+ );
+ }
+ }
}
/** Stats */
diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts
index 28d15d874..5dff6f3dd 100644
--- a/packages/astro/src/core/build/internal.ts
+++ b/packages/astro/src/core/build/internal.ts
@@ -88,6 +88,7 @@ export interface BuildInternals {
entryPoints: Map<RouteData, URL>;
ssrSplitEntryChunks: Map<string, Rollup.OutputChunk>;
componentMetadata: SSRResult['componentMetadata'];
+ middlewareEntryPoint?: URL;
}
/**
diff --git a/packages/astro/src/core/build/plugins/index.ts b/packages/astro/src/core/build/plugins/index.ts
index 160e18fdd..3a44824d6 100644
--- a/packages/astro/src/core/build/plugins/index.ts
+++ b/packages/astro/src/core/build/plugins/index.ts
@@ -19,7 +19,7 @@ export function registerAllPlugins({ internals, options, register }: AstroBuildP
register(pluginAnalyzer(internals));
register(pluginInternals(internals));
register(pluginRenderers(options));
- register(pluginMiddleware(options));
+ register(pluginMiddleware(options, internals));
register(pluginPages(options, internals));
register(pluginCSS(options, internals));
register(astroHeadBuildPlugin(internals));
diff --git a/packages/astro/src/core/build/plugins/plugin-middleware.ts b/packages/astro/src/core/build/plugins/plugin-middleware.ts
index dee73d2f8..6db39733e 100644
--- a/packages/astro/src/core/build/plugins/plugin-middleware.ts
+++ b/packages/astro/src/core/build/plugins/plugin-middleware.ts
@@ -3,12 +3,17 @@ import { MIDDLEWARE_PATH_SEGMENT_NAME } from '../../constants.js';
import { addRollupInput } from '../add-rollup-input.js';
import type { AstroBuildPlugin } from '../plugin';
import type { StaticBuildOptions } from '../types';
+import type { BuildInternals } from '../internal';
export const MIDDLEWARE_MODULE_ID = '@astro-middleware';
const EMPTY_MIDDLEWARE = '\0empty-middleware';
-export function vitePluginMiddleware(opts: StaticBuildOptions): VitePlugin {
+export function vitePluginMiddleware(
+ opts: StaticBuildOptions,
+ internals: BuildInternals
+): VitePlugin {
+ let resolvedMiddlewareId: string;
return {
name: '@astro/plugin-middleware',
@@ -22,6 +27,7 @@ export function vitePluginMiddleware(opts: StaticBuildOptions): VitePlugin {
`${opts.settings.config.srcDir.pathname}/${MIDDLEWARE_PATH_SEGMENT_NAME}`
);
if (middlewareId) {
+ resolvedMiddlewareId = middlewareId.id;
return middlewareId.id;
} else {
return EMPTY_MIDDLEWARE;
@@ -35,18 +41,39 @@ export function vitePluginMiddleware(opts: StaticBuildOptions): VitePlugin {
load(id) {
if (id === EMPTY_MIDDLEWARE) {
return 'export const onRequest = undefined';
+ } else if (id === resolvedMiddlewareId) {
+ this.emitFile({
+ type: 'chunk',
+ preserveSignature: 'strict',
+ fileName: 'middleware.mjs',
+ id,
+ });
+ }
+ },
+
+ writeBundle(_, bundle) {
+ for (const [chunkName, chunk] of Object.entries(bundle)) {
+ if (chunk.type === 'asset') {
+ continue;
+ }
+ if (chunk.fileName === 'middleware.mjs') {
+ internals.middlewareEntryPoint = new URL(chunkName, opts.settings.config.build.server);
+ }
}
},
};
}
-export function pluginMiddleware(opts: StaticBuildOptions): AstroBuildPlugin {
+export function pluginMiddleware(
+ opts: StaticBuildOptions,
+ internals: BuildInternals
+): AstroBuildPlugin {
return {
build: 'ssr',
hooks: {
'build:before': () => {
return {
- vitePlugin: vitePluginMiddleware(opts),
+ vitePlugin: vitePluginMiddleware(opts, internals),
};
},
},
diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts
index cf078f0b5..2ee438a6a 100644
--- a/packages/astro/src/core/build/plugins/plugin-pages.ts
+++ b/packages/astro/src/core/build/plugins/plugin-pages.ts
@@ -73,10 +73,13 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
exports.push(`export { renderers };`);
- const middlewareModule = await this.resolve(MIDDLEWARE_MODULE_ID);
- if (middlewareModule) {
- imports.push(`import { onRequest } from "${middlewareModule.id}";`);
- exports.push(`export { onRequest };`);
+ // The middleware should not be imported by the pages
+ if (!opts.settings.config.build.excludeMiddleware) {
+ const middlewareModule = await this.resolve(MIDDLEWARE_MODULE_ID);
+ if (middlewareModule) {
+ imports.push(`import { onRequest } from "${middlewareModule.id}";`);
+ exports.push(`export { onRequest };`);
+ }
}
return `${imports.join('\n')}${exports.join('\n')}`;
diff --git a/packages/astro/src/core/build/plugins/plugin-ssr.ts b/packages/astro/src/core/build/plugins/plugin-ssr.ts
index 41f38a8b2..514fe2409 100644
--- a/packages/astro/src/core/build/plugins/plugin-ssr.ts
+++ b/packages/astro/src/core/build/plugins/plugin-ssr.ts
@@ -138,6 +138,7 @@ export function pluginSSR(
manifest,
logging: options.logging,
entryPoints: internals.entryPoints,
+ middlewareEntryPoint: internals.middlewareEntryPoint,
});
const code = injectManifest(manifest, internals.ssrEntryChunk);
mutate(internals.ssrEntryChunk, 'server', code);
@@ -260,6 +261,7 @@ export function pluginSSRSplit(
manifest,
logging: options.logging,
entryPoints: internals.entryPoints,
+ middlewareEntryPoint: internals.middlewareEntryPoint,
});
for (const [, chunk] of internals.ssrSplitEntryChunks) {
const code = injectManifest(manifest, chunk);
diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts
index 59a42db34..9bef0d681 100644
--- a/packages/astro/src/core/build/static-build.ts
+++ b/packages/astro/src/core/build/static-build.ts
@@ -26,7 +26,6 @@ import { generatePages } from './generate.js';
import { trackPageData } from './internal.js';
import { createPluginContainer, type AstroBuildPluginContainer } from './plugin.js';
import { registerAllPlugins } from './plugins/index.js';
-import { MIDDLEWARE_MODULE_ID } from './plugins/plugin-middleware.js';
import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';
import { RESOLVED_RENDERERS_MODULE_ID } from './plugins/plugin-renderers.js';
import { RESOLVED_SPLIT_MODULE_ID, SSR_VIRTUAL_MODULE_ID } from './plugins/plugin-ssr.js';
@@ -183,8 +182,6 @@ async function ssrBuild(
);
} else if (chunkInfo.facadeModuleId?.startsWith(RESOLVED_SPLIT_MODULE_ID)) {
return makeSplitEntryPointFileName(chunkInfo.facadeModuleId, routes);
- } else if (chunkInfo.facadeModuleId === MIDDLEWARE_MODULE_ID) {
- return 'middleware.mjs';
} else if (chunkInfo.facadeModuleId === SSR_VIRTUAL_MODULE_ID) {
return opts.settings.config.build.serverEntry;
} else if (chunkInfo.facadeModuleId === RESOLVED_RENDERERS_MODULE_ID) {
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 7410df470..ae681a543 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -25,6 +25,7 @@ const ASTRO_CONFIG_DEFAULTS = {
redirects: true,
inlineStylesheets: 'never',
split: false,
+ excludeMiddleware: false,
},
compressHTML: false,
server: {
@@ -122,6 +123,10 @@ export const AstroConfigSchema = z.object({
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
+ excludeMiddleware: z
+ .boolean()
+ .optional()
+ .default(ASTRO_CONFIG_DEFAULTS.build.excludeMiddleware),
})
.optional()
.default({}),
@@ -283,6 +288,10 @@ export function createRelativeSchema(cmd: string, fileProtocolRoot: URL) {
.default(ASTRO_CONFIG_DEFAULTS.build.inlineStylesheets),
split: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.build.split),
+ excludeMiddleware: z
+ .boolean()
+ .optional()
+ .default(ASTRO_CONFIG_DEFAULTS.build.excludeMiddleware),
})
.optional()
.default({}),
diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts
index dde07cd9c..33cb113a2 100644
--- a/packages/astro/src/core/endpoint/index.ts
+++ b/packages/astro/src/core/endpoint/index.ts
@@ -31,19 +31,26 @@ type EndpointCallResult =
response: Response;
};
+type CreateAPIContext = {
+ request: Request;
+ params: Params;
+ site?: string;
+ props: Record<string, any>;
+ adapterName?: string;
+};
+
+/**
+ * Creates a context that holds all the information needed to handle an Astro endpoint.
+ *
+ * @param {CreateAPIContext} payload
+ */
export function createAPIContext({
request,
params,
site,
props,
adapterName,
-}: {
- request: Request;
- params: Params;
- site?: string;
- props: Record<string, any>;
- adapterName?: string;
-}): APIContext {
+}: CreateAPIContext): APIContext {
const context = {
cookies: new AstroCookies(request),
request,
diff --git a/packages/astro/src/core/middleware/index.ts b/packages/astro/src/core/middleware/index.ts
index f9fb07bd4..47127c674 100644
--- a/packages/astro/src/core/middleware/index.ts
+++ b/packages/astro/src/core/middleware/index.ts
@@ -1,9 +1,107 @@
-import type { MiddlewareResponseHandler } from '../../@types/astro';
+import type { MiddlewareResponseHandler, Params } from '../../@types/astro';
import { sequence } from './sequence.js';
+import { createAPIContext } from '../endpoint/index.js';
function defineMiddleware(fn: MiddlewareResponseHandler) {
return fn;
}
+/**
+ * Payload for creating a context to be passed to Astro middleware
+ */
+export type CreateContext = {
+ /**
+ * The incoming request
+ */
+ request: Request;
+ /**
+ * Optional parameters
+ */
+ params?: Params;
+};
+
+/**
+ * Creates a context to be passed to Astro middleware `onRequest` function.
+ */
+function createContext({ request, params }: CreateContext) {
+ return createAPIContext({
+ request,
+ params: params ?? {},
+ props: {},
+ site: undefined,
+ });
+}
+
+/**
+ * Checks whether the passed `value` is serializable.
+ *
+ * A serializable value contains plain values. For example, `Proxy`, `Set`, `Map`, functions, etc.
+ * are not accepted because they can't be serialized.
+ */
+function isLocalsSerializable(value: unknown): boolean {
+ let type = typeof value;
+ let plainObject = true;
+ if (type === 'object' && isPlainObject(value)) {
+ for (const [, nestedValue] of Object.entries(value)) {
+ if (!isLocalsSerializable(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;
+}
+
+/**
+ * It attempts to serialize `value` and return it as a string.
+ *
+ * ## Errors
+ * If the `value` is not serializable if the function will throw a runtime error.
+ *
+ * Something is **not serializable** when it contains properties/values like functions, `Map`, `Set`, `Date`,
+ * and other types that can't be made a string.
+ *
+ * @param value
+ */
+function trySerializeLocals(value: unknown) {
+ if (isLocalsSerializable(value)) {
+ return JSON.stringify(value);
+ } else {
+ throw new Error("The passed value can't be serialized.");
+ }
+}
+
// NOTE: this export must export only the functions that will be exposed to user-land as officials APIs
-export { sequence, defineMiddleware };
+export { sequence, defineMiddleware, createContext, trySerializeLocals };
diff --git a/packages/astro/src/integrations/index.ts b/packages/astro/src/integrations/index.ts
index eaf4b21d1..b243ba979 100644
--- a/packages/astro/src/integrations/index.ts
+++ b/packages/astro/src/integrations/index.ts
@@ -298,22 +298,30 @@ export async function runHookBuildSetup({
return updatedConfig;
}
+type RunHookBuildSsr = {
+ config: AstroConfig;
+ manifest: SerializedSSRManifest;
+ logging: LogOptions;
+ entryPoints: Map<RouteData, URL>;
+ middlewareEntryPoint: URL | undefined;
+};
+
export async function runHookBuildSsr({
config,
manifest,
logging,
entryPoints,
-}: {
- config: AstroConfig;
- manifest: SerializedSSRManifest;
- logging: LogOptions;
- entryPoints: Map<RouteData, URL>;
-}) {
+ middlewareEntryPoint,
+}: RunHookBuildSsr) {
for (const integration of config.integrations) {
if (integration?.hooks?.['astro:build:ssr']) {
await withTakingALongTimeMsg({
name: integration.name,
- hookResult: integration.hooks['astro:build:ssr']({ manifest, entryPoints }),
+ hookResult: integration.hooks['astro:build:ssr']({
+ manifest,
+ entryPoints,
+ middlewareEntryPoint,
+ }),
logging,
});
}
@@ -340,17 +348,14 @@ export async function runHookBuildGenerated({
}
}
-export async function runHookBuildDone({
- config,
- pages,
- routes,
- logging,
-}: {
+type RunHookBuildDone = {
config: AstroConfig;
pages: string[];
routes: RouteData[];
logging: LogOptions;
-}) {
+};
+
+export async function runHookBuildDone({ config, pages, routes, logging }: RunHookBuildDone) {
const dir = isServerLikeOutput(config) ? config.build.client : config.outDir;
await fs.promises.mkdir(dir, { recursive: true });
diff --git a/packages/astro/test/middleware.test.js b/packages/astro/test/middleware.test.js
index e2c57bafb..9e2213146 100644
--- a/packages/astro/test/middleware.test.js
+++ b/packages/astro/test/middleware.test.js
@@ -2,6 +2,8 @@ import { loadFixture } from './test-utils.js';
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import testAdapter from './test-adapter.js';
+import { fileURLToPath } from 'node:url';
+import { readFileSync, existsSync } from 'node:fs';
describe('Middleware in DEV mode', () => {
/** @type {import('./test-utils').Fixture} */
@@ -104,12 +106,19 @@ describe('Middleware in PROD mode, SSG', () => {
describe('Middleware API in PROD mode, SSR', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
+ let middlewarePath;
before(async () => {
fixture = await loadFixture({
root: './fixtures/middleware-dev/',
output: 'server',
- adapter: testAdapter({}),
+ adapter: testAdapter({
+ setEntryPoints(entryPointsOrMiddleware) {
+ if (entryPointsOrMiddleware instanceof URL) {
+ middlewarePath = entryPointsOrMiddleware;
+ }
+ },
+ }),
});
await fixture.build();
});
@@ -201,6 +210,18 @@ describe('Middleware API in PROD mode, SSR', () => {
const text = await response.text();
expect(text.includes('REDACTED')).to.be.true;
});
+
+ it('the integration should receive the path to the middleware', async () => {
+ expect(middlewarePath).to.not.be.undefined;
+ try {
+ const path = fileURLToPath(middlewarePath);
+ expect(existsSync(path)).to.be.true;
+ const content = readFileSync(fileURLToPath(middlewarePath), 'utf-8');
+ expect(content.length).to.be.greaterThan(0);
+ } catch (e) {
+ throw e;
+ }
+ });
});
describe('Middleware with tailwind', () => {
@@ -224,3 +245,29 @@ describe('Middleware with tailwind', () => {
expect(bundledCSS.includes('--tw-content')).to.be.true;
});
});
+
+describe('Middleware, split middleware option', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: './fixtures/middleware-dev/',
+ output: 'server',
+ build: {
+ excludeMiddleware: true,
+ },
+ adapter: testAdapter({}),
+ });
+ await fixture.build();
+ });
+
+ it('should not render locals data because the page does not export it', async () => {
+ const app = await fixture.loadTestAdapterApp();
+ const request = new Request('http://example.com/');
+ const response = await app.render(request);
+ const html = await response.text();
+ const $ = cheerio.load(html);
+ expect($('p').html()).to.not.equal('bar');
+ });
+});
diff --git a/packages/astro/test/ssr-split-manifest.test.js b/packages/astro/test/ssr-split-manifest.test.js
index 9e8a0981e..394740395 100644
--- a/packages/astro/test/ssr-split-manifest.test.js
+++ b/packages/astro/test/ssr-split-manifest.test.js
@@ -18,7 +18,9 @@ describe('astro:ssr-manifest, split', () => {
output: 'server',
adapter: testAdapter({
setEntryPoints(entries) {
- entryPoints = entries;
+ if (entries) {
+ entryPoints = entries;
+ }
},
setRoutes(routes) {
currentRoutes = routes;
diff --git a/packages/astro/test/test-adapter.js b/packages/astro/test/test-adapter.js
index af5a7777b..ed79e5f21 100644
--- a/packages/astro/test/test-adapter.js
+++ b/packages/astro/test/test-adapter.js
@@ -74,9 +74,10 @@ export default function (
...extendAdapter,
});
},
- 'astro:build:ssr': ({ entryPoints }) => {
+ 'astro:build:ssr': ({ entryPoints, middlewareEntryPoint }) => {
if (setEntryPoints) {
setEntryPoints(entryPoints);
+ setEntryPoints(middlewareEntryPoint);
}
},
'astro:build:done': ({ routes }) => {