Skip to content

Commit

Permalink
feat: implement FetchEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
EGOIST committed May 5, 2022
1 parent 34cbb19 commit a91f477
Show file tree
Hide file tree
Showing 16 changed files with 326 additions and 81 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export default (req: MiddlewareRequest) => {

This plugin uses Vercel's [Build Output API (v3)](https://vercel.com/docs/build-output-api/v3) which requires an Environment Variable named `ENABLE_VC_BUILD` to be set to `1` in order to enable the feature.

## Credits

A lot of code are taken from Next.js since it's basically the same logic, all credits to Next.js authors.

## Sponsors

[![sponsors](https://sponsors-images.egoist.sh/sponsors.svg)](https://github.com/sponsors/egoist)
Expand Down
24 changes: 22 additions & 2 deletions example/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { MiddlewareRequest, MiddlewareResponse } from "vite-vercel/server"
import {
MiddlewareRequest,
MiddlewareResponse,
MiddlewareFetchEvent,
} from "vite-vercel/server"

export default (req: MiddlewareRequest) => {
export default (req: MiddlewareRequest, event: MiddlewareFetchEvent) => {
const url = new URL(req.url)

if (url.pathname === "/from-middleware") {
Expand All @@ -13,5 +17,21 @@ export default (req: MiddlewareRequest) => {
)
}

if (url.pathname === "/stream") {
const { readable, writable } = new TransformStream()

event.waitUntil(
(async () => {
const writer = writable.getWriter()
const encoder = new TextEncoder()
writer.write(encoder.encode("Hello, world! Streamed!"))
writer.write(encoder.encode("response"))
writer.close()
})(),
)

return new Response(readable)
}

return MiddlewareResponse.next()
}
12 changes: 11 additions & 1 deletion packages/vercel-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,20 @@
"./server-node": {
"import": "./dist/server-node.mjs",
"default": "./dist/server-node.js"
},
"./polyfills": {
"import": "./dist/polyfills.mjs",
"default": "./dist/polyfills.js"
}
},
"devDependencies": {
"next": "^12.1.6",
"tsup": "5.12.6"
},
"license": "MIT"
"license": "MIT",
"dependencies": {
"@web-std/file": "^3.0.2",
"node-fetch": "^3.2.4",
"web-streams-polyfill": "^3.2.1"
}
}
1 change: 1 addition & 0 deletions packages/vercel-utils/polyfills.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./dist/polyfills"
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import fetch, { Request, Response, Headers, FormData } from "node-fetch"
import "web-streams-polyfill/es2018"
import fetch, { FormData } from "node-fetch"
import { Blob as NodeBlob, File as NodeFile } from "@web-std/file"
import { Response } from "next/dist/server/web/spec-compliant/response"
import { Request } from "next/dist/server/web/spec-compliant/request"
import { Headers } from "next/dist/server/web/spec-compliant/headers"

globalThis.Request = globalThis.Request || Request
globalThis.Response = globalThis.Response || Response
Expand Down
22 changes: 21 additions & 1 deletion packages/vercel-utils/src/server-node.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { IncomingMessage } from "http"
import { MiddlewareRequest } from "./server"
import { Readable } from "stream"
import { MiddlewareFetchEvent, MiddlewareRequest } from "./server"

/**
* Create Web Headers from Node Headers
Expand Down Expand Up @@ -42,3 +43,22 @@ export function createRequest(req: IncomingMessage): Request {

return new MiddlewareRequest(url.href, init)
}

export function createFetchEvent(request: Request): MiddlewareFetchEvent {
return new MiddlewareFetchEvent(request)
}

export function bodyStreamToNodeStream(bodyStream: ReadableStream): Readable {
const reader = bodyStream.getReader()
return Readable.from(
(async function* () {
while (true) {
const { done, value } = await reader.read()
if (done) {
return
}
yield value
}
})(),
)
}
53 changes: 3 additions & 50 deletions packages/vercel-utils/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,4 @@
import { validateURL } from "./server-utils"

const REDIRECTS = new Set([301, 302, 303, 307, 308])

export class MiddlewareRequest extends Request {
/** Only available on Vercel */
geo?: {
city?: string
country?: string
region?: string
latitude?: string
longitude?: string
}

/** Only available on Vercel */
ip?: string
}

export class MiddlewareResponse extends Response {
static rewrite(destination: string | URL) {
return new MiddlewareResponse(null, {
headers: {
"x-middleware-rewrite": validateURL(destination),
},
})
}

static next() {
return new MiddlewareResponse(null, {
headers: {
"x-middleware-next": "1",
},
})
}

static redirect(url: string | URL, status = 307) {
if (!REDIRECTS.has(status)) {
throw new RangeError(
'Failed to execute "redirect" on "response": Invalid status code',
)
}

const destination = validateURL(url)
return new MiddlewareResponse(destination, {
headers: { Location: destination },
status,
})
}
}

export { MiddlewareRequest } from "./spec-extensions/request"
export { MiddlewareResponse } from "./spec-extensions/response"
export { MiddlewareFetchEvent } from "./spec-extensions/fetch-event"
export { isBot } from "./server-utils"
3 changes: 3 additions & 0 deletions packages/vercel-utils/src/spec-extensions/fetch-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { FetchEvent } from "next/dist/server/web/spec-compliant/fetch-event"

export class MiddlewareFetchEvent extends FetchEvent {}
15 changes: 15 additions & 0 deletions packages/vercel-utils/src/spec-extensions/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Request } from "next/dist/server/web/spec-compliant/request"

