Skip to content

Commit fe1a11e

Browse files
teemingcbenmccanngithub-actions[bot]renovate[bot]Rich-Harris
authored
feat: include server assets for Vercel serverless functions (#10979)
* add ability to copy server assets per route * changeset * fix test * fix doc * fix asset copying * change process dir when using vite preview * remove process.chdir from vite preview * include assets used by default error pages * fix doc link * better doc example * fix doc type * ok for real * add tests for vercel adapter * prettier * fix tests * fix types * more type fixes * try this * documentation * skip adapter-vercel tests if on node v20 * oopsie wrong PR * move server asset metadata resolving to function * update changesets * revert builder test fixes * fix lockfile * prettier * lint * fix vercel tsc errors * prepublish only * fix: avoid running load functions when prerendering if no server load function exists and SSR is off (#11405) * chore: upgrade eslint-plugin-unicorn (#11432) * Version Packages (#11420) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * chore(deps): update dependency worktop to v0.8.0-next.16 (#11437) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * i don't think we need this any more (#11439) Co-authored-by: Rich Harris <[email protected]> * fix: only disallow dynamic env access when prerendering (#11436) * only disallow dynamic env access when prerendering * changeset * add test * sigh --------- Co-authored-by: Rich Harris <[email protected]> * docs: fix links to sveltesociety.dev (#11441) * Version Packages (#11442) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: set ESLint config type to `Config` instead of `FlatConfig` (#11453) * Version Packages (#11457) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: improve warning when encountering import.meta.env (#11440) * empty commit (#11469) Co-authored-by: Rich Harris <[email protected]> * fix form actions docs (#11470) Co-authored-by: Rich Harris <[email protected]> * Version Packages (#11468) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * chore(deps): update pnpm to v8.13.1 (#11471) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * docs: add performance page (#11424) * docs: add performance page * address minor feedback issues * lazy loading and waterfalls * prefetching * Apply suggestions from code review * Apply suggestions from code review * various * tweak * MDN page says nothing about lazy-loading videos * fix link * Update documentation/docs/40-best-practices/05-performance.md Co-authored-by: Ben McCann <[email protected]> * move images page * Update documentation/docs/40-best-practices/05-performance.md Co-authored-by: Ben McCann <[email protected]> * update font section (no point mentioning font-display without a recommended value) * various tweaks --------- Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Rich Harris <[email protected]> * chore(deps): update pnpm to v8.14.0 (#11504) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * feat: use latest Azure adapter in adapter-auto (#11496) * fix: update @vercel/nft to 0.26.1 (#11508) * Version Packages (#11507) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * exclude universal nodes * include assets imported by server hooks * update adapter-vercel tests * fix tests * fix and add test for server hooks assets * update adapter-vercel test app packages * im gonna lose my mind * ignore hashes for filenames in tests * oops * fix tests? * revert adapter-vercel types fix * improve readability of route asset for loop * simplify file reading in serverless functions example --------- Co-authored-by: Ben McCann <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Geoff Rich <[email protected]>
1 parent 22ed677 commit fe1a11e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1278
-617
lines changed

.changeset/loud-needles-refuse.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/adapter-vercel': minor
3+
---
4+
5+
feat: bundle server assets with serverless functions

.changeset/selfish-bobcats-run.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: adapter method to get server assets used by each route

documentation/docs/25-build-and-deploy/80-adapter-netlify.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,29 @@ Additionally, you can add your own Netlify functions by creating a directory for
107107
directory = "functions"
108108
```
109109

110-
## Troubleshooting
111-
112110
### Accessing the file system
113111

114-
You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content.
112+
You can [use files in Netlify Serverless Functions](https://www.netlify.com/blog/2021/08/12/how-to-include-files-in-netlify-serverless-functions/).
113+
114+
```js
115+
// @errors: 2307 7031
116+
/// file: +server.js
117+
import fs from "node:fs";
118+
import path from "node:path";
119+
import { dev } from '$app/environment';
120+
121+
// importing a static asset will return the resolved path in the production build
122+
import PalatinoBoldFont from "$lib/fonts/PalatinoBold.ttf";
123+
124+
const cwd = process.cwd();
125+
126+
// server assets live in `.netlify/server` when deployed to Netlify
127+
const dir = dev ? cwd : path.join(cwd, '.netlify/server');
128+
129+
const pathToFile = path.join(dir, PalatinoBoldFont);
130+
131+
export async function GET() {
132+
const file = fs.readFileSync(pathToFile);
133+
// ...
134+
}
135+
```

documentation/docs/25-build-and-deploy/90-adapter-vercel.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,25 @@ If you have Vercel functions contained in the `api` directory at the project's r
153153

154154
Projects created before a certain date may default to using an older Node version than what SvelteKit currently requires. You can [change the Node version in your project settings](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version).
155155

156-
## Troubleshooting
157-
158156
### Accessing the file system
159157

160-
You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content.
158+
You can [use files in Serverless Functions on Vercel](https://vercel.com/guides/how-can-i-use-files-in-serverless-functions).
159+
160+
```js
161+
// @errors: 2307 7031
162+
/// file: api/pdf/+server.js
163+
import fs from "node:fs";
164+
import path from "node:path";
165+
166+
// importing a static asset will return the resolved path in the production build
167+
import PalatinoBoldFont from "$lib/fonts/PalatinoBold.ttf";
168+
169+
const pathToFile = path.join(process.cwd(), PalatinoBoldFont);
170+
171+
export async function GET() {
172+
const file = fs.readFileSync(pathToFile);
173+
// ...
174+
}
175+
```
176+
177+
> Only assets that are imported in `+page.server`, `+layout.server` and `+server` files are included in the Serverless Function bundle.

packages/adapter-vercel/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.DS_Store
22
node_modules
3+
.vercel

packages/adapter-vercel/index.js

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ const plugin = function (defaults = {}) {
5959

6060
/**
6161
* @param {string} name
62-
* @param {import('.').ServerlessConfig} config
63-
* @param {import('@sveltejs/kit').RouteDefinition<import('.').Config>[]} routes
62+
* @param {import('./index.js').ServerlessConfig} config
63+
* @param {import('@sveltejs/kit').RouteDefinition<import('./index.js').Config>[]} routes
6464
*/
6565
async function generate_serverless_function(name, config, routes) {
6666
const relativePath = path.posix.relative(tmp, builder.getServerDirectory());
@@ -81,14 +81,15 @@ const plugin = function (defaults = {}) {
8181
builder,
8282
`${tmp}/index.js`,
8383
`${dirs.functions}/${name}.func`,
84-
config
84+
config,
85+
routes
8586
);
8687
}
8788

8889
/**
8990
* @param {string} name
90-
* @param {import('.').EdgeConfig} config
91-
* @param {import('@sveltejs/kit').RouteDefinition<import('.').EdgeConfig>[]} routes
91+
* @param {import('./index.js').EdgeConfig} config
92+
* @param {import('@sveltejs/kit').RouteDefinition<import('./index.js').EdgeConfig>[]} routes
9293
*/
9394
async function generate_edge_function(name, config, routes) {
9495
const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`);
@@ -135,7 +136,7 @@ const plugin = function (defaults = {}) {
135136
);
136137
}
137138

138-
/** @type {Map<string, { i: number, config: import('.').Config, routes: import('@sveltejs/kit').RouteDefinition<import('.').Config>[] }>} */
139+
/** @type {Map<string, { i: number, config: import('./index.js').Config, routes: import('@sveltejs/kit').RouteDefinition<import('./index.js').Config>[] }>} */
139140
const groups = new Map();
140141

141142
/** @type {Map<string, { hash: string, route_id: string }>} */
@@ -144,7 +145,7 @@ const plugin = function (defaults = {}) {
144145
/** @type {Map<string, string>} */
145146
const functions = new Map();
146147

147-
/** @type {Map<import('@sveltejs/kit').RouteDefinition<import('.').Config>, { expiration: number | false, bypassToken: string | undefined, allowQuery: string[], group: number, passQuery: true }>} */
148+
/** @type {Map<import('@sveltejs/kit').RouteDefinition<import('./index.js').Config>, { expiration: number | false, bypassToken: string | undefined, allowQuery: string[], group: number, passQuery: true }>} */
148149
const isr_config = new Map();
149150

150151
/** @type {Set<string>} */
@@ -163,7 +164,7 @@ const plugin = function (defaults = {}) {
163164
}
164165

165166
const node_runtime = /nodejs([0-9]+)\.x/.exec(runtime);
166-
if (runtime !== 'edge' && (!node_runtime || node_runtime[1] < 18)) {
167+
if (runtime !== 'edge' && (!node_runtime || +node_runtime[1] < 18)) {
167168
throw new Error(
168169
`Invalid runtime '${runtime}' for route ${route.id}. Valid runtimes are 'edge' and 'nodejs18.x' or higher ` +
169170
'(see the Node.js Version section in your Vercel project settings for info on the currently supported versions).'
@@ -368,7 +369,7 @@ function write(file, data) {
368369
// This function is duplicated in adapter-static
369370
/**
370371
* @param {import('@sveltejs/kit').Builder} builder
371-
* @param {import('.').Config} config
372+
* @param {import('index.js').Config} config
372373
*/
373374
function static_vercel_config(builder, config) {
374375
/** @type {any[]} */
@@ -377,8 +378,11 @@ function static_vercel_config(builder, config) {
377378
/** @type {Record<string, { path: string }>} */
378379
const overrides = {};
379380

380-
/** @type {import('./index').ImagesConfig} */
381-
const images = config.images;
381+
/** @type {import('./index.js').ImagesConfig | undefined} */
382+
let images;
383+
if (config.runtime !== 'edge') {
384+
images = /** @type {import('./index.js').ServerlessConfig} */ (config).images;
385+
}
382386

383387
for (const [src, redirect] of builder.prerendered.redirects) {
384388
prerendered_redirects.push({
@@ -434,9 +438,10 @@ function static_vercel_config(builder, config) {
434438
* @param {import('@sveltejs/kit').Builder} builder
435439
* @param {string} entry
436440
* @param {string} dir
437-
* @param {import('.').ServerlessConfig} config
441+
* @param {import('./index.js').ServerlessConfig} config
442+
* @param {import('@sveltejs/kit').RouteDefinition[]} routes
438443
*/
439-
async function create_function_bundle(builder, entry, dir, config) {
444+
async function create_function_bundle(builder, entry, dir, config, routes) {
440445
fs.rmSync(dir, { force: true, recursive: true });
441446

442447
let base = entry;
@@ -544,6 +549,24 @@ async function create_function_bundle(builder, entry, dir, config) {
544549
)
545550
);
546551

552+
const server_assets = builder.getServerAssets();
553+
let routes_assets = new Set(server_assets.rootErrorPage);
554+
555+
for (const route of routes) {
556+
const assets = server_assets.routes.get(route.id);
557+
if (assets) {
558+
routes_assets = new Set([...routes_assets, ...assets]);
559+
}
560+
}
561+
562+
if (server_assets.hooks) {
563+
routes_assets = new Set([...routes_assets, ...server_assets.hooks]);
564+
}
565+
566+
for (const asset of routes_assets) {
567+
builder.copy(path.join(builder.getServerDirectory(), asset), path.join(dir, asset));
568+
}
569+
547570
write(`${dir}/package.json`, JSON.stringify({ type: 'module' }));
548571
}
549572

packages/adapter-vercel/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@
2828
"lint": "prettier --check .",
2929
"format": "pnpm lint --write",
3030
"check": "tsc",
31-
"test": "vitest run"
31+
"test": "vitest run & pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test"
3232
},
3333
"dependencies": {
3434
"@vercel/nft": "^0.26.1",
3535
"esbuild": "^0.19.9"
3636
},
3737
"devDependencies": {
38+
"@playwright/test": "1.30.0",
3839
"@sveltejs/kit": "workspace:^",
3940
"@sveltejs/vite-plugin-svelte": "^3.0.1",
4041
"@types/node": "^18.19.3",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.DS_Store
2+
node_modules
3+
/build
4+
/.svelte-kit
5+
/package
6+
.env
7+
.env.*
8+
!.env.example
9+
vite.config.js.timestamp-*
10+
vite.config.ts.timestamp-*
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "~TODO~",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"dev": "vite dev",
7+
"build": "vite build",
8+
"preview": "vite preview",
9+
"test": "playwright test"
10+
},
11+
"devDependencies": {
12+
"@sveltejs/kit": "workspace:^",
13+
"svelte": "^4.2.8",
14+
"typescript": "^5.3.3",
15+
"vite": "^5.0.8"
16+
},
17+
"type": "module"
18+
}

0 commit comments

Comments
 (0)