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

feat(vercel): maxDuration config #8867

Merged
merged 10 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/lazy-actors-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@astrojs/vercel': minor
---

You can now configure how long your functions can run before timing out.

```diff
export default defineConfig({
output: "server",
adapter: vercel({
+ maxDuration: 60
}),
});
```
20 changes: 20 additions & 0 deletions packages/integrations/vercel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,26 @@ export default defineConfig({
});
```

### maxDuration

**Type:** `number`<br>
**Available for:** Serverless

Use this property to extend or limit the maximum duration (in seconds) that the Serverless Function can run before timing out. See [Vercel documentation](https://vercel.com/docs/functions/serverless-functions/runtimes#maxduration) to find out the valid range for your plan.
lilnasy marked this conversation as resolved.
Show resolved Hide resolved

```js
lilnasy marked this conversation as resolved.
Show resolved Hide resolved
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
output: "server",
adapter: vercel({
+ maxDuration: 60
}),
});
```

### Function bundling configuration

The Vercel adapter combines all of your routes into a single function by default.
Expand Down
151 changes: 109 additions & 42 deletions packages/integrations/vercel/src/serverless/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,36 @@ export interface VercelServerlessConfig {
* @deprecated
*/
analytics?: boolean;

/** Configuration for [Vercel Web Analytics](https://vercel.com/docs/concepts/analytics). */
webAnalytics?: VercelWebAnalyticsConfig;

/** Configuration for [Vercel Speed Insights](https://vercel.com/docs/concepts/speed-insights). */
speedInsights?: VercelSpeedInsightsConfig;

/** Force files to be bundled with your function. This is helpful when you notice missing files. */
includeFiles?: string[];

/** Exclude any files from the bundling process that would otherwise be included. */
excludeFiles?: string[];

/** When enabled, an Image Service powered by the Vercel Image Optimization API will be automatically configured and used in production. In development, the image service specified by devImageService will be used instead. */
imageService?: boolean;

/** Configuration options for [Vercel’s Image Optimization API](https://vercel.com/docs/concepts/image-optimization). See [Vercel’s image configuration documentation](https://vercel.com/docs/build-output-api/v3/configuration#images) for a complete list of supported parameters. */
imagesConfig?: VercelImageConfig;

/** Allows you to configure which image service to use in development when imageService is enabled. */
devImageService?: DevImageService;

/** Whether to create the Vercel Edge middleware from an Astro middleware in your code base. */
edgeMiddleware?: boolean;

/** Whether to split builds into a separate function for each route. */
functionPerRoute?: boolean;

/** Maximum duration (in seconds) that the function can for before timing out. See [Vercel documentation](https://vercel.com/docs/functions/serverless-functions/runtimes#maxduration) for the allowed values for your plan. */
lilnasy marked this conversation as resolved.
Show resolved Hide resolved
maxDuration?: number;
}

export default function vercelServerless({
Expand All @@ -97,7 +118,18 @@ export default function vercelServerless({
devImageService = 'sharp',
functionPerRoute = false,
edgeMiddleware = false,
maxDuration,
}: VercelServerlessConfig = {}): AstroIntegration {

if (maxDuration) {
if (typeof maxDuration !== 'number') {
throw new TypeError(`maxDuration must be a number`, { cause: maxDuration });
}
if (maxDuration <= 0) {
throw new TypeError(`maxDuration must be a positive number`, { cause: maxDuration });
}
}

let _config: AstroConfig;
let buildTempFolder: URL;
let serverEntry: string;
Expand All @@ -107,45 +139,16 @@ export default function vercelServerless({

const NTF_CACHE = Object.create(null);

async function createFunctionFolder(
funcName: string,
entry: URL,
inc: URL[],
logger: AstroIntegrationLogger
) {
const functionFolder = new URL(`./functions/${funcName}.func/`, _config.outDir);

// Copy necessary files (e.g. node_modules/)
const { handler } = await copyDependenciesToFunction(
{
entry,
outDir: functionFolder,
includeFiles: inc,
excludeFiles: excludeFiles?.map((file) => new URL(file, _config.root)) || [],
logger,
},
NTF_CACHE
);

// Enable ESM
// https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/
await writeJson(new URL(`./package.json`, functionFolder), {
type: 'module',
});

// Serverless function config
// https://vercel.com/docs/build-output-api/v3#vercel-primitives/serverless-functions/configuration
await writeJson(new URL(`./.vc-config.json`, functionFolder), {
runtime: getRuntime(),
handler,
launcherType: 'Nodejs',
});
}

return {
name: PACKAGE_NAME,
hooks: {
'astro:config:setup': async ({ command, config, updateConfig, injectScript, logger }) => {

if (maxDuration && maxDuration > 900) {
logger.warn(`maxDuration is set to ${maxDuration} seconds, which is longer than the maximum allowed duration of 900 seconds.`)
logger.warn(`Please make sure that your plan allows for this duration. See https://vercel.com/docs/functions/serverless-functions/runtimes#maxduration for more information.`)
}