export class MiddlewareRequest extends Request {
/** Only available on Vercel */
geo?: {
city?: string
country?: string
region?: string
latitude?: string
longitude?: string
}

/** Only available on Vercel */
ip?: string
}
3 changes: 3 additions & 0 deletions packages/vercel-utils/src/spec-extensions/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { NextResponse } from "next/dist/server/web/spec-extension/response"

export class MiddlewareResponse extends NextResponse {}
5 changes: 2 additions & 3 deletions packages/vite-vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,14 @@
"@types/fs-extra": "^9.0.13",
"@types/node": "^17.0.31",
"prettier": "2.6.2",
"tsup": "5.12.6",
"tsup": "5.12.7",
"typescript": "4.6.4",
"vite": "^2.9.7",
"vitest": "0.10.1"
},
"dependencies": {
"@web-std/file": "^3.0.2",
"fs-extra": "^10.1.0",
"node-fetch": "^3.2.4",
"resolve-from": "^5.0.0",
"vercel-utils": "workspace:*"
},
"peerDependencies": {
Expand Down
31 changes: 25 additions & 6 deletions packages/vite-vercel/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import path from "path"
import { type Plugin, build } from "vite"
import fs from "fs-extra"
import resolveFrom from "resolve-from"
import { fileURLToPath } from "url"

export type Options = {
middleware?: string
}

declare const TSUP_FORMAT: string

const resolve = (id: string) => {
return resolveFrom(
TSUP_FORMAT === "esm"
? path.dirname(fileURLToPath(import.meta.url))
: __dirname,
id,
)
}

const writeJson = (filepath: string, data: any) => {
fs.mkdirSync(path.dirname(filepath), { recursive: true })
fs.writeFileSync(filepath, JSON.stringify(data))
Expand All @@ -16,7 +29,6 @@ export const plugin = (options: Options = {}): Plugin => {
return {
name: "vercel",

// @ts-expect-error
config() {
return {
ssr: {
Expand All @@ -28,6 +40,11 @@ export const plugin = (options: Options = {}): Plugin => {
// No sure why sometimes this is externalized
noExternal: [/vite-vercel/],
},
resolve: {
alias: {
"vercel-utils": path.dirname(resolve("vercel-utils/polyfills")),
},
},
}
},

Expand All @@ -44,9 +61,9 @@ export const plugin = (options: Options = {}): Plugin => {
server.middlewares.use(async (req, res, next) => {
if (serverNode) return next()

await server.ssrLoadModule(`/@id/vite-vercel/server-prepare`)
await server.ssrLoadModule("vercel-utils/polyfills")
serverNode = (await server.ssrLoadModule(
`/@id/vite-vercel/server-node`,
"vercel-utils/server-node",
)) as any
next()
})
Expand All @@ -57,7 +74,8 @@ export const plugin = (options: Options = {}): Plugin => {
try {
const middleware = await server.ssrLoadModule(`/@fs${middlewarePath}`)
const request = serverNode.createRequest(req)
let response: Response = await middleware.default(request)
const event = serverNode.createFetchEvent(request)
let response: Response = await middleware.default(request, event)

if (response.headers.get("x-middleware-next") === "1") {
return next()
Expand All @@ -74,8 +92,8 @@ export const plugin = (options: Options = {}): Plugin => {
}
})

const ab = await response.arrayBuffer()
res.end(Buffer.from(ab))
const stream = serverNode.bodyStreamToNodeStream(response.body!)
stream.pipe(res)
} catch (error) {
if (error instanceof Error) {
server.ssrFixStacktrace(error)
Expand Down Expand Up @@ -109,6 +127,7 @@ export const plugin = (options: Options = {}): Plugin => {
preserveEntrySignatures: "strict",
},
outDir: `.vercel/output/functions/main.func`,
target: "esnext",
},
})

Expand Down
1 change: 1 addition & 0 deletions packages/vite-vercel/src/polyfills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "vercel-utils/polyfills"
6 changes: 5 additions & 1 deletion packages/vite-vercel/src/server.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export * from "vercel-utils/server"
export {
MiddlewareFetchEvent,
MiddlewareRequest,
MiddlewareResponse,
} from "vercel-utils/server"
18 changes: 11 additions & 7 deletions packages/vite-vercel/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { defineConfig } from "tsup"

export default defineConfig({
target: "node14",
entry: ["./src/index.ts", "./src/server.ts", "./src/server-prepare.ts"],
format: ["esm", "cjs"],
dts: true,
splitting: true,
shims: false,
export default defineConfig(() => {
return {
target: "node14",
entry: ["./src/index.ts", "./src/server.ts", "./src/polyfills.ts"],
format: ["cjs", "esm"],
dts: true,
splitting: true,
shims: false,
minify: true,
sourcemap: true,
}
})
Loading

0 comments on commit a91f477

Please sign in to comment.