Skip to content

Commit

Permalink
Merge pull request #108 from magne4000/headers
Browse files Browse the repository at this point in the history
feat: vike headers config support
  • Loading branch information
magne4000 authored Aug 20, 2024
2 parents e601081 + 5fe1f01 commit f558914
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 26 deletions.
3 changes: 3 additions & 0 deletions examples/demo/pages/vike-edge/+config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ import type { Config } from "vike/types";
export default {
prerender: false,
edge: true,
headers: {
"X-VitePluginVercel-Test": "test",
},
} satisfies Config;
14 changes: 14 additions & 0 deletions examples/demo/tests/05-vike/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ prepareTestJsonFileContent("config.json", (context) => {

it("should have defaults routes only", () => {
const expected = [
{
src: "^/vike-edge$",
headers: {
"X-VitePluginVercel-Test": "test",
},
continue: true,
},
{
src: "^/vike-edge/index\\.pageContext\\.json$",
headers: {
"X-VitePluginVercel-Test": "test",
},
continue: true,
},
{
src: "^/api/page$",
headers: { "X-VitePluginVercel-Test": "test" },
Expand Down
32 changes: 24 additions & 8 deletions packages/vercel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,27 +97,29 @@ export default async function handler() {

You can use [Edge middleware as describe in the official documentation](https://vercel.com/docs/functions/edge-middleware/middleware-api) (i.e. with a `middleware.ts` file at the root of your project).

## Usage with vike
## Usage with Vike

[vike](https://vike.dev/) is supported through [@vite-plugin-vercel/vike](/packages/vike-integration/README.md) plugin.
[Vike](https://vike.dev/) is supported through [@vite-plugin-vercel/vike](/packages/vike-integration/README.md) plugin.

You only need to install `@vite-plugin-vercel/vike`, the Vite config stays the same as above.

> [!IMPORTANT]
> `@vite-plugin-vercel/vike` supersedes the old `@magne4000/vite-plugin-vercel-ssr` package.
> As such, you should remove `@magne4000/vite-plugin-vercel-ssr` from your package.json and vite config file.
You can then leverage [config files](https://vike.dev/config) to customize ISR configuration:
You can then leverage [config files](https://vike.dev/config) to customize your endpoints:

```ts
// /pages/product/+config.ts

import Page from './Page';
import type { Config } from 'vike/types';

// Customize ISR config for this page
export default {
// Customize ISR config for this page
isr: { expiration: 15 },
// Target Edge instead of Serverless
edge: true,
// append headers to all responses
headers: {
'X-Header': 'value'
}
} satisfies Config;
```

Expand Down Expand Up @@ -172,6 +174,20 @@ export default defineConfig({
* See https://vercel.com/docs/projects/project-configuration#rewrites
*/
rewrites: [{ source: '/about', destination: '/about-our-company.html' }],
/**
* @see {@link https://vercel.com/docs/projects/project-configuration#headers}
*/
headers: [
{
"source": "/service-worker.js",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, must-revalidate"
}
]
}
],
/**
* See https://vercel.com/docs/projects/project-configuration#redirects
*/
Expand Down
31 changes: 23 additions & 8 deletions packages/vercel/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,24 @@ async function extractExports(filepath: string) {
}
}

async function extractHeaders(resolvedConfig: ResolvedConfig) {
let headers: Header[] = [];
if (typeof resolvedConfig.vercel?.headers === "function") {
headers = await resolvedConfig.vercel.headers();
} else if (Array.isArray(resolvedConfig.vercel?.headers)) {
headers = resolvedConfig.vercel.headers;
}

return headers;
}

export async function buildEndpoints(resolvedConfig: ResolvedConfig): Promise<{
rewrites: Rewrite[];
isr: Record<string, VercelOutputIsr>;
headers: Header[];
}> {
const entries = await getEntries(resolvedConfig);
const headers = await extractHeaders(resolvedConfig);

for (const entry of entries) {
if (typeof entry.source === "string") {
Expand Down Expand Up @@ -407,14 +419,17 @@ export async function buildEndpoints(resolvedConfig: ResolvedConfig): Promise<{
};
}),
isr: Object.fromEntries(isrEntries) as Record<string, VercelOutputIsr>,
headers: entries
.filter((e) => e.headers)
.map((e) => ({
source: `/${e.destination.replace(/\.func$/, "")}`,
headers: Object.entries(e.headers ?? {}).map(([key, value]) => ({
key,
value,
headers: [
...entries
.filter((e) => e.headers)
.map((e) => ({
source: `/${e.destination.replace(/\.func$/, "")}`,
headers: Object.entries(e.headers ?? {}).map(([key, value]) => ({
key,
value,
})),
})),
})),
...headers,
],
};
}
9 changes: 7 additions & 2 deletions packages/vercel/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Redirect, Rewrite } from "@vercel/routing-utils";
import type { Header, Redirect, Rewrite } from "@vercel/routing-utils";
import type { BuildOptions, StdinOptions } from "esbuild";
import type { ResolvedConfig } from "vite";
import type { VercelOutputConfig } from "./schemas/config/config";
Expand Down Expand Up @@ -37,6 +37,11 @@ export interface ViteVercelConfig {
* @see {@link https://vercel.com/docs/projects/project-configuration#rewrites}
*/
rewrites?: ViteVercelRewrite[];
/**
* @see {@link https://vercel.com/docs/projects/project-configuration#headers}
* @beta
*/
headers?: Header[] | (() => Awaitable<Header[]>);
/**
* @see {@link https://vercel.com/docs/projects/project-configuration#redirects}
*/
Expand Down Expand Up @@ -161,7 +166,7 @@ export interface ViteVercelApiEntry {
/**
* Additional headers
*/
headers?: Record<string, string>;
headers?: Record<string, string> | null;
/**
* ISR config
*/
Expand Down
7 changes: 3 additions & 4 deletions packages/vike-integration/+config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ export default {
edge: {
env: { server: true },
},
// TODO
// headers: {
// env: { server: true },
// },
headers: {
env: { server: true },
},
},
} satisfies Config;
3 changes: 1 addition & 2 deletions packages/vike-integration/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ declare global {
export interface Config {
isr?: boolean | { expiration: number };
edge?: boolean;
// TODO
// headers?: Record<string, string>;
headers?: Record<string, string>;
}
}
}
Expand Down
61 changes: 59 additions & 2 deletions packages/vike-integration/vike.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ async function copyDir(src: string, dest: string) {
}
}

function assertHeaders(exports: unknown): Record<string, string> | null {
if (exports === null || typeof exports !== "object") return null;
if (!("headers" in exports)) return null;
const headers = (exports as { headers: unknown }).headers;

if (headers === null || headers === undefined) {
return null;
}

assert(typeof headers === "object", " `{ headers }` must be an object");

for (const value of Object.values(headers)) {
assert(typeof value === "string", " `{ headers }` must only contains string values");
}

return headers as Record<string, string>;
}

function assertEdge(exports: unknown): boolean | null {
if (exports === null || typeof exports !== "object") return null;
if (!("edge" in exports)) return null;
Expand Down Expand Up @@ -379,7 +397,15 @@ export function vitePluginVercelVikeConfigPlugin(): Plugin {
name: "vite-plugin-vercel:vike-config",
apply: "build",
async config(userConfig): Promise<UserConfig> {
let memoizedP: ReturnType<typeof _getPagesWithConfigs> | undefined = undefined;

async function getPagesWithConfigs() {
if (memoizedP) return memoizedP;
memoizedP = _getPagesWithConfigs();
return memoizedP;
}

async function _getPagesWithConfigs() {
const { pageFilesAll, allPageIds, pageRoutes, pageConfigs } = await getPagesAndRoutes();

const isLegacy = pageFilesAll.length > 0;
Expand All @@ -388,7 +414,7 @@ export function vitePluginVercelVikeConfigPlugin(): Plugin {
await Promise.all(pageFilesAll.map((p) => p.loadFile?.()));
}

return await Promise.all(
return Promise.all(
allPageIds.map(async (pageId) => {
let page: {
config: unknown;
Expand Down Expand Up @@ -431,6 +457,7 @@ export function vitePluginVercelVikeConfigPlugin(): Plugin {
const rawIsr = extractIsr(page.config);
let isr = assertIsr(userConfig, page.config);
const edge = assertEdge(page.config);
const headers = assertHeaders(page.config);

// if ISR + Function routing -> warn because ISR is not unsupported in this case
if (typeof route === "function" && isr) {
Expand All @@ -452,6 +479,7 @@ export function vitePluginVercelVikeConfigPlugin(): Plugin {
filePath: page.filePath,
isr,
edge,
headers,
route: typeof route === "string" ? getParametrizedRoute(route) : null,
};
}),
Expand All @@ -462,6 +490,35 @@ export function vitePluginVercelVikeConfigPlugin(): Plugin {

return {
vercel: {
async headers() {
const pagesWithConfigs = await getPagesWithConfigs();

return pagesWithConfigs
.filter((page) => {
if (!page.route) {
console.warn(
`Page ${page._pageId}: headers is not supported when using route function. Remove \`{ headers }\` config or use a route string if possible.`,
);
}
return page.headers !== null && page.headers !== undefined && page.route;
})
.flatMap((page) => {
const headers = Object.entries(page.headers ?? {}).map(([key, value]) => ({
key,
value,
}));
return [
{
source: `${page.route}`,
headers,
},
{
source: `${page.route}/index\\.pageContext\\.json`,
headers,
},
];
});
},
additionalEndpoints: [
async () => {
const pagesWithConfigs = await getPagesWithConfigs();
Expand All @@ -473,7 +530,7 @@ export function vitePluginVercelVikeConfigPlugin(): Plugin {
.map((page) => {
if (!page.route) {
console.warn(
`Page ${page._pageId}: edge is not supported when using route function. Remove \`{ edge }\` export or use a route string if possible.`,
`Page ${page._pageId}: edge is not supported when using route function. Remove \`{ edge }\` config or use a route string if possible.`,
);
}
const destination = `${page._pageId.replace(/\/index$/g, "")}-edge-${nanoid()}`;
Expand Down

0 comments on commit f558914

Please sign in to comment.