前段时间在折腾 Vercel 的函数。最开始我只是拿他当一个静态站用,现在发现完全可以在上面建动态站。

静态文件服务器

静态文件服务器最简单了。建一个支持的git仓库,把静态文件丢进去,然后在Vercel上面导入,大功告成。

但是里面如果有package.json,不能有build脚本,不然会被认为是需要构建的静态文件服务器。

需要构建的静态文件服务器

如果package.json里面有build脚本,而且没有在vercel.json里面写构建相关部分,那么仓库会被认为是需要构建的。

Vercel 会先yarn install,然后yarn run build,最后把public文件夹里面的东西放在静态文件服务器上面。

为什么是Yarn?因为Vercel默认用Yarn作为包管理器,你也可以在导入项目的时候或者在项目配置里面覆写了。

函数

如果你没有配置过,那么正常情况下/api/底下的每一个文件都会被当作一个serverless函数处理。各家对serverless的接口要求不一样,比如Cloudflare Workers的是koa风格的,而Vercel的是Express风格的。

这里只说js/ts的情况,因为笔者只会这个,不会go和php。回头学了再补充吧。

Vercel需要一个默认导出函数,签名长这样:

1
(request:VercelRequest, response:VercelResponse)=>Promise<void>

这样子写就行。

1
2
3
4
5
import { VercelRequest, VercelResponse } from "@vercel/node";

module.exports = async function(request:VercelRequest, response:VercelResponse) {
//codes here
};

JavaScript的话把类型去掉就行。当然在TypeScript你可以直接用export default,编译的时候会转换的。

ts文件不用编译可以直接放在那里,Vercel会编译的。

一个函数一个站

Vercel对Hobby Plan的账号(也就是免费账号)的单项目函数数量是有要求的,不能多于6个。但是如果要实现很多功能的话,6个是远远不够的。

这时候可以用一个函数解决所有的问题,然后把所有请求都路由到这个函数来。

举个例子,主函数在src/main.tsvercel.json可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"builds": [
{
"src": "src/*",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "src/main.ts"
}
]
}

这样所有的请求都会到这个函数里面去了。

VCLight

站建多了,我人也懒了,搓了个轮子解决建站的事情。(造轮子造的.jpg)

先创建项目

1
npx vclight-cli@latest create $ProjectName

$ProjectName换成你自己的项目名称。

建议选A template project with VCLight and router,带路由的。反正接下来基于这个讲。文档写了一半摸鱼了,搞不懂的看看源码吧。

仓库:VCLight, VCLight-Router

给点Star嘛求求了。

个人习惯带Prettier玩,你们看着办吧。

建完项目,加新路由有两种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
pattern(pattern: RegExp, fn: (data: RequestContext, response: ResponseContext) => void)//正则表达式匹配,如果匹配上了就用

on(event: string, fn: (data: RequestContext, response: ResponseContext) => void)//字符串匹配,这个优先级更高

//例子

router.on(`/page/`, async function(data, response){//建议路径后面带上'/',这样不管用户有没有带'/'都能匹配到。
response.response = "Hello World!";
});

router.pattern(/^(?=\/hello\/)/, async function(data, response) {//匹配'/hello/*'
response.response = "Hello World!";
});

response定义回应,结构这样:

1
2
3
4
5
6
7
8
9
class ResponseContext {
public redirect: boolean | undefined; //是否重定向
public redirectUrl: string | undefined; //重定向url
public status: number = 200; //响应状态码
public contentType: string | undefined; //Content Type
public cache: number | undefined; //缓存多久,如果没设置就不缓存,单位为秒,实现是stale-while-revalidate
public response: any; //响应体,如果是类会被转换为JSON
public cookie: CookieElement[] = []; //添加的Cookie
}

剩下的看IDE签名吧。

部分静态

如果有些文件你就是想要静态的,有两种办法。

不管哪一种,都要先在build里面定义:

1
2
3
4
5
6
7
8
9
10
11
12
{
"builds": [
{
"src": "src/*",
"use": "@vercel/node"
},
{
"src": "static/**/*",
"use": "@vercel/static"
}
]
}

接下来,你可以在routes中选择匹配或者文件系统handle。后者会匹配路径对应的请求。这里面写的越前面优先级越高。

1
2
3
4
5
6
7
8
9
10
"routes": [
{
"src": "/assets/(?<file>[^/]*)",
"dest": "public/assets/$file"
},
{
"src": "/(.*)",
"dest": "src/main.ts"
}
]

1
2
3
4
5
6
7
8
9
"routes": [
{
"handle": "filesystem"
},
{
"src": "/(.*)",
"dest": "src/main.ts"
}
]

构建过程

Vercel会按照你的vercel.json构建。

这里只讲有函数的情况。

函数部分在编译为js之后会被放在构建机器(读写R/W)的/vercel/output/中,之后会被转移到serverless容器(只读RO)中。

你可以试试魔改运行时来整一些花活。比如说,Vercel只会把引用的文件而不是所有文件复制到容器中。如果有些文件你也希望带进容器(比如需要用路径匹配的文件),可以试试魔改来解决。还可以把构建机器的详细信息放在容器某个不为人知的角落。 (经试验不行,可能是我技术太烂了。)

但是还有一种办法。js有一个事件钩子postinstall,会在npm i完成之后运行。你可以在这个阶段完成动态生成,然后尝试把文件引用让它参与构建。亲测可以的。

容器

serverless的宿主机是一个十分高度定制的机子。

这是uname -a的执行输出:

1
Linux 169.254.224.69 4.14.255-311-248.529.amzn2.x86_64 #1 SMP Wed Apr 12 19:03:39 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

这啥?

你的函数被放在了/var/task/底下。

你猜我怎么知道这些的?

我做了个mini bash。

这是源码

更多的?

我目前就发现了这些,更多的可以往评论区贴,我会补充的。