Skip to content

Commit

Permalink
Merge pull request #107 from magne4000/vike-edge-support
Browse files Browse the repository at this point in the history
feat: Vike `edge` config support
  • Loading branch information
magne4000 authored Aug 20, 2024
2 parents 005b292 + a930ad4 commit af84afb
Show file tree
Hide file tree
Showing 25 changed files with 381 additions and 85 deletions.
17 changes: 17 additions & 0 deletions examples/demo/pages/vike-edge/+Page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
import { Counter } from "./Counter.js";

export default function Page() {
return (
<>
<h1>Welcome</h1>
This page is:
<ul>
<li>Rendered to HTML.</li>
<li>
Interactive. <Counter />
</li>
</ul>
</>
);
}
6 changes: 6 additions & 0 deletions examples/demo/pages/vike-edge/+config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Config } from "vike/types";

export default {
prerender: false,
edge: true,
} satisfies Config;
12 changes: 12 additions & 0 deletions examples/demo/pages/vike-edge/Counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React, { useState } from "react";

export { Counter };

function Counter() {
const [count, setCount] = useState(0);
return (
<button type="button" onClick={() => setCount((count) => count + 1)}>
Counter {count}
</button>
);
}
1 change: 1 addition & 0 deletions examples/demo/renderer/PageShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function PageShell({
<Link href="/catch-all/a/b/c">Catch-all</Link>
<Link href="/function/a">Function</Link>
<Link href="/edge">Edge Function endpoint</Link>
<Link href="/vike-edge">Vike Edge Function endpoint</Link>
<Link href="/og-edge">Edge OG endpoint</Link>
<Link href="/og-node">Node OG endpoint</Link>
</Sidebar>
Expand Down
3 changes: 1 addition & 2 deletions examples/demo/tests/01-minimal/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import path from "node:path";
import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
import { expect, it } from "vitest";
import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
import { prepareTestJsonFileContent, testSchema } from "../common/helpers";

prepareTestJsonFileContent(path.basename(__dirname), "config.json", (context) => {
testSchema(context, vercelOutputConfigSchema);

it("should have defaults routes only", () => {
console.log(context.file);
expect(context.file).toHaveProperty("routes", [
{
src: "^/api/page$",
Expand Down
7 changes: 6 additions & 1 deletion examples/demo/tests/05-vike/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { beforeAll, describe, expect, it } from "vitest";
import { expect, it } from "vitest";
import { vercelOutputConfigSchema } from "../../../../packages/vercel/src/schemas/config/config";
import { testSchema } from "../common/helpers";
import { prepareTestJsonFileContent } from "./utils";
Expand Down Expand Up @@ -39,6 +39,11 @@ prepareTestJsonFileContent("config.json", (context) => {
dest: "/og-edge/$1",
check: true,
},
{
src: "^(/vike-edge(?:/index\\.pageContext\\.json)?)$",
dest: expect.stringMatching("/pages/vike-edge-edge-([^/]+?)/\\?__original_path=\\$1"),
check: true,
},
{
check: true,
src: "^/api/page$",
Expand Down
3 changes: 3 additions & 0 deletions examples/demo/tests/05-vike/fs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ describe("fs", () => {
/\/functions\/pages\/named-([^\/]+?)\.prerender-config\.json/,
/\/functions\/pages\/named-([^\/]+?)\.func\/index\.mjs/,
/\/functions\/pages\/named-([^\/]+?)\.func\/\.vc-config\.json/,
// vike-edge
/\/functions\/pages\/vike-edge-edge-([^\/]+?)\.func\/index\.js/,
/\/functions\/pages\/vike-edge-edge-([^\/]+?)\.func\/\.vc-config\.json/,
...generatedFiles.map((f) => `/static/${f}`),
];

Expand Down
2 changes: 1 addition & 1 deletion examples/demo/tests/05-vike/vite.config._test_.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default {
source: "endpoints/edge.ts",
destination: "edge",
edge: true,
addRoute: true,
route: true,
},
],
expiration: 25,
Expand Down
4 changes: 2 additions & 2 deletions examples/demo/tests/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os from "node:os";
import path from "node:path";
import { build, type InlineConfig } from "vite";
import { type InlineConfig, build } from "vite";

export function getTmpDir(displayName: string) {
return path.join(os.tmpdir(), `vpv-demo-${displayName}`);
Expand Down Expand Up @@ -36,7 +36,7 @@ export async function callBuild(dirname: string, config: InlineConfig) {
{
source: "endpoints/edge.ts",
destination: "edge",
addRoute: true,
route: true,
},
...(config.vercel?.additionalEndpoints ?? []),
],
Expand Down
8 changes: 4 additions & 4 deletions examples/demo/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import react from "@vitejs/plugin-react-swc";
import ssr from "vike/plugin";
import vercel from "vite-plugin-vercel";
import type { UserConfig } from "vite";
import vercel from "vite-plugin-vercel";

export default {
plugins: [
Expand All @@ -17,17 +17,17 @@ export default {
{
source: "endpoints/edge.ts",
destination: "edge",
addRoute: true,
route: true,
},
{
source: "endpoints/og-node.tsx",
destination: "og-node",
addRoute: true,
route: true,
},
{
source: "endpoints/og-edge.tsx",
destination: "og-edge",
addRoute: true,
route: true,
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion examples/express/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default {
// replaces default Vike target
destination: "ssr_",
// already added by default Vike route
addRoute: false,
route: false,
},
],
},
Expand Down
4 changes: 2 additions & 2 deletions packages/vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"module": "./dist/index.js",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./index.d.ts"
"require": "./dist/index.cjs"
},
"./types": {
"types": "./index.d.ts"
Expand Down
87 changes: 66 additions & 21 deletions packages/vercel/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,32 @@ import { vercelEndpointExports } from "./schemas/exports";
import type { VercelOutputIsr, ViteVercelApiEntry } from "./types";
import { getOutput, getRoot, pathRelativeTo } from "./utils";

export function getAdditionalEndpoints(resolvedConfig: ResolvedConfig) {
return (resolvedConfig.vercel?.additionalEndpoints ?? []).map((e) => ({
export async function getAdditionalEndpoints(resolvedConfig: ResolvedConfig) {
const userEndpoints: ViteVercelApiEntry[] = [];
if (Array.isArray(resolvedConfig.vercel?.additionalEndpoints)) {
for (const endpoint of resolvedConfig.vercel.additionalEndpoints) {
if (typeof endpoint === "function") {
const res = await endpoint();
if (Array.isArray(res)) {
userEndpoints.push(...res);
} else {
userEndpoints.push(res);
}
} else {
userEndpoints.push(endpoint);
}
}
}

return userEndpoints.map((e) => ({
...e,
addRoute: e.addRoute ?? true,
route: e.route ?? true,
// path.resolve removes the trailing slash if any
destination: `${path.posix.resolve("/", e.destination)}.func`,
}));
}

export function getEntries(resolvedConfig: ResolvedConfig): ViteVercelApiEntry[] {
export async function getEntries(resolvedConfig: ResolvedConfig): Promise<ViteVercelApiEntry[]> {
const apiEntries = glob
.sync(`${getRoot(resolvedConfig)}/api/**/*.*([a-zA-Z0-9])`)
// from Vercel doc: Files with the underscore prefix are not turned into Serverless Functions.
Expand All @@ -43,18 +59,21 @@ export function getEntries(resolvedConfig: ResolvedConfig): ViteVercelApiEntry[]
// from Vercel doc: Files with the underscore prefix are not turned into Serverless Functions.
.filter((filepath) => !path.basename(filepath).startsWith("_"));

return [...apiEntries, ...otherApiEntries].reduce((entryPoints, filePath) => {
const outFilePath = pathRelativeTo(filePath, resolvedConfig, filePath.includes("/_api/") ? "_api" : "api");
const parsed = path.posix.parse(outFilePath);
return [...apiEntries, ...otherApiEntries].reduce(
(entryPoints, filePath) => {
const outFilePath = pathRelativeTo(filePath, resolvedConfig, filePath.includes("/_api/") ? "_api" : "api");
const parsed = path.posix.parse(outFilePath);

entryPoints.push({
source: filePath,
destination: `api/${path.posix.join(parsed.dir, parsed.name)}.func`,
addRoute: true,
});
entryPoints.push({
source: filePath,
destination: `api/${path.posix.join(parsed.dir, parsed.name)}.func`,
route: true,
});

return entryPoints;
}, getAdditionalEndpoints(resolvedConfig));
return entryPoints;
},
await getAdditionalEndpoints(resolvedConfig),
);
}

const edgeWasmPlugin: Plugin = {
Expand Down Expand Up @@ -306,7 +325,7 @@ export async function buildEndpoints(resolvedConfig: ResolvedConfig): Promise<{
isr: Record<string, VercelOutputIsr>;
headers: Header[];
}> {
const entries = getEntries(resolvedConfig);
const entries = await getEntries(resolvedConfig);

for (const entry of entries) {
if (typeof entry.source === "string") {
Expand Down Expand Up @@ -336,6 +355,13 @@ export async function buildEndpoints(resolvedConfig: ResolvedConfig): Promise<{
);
}

if (
(entry.isr !== undefined || exports.isr !== undefined) &&
(entry.edge !== undefined || exports.edge !== undefined)
) {
throw new Error(`isr cannot be enabled for edge functions ('${entry.source}')`);
}

if (exports.isr) {
entry.isr = exports.isr;
}
Expand All @@ -355,12 +381,31 @@ export async function buildEndpoints(resolvedConfig: ResolvedConfig): Promise<{

return {
rewrites: entries
.filter((e) => e.addRoute !== false)
.map((e) => e.destination.replace(/\.func$/, ""))
.map((destination) => ({
source: replaceBrackets(getSourceAndDestination(destination)),
destination: getSourceAndDestination(destination),
})),
.filter((e) => {
if (e.addRoute === undefined && e.route !== undefined) {
return e.route !== false;
}
if (e.addRoute !== undefined && e.route === undefined) {
return e.addRoute !== false;
}
if (e.addRoute !== undefined && e.route !== undefined) {
throw new Error("Cannot use both `route` and `addRoute` in `additionalEndpoints`");
}
return true;
})
.map((e) => {
const destination = e.destination.replace(/\.func$/, "");
if (typeof e.route === "string") {
return {
source: `(${e.route})`,
destination: `${destination}/?__original_path=$1`,
};
}
return {
source: replaceBrackets(getSourceAndDestination(destination)),
destination: getSourceAndDestination(destination),
};
}),
isr: Object.fromEntries(isrEntries) as Record<string, VercelOutputIsr>,
headers: entries
.filter((e) => e.headers)
Expand Down
33 changes: 20 additions & 13 deletions packages/vercel/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import type { ResolvedConfig } from "vite";
import type { Redirect, Rewrite } from "@vercel/routing-utils";
import type { BuildOptions, StdinOptions } from "esbuild";
import type { ResolvedConfig } from "vite";
import type { VercelOutputConfig } from "./schemas/config/config";
import type { VercelOutputVcConfig } from "./schemas/config/vc-config";
import type { VercelOutputPrerenderConfig } from "./schemas/config/prerender-config";
import type { Rewrite, Redirect } from "@vercel/routing-utils";
import type { VercelOutputVcConfig } from "./schemas/config/vc-config";

export type { VercelOutputConfig, VercelOutputVcConfig, VercelOutputPrerenderConfig };

export type ViteVercelRewrite = Rewrite & { enforce?: "pre" | "post" };
export type ViteVercelRedirect = Redirect & { enforce?: "pre" | "post" };

export type Awaitable<T> = T | Promise<T>;

// Vite config for Vercel

export interface ViteVercelConfig {
Expand Down Expand Up @@ -72,7 +74,11 @@ export interface ViteVercelConfig {
* }
* ```
*/
additionalEndpoints?: ViteVercelApiEntry[];
additionalEndpoints?: (
| ViteVercelApiEntry
| (() => Awaitable<ViteVercelApiEntry>)
| (() => Awaitable<ViteVercelApiEntry[]>)
)[];
/**
* Advanced configuration to override .vercel/output/config.json
* @see {@link https://vercel.com/docs/build-output-api/v3/configuration#configuration}
Expand All @@ -99,9 +105,7 @@ export interface ViteVercelConfig {
*
* @protected
*/
isr?:
| Record<string, VercelOutputIsr>
| (() => Promise<Record<string, VercelOutputIsr>> | Record<string, VercelOutputIsr>);
isr?: Record<string, VercelOutputIsr> | (() => Awaitable<Record<string, VercelOutputIsr>>);
/**
* Defaults to `.vercel/output`. Mostly useful for testing purpose
* @protected
Expand All @@ -111,7 +115,7 @@ export interface ViteVercelConfig {
* By default, Vite generates static files under `dist` folder.
* But usually, when used through a Framework, such as Vike,
* this folder can contain anything, requiring custom integration.
* Set this to false is you create a plugin for a Framework.
* Set this to false if you create a plugin for a Framework.
*/
distContainsOnlyStatic?: boolean;
}
Expand All @@ -125,9 +129,7 @@ export interface VercelOutputIsr extends VercelOutputPrerenderConfig {
* Keys are path relative to .vercel/output/static directory
*/
export type ViteVercelPrerenderRoute = VercelOutputConfig["overrides"];
export type ViteVercelPrerenderFn = (
resolvedConfig: ResolvedConfig,
) => ViteVercelPrerenderRoute | Promise<ViteVercelPrerenderRoute>;
export type ViteVercelPrerenderFn = (resolvedConfig: ResolvedConfig) => Awaitable<ViteVercelPrerenderRoute>;

export interface ViteVercelApiEntry {
/**
Expand All @@ -143,10 +145,15 @@ export interface ViteVercelApiEntry {
*/
buildOptions?: BuildOptions;
/**
* Automatically add a route for the function (mimics defaults Vercel behavior)
* Set to `false` to disable
* @deprecated use `route` instead
*/
addRoute?: boolean;
/**
* If `true`, guesses route for the function, and adds it to config.json (mimics defaults Vercel behavior).
* If a string is provided, it will be equivalent to a `rewrites` rule.
* Set to `false` to disable
*/
route?: string | boolean;
/**
* Set to `true` to mark this function as an Edge Function
*/
Expand Down
6 changes: 3 additions & 3 deletions packages/vike-integration/+config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export default {
isr: {
env: { server: true },
},
edge: {
env: { server: true },
},
// TODO
// edge: {
// env: { server: true },
// },
// headers: {
// env: { server: true },
// },
Expand Down
Loading

0 comments on commit af84afb

Please sign in to comment.