if (webAnalytics?.enabled || analytics) {
if (analytics) {
logger.warn(
Expand Down Expand Up @@ -261,19 +264,32 @@ You can set functionPerRoute: false to prevent surpassing the limit.`
? getRouteFuncName(route)
: getFallbackFuncName(entryFile);

await createFunctionFolder(func, entryFile, filesToInclude, logger);
await createFunctionFolder({
functionName: func,
entry: entryFile,
config: _config,
logger,
NTF_CACHE,
includeFiles: filesToInclude,
excludeFiles,
maxDuration
});
routeDefinitions.push({
src: route.pattern.source,
dest: func,
});
}
} else {
await createFunctionFolder(
'render',
new URL(serverEntry, buildTempFolder),
filesToInclude,
logger
);
await createFunctionFolder({
functionName: 'render',
entry: new URL(serverEntry, buildTempFolder),
config: _config,
logger,
NTF_CACHE,
includeFiles: filesToInclude,
excludeFiles,
maxDuration
});
routeDefinitions.push({ src: '/.*', dest: 'render' });
}

Expand Down Expand Up @@ -314,6 +330,57 @@ You can set functionPerRoute: false to prevent surpassing the limit.`
};
}

interface CreateFunctionFolderArgs {
functionName: string
entry: URL
config: AstroConfig
logger: AstroIntegrationLogger
NTF_CACHE: any
includeFiles: URL[]
excludeFiles?: string[]
maxDuration?: number
}

async function createFunctionFolder({
functionName,
entry,
config,
logger,
NTF_CACHE,
includeFiles,
excludeFiles,
maxDuration,
}: CreateFunctionFolderArgs) {
const functionFolder = new URL(`./functions/${functionName}.func/`, config.outDir);

// Copy necessary files (e.g. node_modules/)
const { handler } = await copyDependenciesToFunction(
{
entry,
outDir: functionFolder,
includeFiles,
excludeFiles: excludeFiles?.map((file) => new URL(file, config.root)) || [],
logger,
},
NTF_CACHE
);

// Enable ESM
// https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/
await writeJson(new URL(`./package.json`, functionFolder), {
type: 'module',
});

// Serverless function config
// https://vercel.com/docs/build-output-api/v3#vercel-primitives/serverless-functions/configuration
await writeJson(new URL(`./.vc-config.json`, functionFolder), {
runtime: getRuntime(),
handler,
launcherType: 'Nodejs',
maxDuration,
Copy link
Contributor Author

@lilnasy lilnasy Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new part. Commenting for quick visibility.

Relevant Vercel docs: https://vercel.com/docs/build-output-api/v3/primitives#base-config.

});
}

function validateRuntime() {
const version = process.version.slice(1); // 'v16.5.0' --> '16.5.0'
const major = version.split('.')[0]; // '16.5.0' --> '16'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
output: "server",
adapter: vercel({
maxDuration: 60
})
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@test/vercel-max-duration",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/vercel": "workspace:*",
"astro": "workspace:*"
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>One</title>
</head>
<body>
<h1>One</h1>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>Two</title>
</head>
<body>
<h1>Two</h1>
</body>
</html>
19 changes: 19 additions & 0 deletions packages/integrations/vercel/test/max-duration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { loadFixture } from './test-utils.js';
import { expect } from 'chai';

describe('maxDuration', () => {
/** @type {import('./test-utils.js').Fixture} */
let fixture;

before(async () => {
fixture = await loadFixture({
root: './fixtures/max-duration/',
});
await fixture.build();
});

it('makes it to vercel function configuration', async () => {
const vcConfig = JSON.parse(await fixture.readFile('../.vercel/output/functions/render.func/.vc-config.json'));
expect(vcConfig).to.deep.include({ maxDuration: 60 });
});
});
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading