feat: support features for adapters
This commit is contained in:
parent
a0a1ca3e58
commit
a26ef031df
10 changed files with 229 additions and 1 deletions
17
.changeset/sharp-elephants-vanish.md
Normal file
17
.changeset/sharp-elephants-vanish.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
'astro': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Now you can tell Astro if an adapter supports certain features.
|
||||||
|
|
||||||
|
When creating ad adapter, you can specify an object like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// adapter.js
|
||||||
|
setAdapter({
|
||||||
|
// ...
|
||||||
|
supportsFeatures: {
|
||||||
|
edgeMiddleware: "Experimental"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
7
.changeset/tiny-colts-call.md
Normal file
7
.changeset/tiny-colts-call.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
'@astrojs/cloudflare': patch
|
||||||
|
'@astrojs/vercel': patch
|
||||||
|
'@astrojs/node': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Signal Astro the kind of support for of the new created features
|
|
@ -1638,12 +1638,31 @@ export type PaginateFunction = (data: any[], args?: PaginateOptions) => GetStati
|
||||||
|
|
||||||
export type Params = Record<string, string | undefined>;
|
export type Params = Record<string, string | undefined>;
|
||||||
|
|
||||||
|
export type SupportsKind = 'Unsupported' | 'Stable' | 'Experimental' | 'Deprecated';
|
||||||
|
|
||||||
|
export type AstroAdapterSupportsFeatures = {
|
||||||
|
/**
|
||||||
|
* Support when `build.split` is enabled.
|
||||||
|
*/
|
||||||
|
buildSplit?: SupportsKind;
|
||||||
|
/**
|
||||||
|
* Support when `build.ecludeMiddleware` is enabled.
|
||||||
|
*/
|
||||||
|
edgeMiddleware?: SupportsKind;
|
||||||
|
};
|
||||||
|
|
||||||
export interface AstroAdapter {
|
export interface AstroAdapter {
|
||||||
name: string;
|
name: string;
|
||||||
serverEntrypoint?: string;
|
serverEntrypoint?: string;
|
||||||
previewEntrypoint?: string;
|
previewEntrypoint?: string;
|
||||||
exports?: string[];
|
exports?: string[];
|
||||||
args?: any;
|
args?: any;
|
||||||
|
/**
|
||||||
|
* List of features supported by an adapter.
|
||||||
|
*
|
||||||
|
* If the adapter is not able to handle certain configurations, Astro will throw an error.
|
||||||
|
*/
|
||||||
|
supportsFeatures?: AstroAdapterSupportsFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Body = string;
|
type Body = string;
|
||||||
|
|
|
@ -1071,6 +1071,19 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @docs
|
||||||
|
* @message Feature not supported by the adapter.
|
||||||
|
* @description
|
||||||
|
* The adapter doesn't support certain feature enabled via configuration.
|
||||||
|
*/
|
||||||
|
FeatureNotSupportedByAdapter: {
|
||||||
|
title: 'Feature not supported by the adapter.',
|
||||||
|
message: (adapterName: string, featureName: string) => {
|
||||||
|
return `The adapter ${adapterName} doesn't support the feature ${featureName}. Please turn it off.`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @docs
|
* @docs
|
||||||
* @see
|
* @see
|
||||||
|
|
|
@ -4,6 +4,7 @@ import fs from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import type { InlineConfig, ViteDevServer } from 'vite';
|
import type { InlineConfig, ViteDevServer } from 'vite';
|
||||||
import type {
|
import type {
|
||||||
|
AstroAdapter,
|
||||||
AstroConfig,
|
AstroConfig,
|
||||||
AstroRenderer,
|
AstroRenderer,
|
||||||
AstroSettings,
|
AstroSettings,
|
||||||
|
@ -11,13 +12,15 @@ import type {
|
||||||
DataEntryType,
|
DataEntryType,
|
||||||
HookParameters,
|
HookParameters,
|
||||||
RouteData,
|
RouteData,
|
||||||
|
SupportsKind,
|
||||||
} from '../@types/astro.js';
|
} from '../@types/astro.js';
|
||||||
import type { SerializedSSRManifest } from '../core/app/types';
|
import type { SerializedSSRManifest } from '../core/app/types';
|
||||||
import type { PageBuildData } from '../core/build/types';
|
import type { PageBuildData } from '../core/build/types';
|
||||||
import { buildClientDirectiveEntrypoint } from '../core/client-directive/index.js';
|
import { buildClientDirectiveEntrypoint } from '../core/client-directive/index.js';
|
||||||
import { mergeConfig } from '../core/config/config.js';
|
import { mergeConfig } from '../core/config/config.js';
|
||||||
import { info, type LogOptions } from '../core/logger/core.js';
|
import { error, info, type LogOptions, warn } from '../core/logger/core.js';
|
||||||
import { isServerLikeOutput } from '../prerender/utils.js';
|
import { isServerLikeOutput } from '../prerender/utils.js';
|
||||||
|
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||||
|
|
||||||
async function withTakingALongTimeMsg<T>({
|
async function withTakingALongTimeMsg<T>({
|
||||||
name,
|
name,
|
||||||
|
@ -178,6 +181,7 @@ export async function runHookConfigDone({
|
||||||
`Integration "${integration.name}" conflicts with "${settings.adapter.name}". You can only configure one deployment integration.`
|
`Integration "${integration.name}" conflicts with "${settings.adapter.name}". You can only configure one deployment integration.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
validateSupportedFeatures(adapter, settings.config, logging);
|
||||||
settings.adapter = adapter;
|
settings.adapter = adapter;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -187,6 +191,101 @@ export async function runHookConfigDone({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an adapter supports certain features that are enabled via Astro configuration.
|
||||||
|
*
|
||||||
|
* If a configuration is enabled and "unlocks" a feature, but the adapter doesn't support, the function
|
||||||
|
* will throw a runtime error.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function validateSupportedFeatures(
|
||||||
|
adapter: AstroAdapter,
|
||||||
|
config: AstroConfig,
|
||||||
|
logging: LogOptions
|
||||||
|
) {
|
||||||
|
let supportsFeatures = adapter.supportsFeatures ?? {
|
||||||
|
buildSplit: 'Unsupported',
|
||||||
|
edgeMiddleware: 'Unsupported',
|
||||||
|
};
|
||||||
|
const { buildSplit, edgeMiddleware } = supportsFeatures;
|
||||||
|
if (buildSplit) {
|
||||||
|
validateSupportKind(
|
||||||
|
buildSplit,
|
||||||
|
adapter.name,
|
||||||
|
logging,
|
||||||
|
'build.split',
|
||||||
|
() => config.build.split === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edgeMiddleware) {
|
||||||
|
validateSupportKind(
|
||||||
|
edgeMiddleware,
|
||||||
|
adapter.name,
|
||||||
|
logging,
|
||||||
|
'build.excludeMiddleware',
|
||||||
|
() => config.build.excludeMiddleware === true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const STABLE = 'Stable';
|
||||||
|
const DEPRECATED = 'Deprecated';
|
||||||
|
const UNSUPPORTED = 'Unsupported';
|
||||||
|
const EXPERIMENTAL = 'Experimental';
|
||||||
|
|
||||||
|
function validateSupportKind(
|
||||||
|
supportKind: SupportsKind,
|
||||||
|
adapterName: string,
|
||||||
|
logging: LogOptions,
|
||||||
|
featureName: string,
|
||||||
|
validation: () => boolean
|
||||||
|
) {
|
||||||
|
switch (supportKind) {
|
||||||
|
case DEPRECATED: {
|
||||||
|
featureIsDeprecated(adapterName, logging);
|
||||||
|
}
|
||||||
|
case UNSUPPORTED: {
|
||||||
|
if (validation()) {
|
||||||
|
featureIsUnsupported(adapterName, logging, featureName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case EXPERIMENTAL: {
|
||||||
|
featureIsExperimental(adapterName, logging);
|
||||||
|
}
|
||||||
|
case STABLE: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function featureIsUnsupported(adapterName: string, logging: LogOptions, featureName: string) {
|
||||||
|
error(
|
||||||
|
logging,
|
||||||
|
`adapter/${adapterName}`,
|
||||||
|
`The feature ${featureName} is not supported by ${adapterName}.`
|
||||||
|
);
|
||||||
|
throw new AstroError({
|
||||||
|
...AstroErrorData.FeatureNotSupportedByAdapter,
|
||||||
|
message: AstroErrorData.FeatureNotSupportedByAdapter.message(adapterName, featureName),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function featureIsExperimental(adapterName: string, logging: LogOptions) {
|
||||||
|
warn(
|
||||||
|
logging,
|
||||||
|
`adapter/${adapterName}`,
|
||||||
|
'The feature is experimental and subject to issues or changes.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function featureIsDeprecated(adapterName: string, logging: LogOptions) {
|
||||||
|
warn(
|
||||||
|
logging,
|
||||||
|
`adapter/${adapterName}`,
|
||||||
|
'The feature is deprecated and will be moved in the next release.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function runHookServerSetup({
|
export async function runHookServerSetup({
|
||||||
config,
|
config,
|
||||||
server,
|
server,
|
||||||
|
|
55
packages/astro/test/featuresSupport.test.js
Normal file
55
packages/astro/test/featuresSupport.test.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { loadFixture } from './test-utils.js';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import testAdapter from './test-adapter.js';
|
||||||
|
|
||||||
|
describe('Adapter', () => {
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
it("should error if the adapter doesn't support edge middleware", async () => {
|
||||||
|
try {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/middleware-dev/',
|
||||||
|
output: 'server',
|
||||||
|
build: {
|
||||||
|
excludeMiddleware: true,
|
||||||
|
},
|
||||||
|
adapter: testAdapter({
|
||||||
|
extendAdapter: {
|
||||||
|
supportsFeatures: {
|
||||||
|
edgeMiddleware: 'Unsupported',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
await fixture.build();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.toString()).to.contain(
|
||||||
|
"The adapter my-ssr-adapter doesn't support the feature build.excludeMiddleware."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error if the adapter doesn't support split build", async () => {
|
||||||
|
try {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: './fixtures/middleware-dev/',
|
||||||
|
output: 'server',
|
||||||
|
build: {
|
||||||
|
split: true,
|
||||||
|
},
|
||||||
|
adapter: testAdapter({
|
||||||
|
extendAdapter: {
|
||||||
|
supportsFeatures: {
|
||||||
|
buildSplit: 'Unsupported',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
await fixture.build();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.toString()).to.contain(
|
||||||
|
"The adapter my-ssr-adapter doesn't support the feature build.split."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -71,6 +71,10 @@ export default function (
|
||||||
name: 'my-ssr-adapter',
|
name: 'my-ssr-adapter',
|
||||||
serverEntrypoint: '@my-ssr',
|
serverEntrypoint: '@my-ssr',
|
||||||
exports: ['manifest', 'createApp'],
|
exports: ['manifest', 'createApp'],
|
||||||
|
supportsFeatures: {
|
||||||
|
edgeMiddleware: 'Stable',
|
||||||
|
buildSplit: 'Stable',
|
||||||
|
},
|
||||||
...extendAdapter,
|
...extendAdapter,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,11 +24,17 @@ export function getAdapter(isModeDirectory: boolean): AstroAdapter {
|
||||||
name: '@astrojs/cloudflare',
|
name: '@astrojs/cloudflare',
|
||||||
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
|
serverEntrypoint: '@astrojs/cloudflare/server.directory.js',
|
||||||
exports: ['onRequest', 'manifest'],
|
exports: ['onRequest', 'manifest'],
|
||||||
|
supportsFeatures: {
|
||||||
|
buildSplit: 'Experimental',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name: '@astrojs/cloudflare',
|
name: '@astrojs/cloudflare',
|
||||||
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
|
serverEntrypoint: '@astrojs/cloudflare/server.advanced.js',
|
||||||
exports: ['default'],
|
exports: ['default'],
|
||||||
|
supportsFeatures: {
|
||||||
|
buildSplit: 'Experimental',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@ export function getAdapter(options: Options): AstroAdapter {
|
||||||
previewEntrypoint: '@astrojs/node/preview.js',
|
previewEntrypoint: '@astrojs/node/preview.js',
|
||||||
exports: ['handler', 'startServer'],
|
exports: ['handler', 'startServer'],
|
||||||
args: options,
|
args: options,
|
||||||
|
supportsFeatures: {
|
||||||
|
edgeMiddleware: 'Stable',
|
||||||
|
buildSplit: 'Stable',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,10 @@ function getAdapter(): AstroAdapter {
|
||||||
name: PACKAGE_NAME,
|
name: PACKAGE_NAME,
|
||||||
serverEntrypoint: `${PACKAGE_NAME}/entrypoint`,
|
serverEntrypoint: `${PACKAGE_NAME}/entrypoint`,
|
||||||
exports: ['default'],
|
exports: ['default'],
|
||||||
|
supportsFeatures: {
|
||||||
|
buildSplit: 'Experimental',
|
||||||
|
edgeMiddleware: 'Experimental',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue