@sveltejs/adapter-vercel 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -75
  2. package/index.js +335 -0
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -2,83 +2,11 @@
2
2
 
3
3
  A SvelteKit adapter that creates a Vercel app.
4
4
 
5
- If you're using [adapter-auto](../adapter-auto), you don't need to install this unless you need to specify Vercel-specific options, since it's already included.
5
+ If you're using [adapter-auto](https://kit.svelte.dev/docs/adapter-auto), you don't need to install this unless you need to specify Vercel-specific options, since it's already included.
6
6
 
7
- ## Usage
7
+ ## Docs
8
8
 
9
- Add `"@sveltejs/adapter-vercel": "next"` to the `devDependencies` in your `package.json` and run `npm install`.
10
-
11
- Then in your `svelte.config.js`:
12
-
13
- ```js
14
- import vercel from '@sveltejs/adapter-vercel';
15
-
16
- export default {
17
- kit: {
18
- // default options are shown
19
- adapter: vercel({
20
- // if true, will deploy the app using edge functions
21
- // (https://vercel.com/docs/concepts/functions/edge-functions)
22
- // rather than serverless functions
23
- edge: false,
24
-
25
- // an array of dependencies that esbuild should treat
26
- // as external when bundling functions
27
- external: [],
28
-
29
- // if true, will split your app into multiple functions
30
- // instead of creating a single one for the entire app
31
- split: false
32
- })
33
- }
34
- };
35
- ```
36
-
37
- ## Environment Variables
38
-
39
- Vercel makes a set of [deployment-specific environment variables](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) available. Like other environment variables, these are accessible from `$env/static/private` and `$env/dynamic/private` (sometimes — more on that later), and inaccessible from their public counterparts. To access one of these variables from the client:
40
-
41
- ```js
42
- // +layout.server.js
43
- import { VERCEL_COMMIT_REF } from '$env/static/private';
44
-
45
- /** @type {import('./$types').LayoutServerLoad} */
46
- export function load() {
47
- return {
48
- deploymentGitBranch: VERCEL_COMMIT_REF
49
- };
50
- }
51
- ```
52
-
53
- ```svelte
54
- <!-- +layout.svelte -->
55
- <script>
56
- /** @type {import('./$types').LayoutServerData} */
57
- export let data;
58
- </script>
59
-
60
- <p>This staging environment was deployed from {data.deploymentGitBranch}.</p>
61
- ```
62
-
63
- Since all of these variables are unchanged between build time and run time when building on Vercel, we recommend using `$env/static/private` — which will statically replace the variables, enabling optimisations like dead code elimination — rather than `$env/dynamic/private`. If you're deploying with `edge: true` you _must_ use `$env/static/private`, as `$env/dynamic/private` and `$env/dynamic/public` are not currently populated in edge functions on Vercel.
64
-
65
- ## Notes
66
-
67
- ### Vercel functions
68
-
69
- Vercel functions contained in the `/api` directory at the project's root will _not_ be included in the deployment — these should be implemented as [server endpoints](https://kit.svelte.dev/docs/routing#server) in your SvelteKit app.
70
-
71
- ### Node version
72
-
73
- Projects created before a certain date will default to using Node 14, while SvelteKit requires Node 16 or later. You can change that in your project settings:
74
-
75
- ![Vercel project settings](settings.png)
76
-
77
- ## Troubleshooting
78
-
79
- ### Accessing the file system
80
-
81
- 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.
9
+ [Docs](https://kit.svelte.dev/docs/adapter-cloudflare)
82
10
 
83
11
  ## Changelog
84
12
 
package/index.js ADDED
@@ -0,0 +1,335 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { nodeFileTrace } from '@vercel/nft';
5
+ import esbuild from 'esbuild';
6
+
7
+ /** @type {import('.').default} **/
8
+ const plugin = function ({ external = [], edge, split } = {}) {
9
+ return {
10
+ name: '@sveltejs/adapter-vercel',
11
+
12
+ async adapt(builder) {
13
+ const node_version = get_node_version();
14
+
15
+ const dir = '.vercel/output';
16
+ const tmp = builder.getBuildDirectory('vercel-tmp');
17
+
18
+ builder.rimraf(dir);
19
+ builder.rimraf(tmp);
20
+
21
+ const files = fileURLToPath(new URL('./files', import.meta.url).href);
22
+
23
+ const dirs = {
24
+ static: `${dir}/static${builder.config.kit.paths.base}`,
25
+ functions: `${dir}/functions`
26
+ };
27
+
28
+ const config = static_vercel_config(builder);
29
+
30
+ builder.log.minor('Generating serverless function...');
31
+
32
+ /**
33
+ * @param {string} name
34
+ * @param {string} pattern
35
+ * @param {(options: { relativePath: string }) => string} generate_manifest
36
+ */
37
+ async function generate_serverless_function(name, pattern, generate_manifest) {
38
+ const relativePath = path.posix.relative(tmp, builder.getServerDirectory());
39
+
40
+ builder.copy(`${files}/serverless.js`, `${tmp}/index.js`, {
41
+ replace: {
42
+ SERVER: `${relativePath}/index.js`,
43
+ MANIFEST: './manifest.js'
44
+ }
45
+ });
46
+
47
+ write(
48
+ `${tmp}/manifest.js`,
49
+ `export const manifest = ${generate_manifest({ relativePath })};\n`
50
+ );
51
+
52
+ await create_function_bundle(
53
+ builder,
54
+ `${tmp}/index.js`,
55
+ `${dirs.functions}/${name}.func`,
56
+ `nodejs${node_version.major}.x`
57
+ );
58
+
59
+ config.routes.push({ src: pattern, dest: `/${name}` });
60
+ }
61
+
62
+ /**
63
+ * @param {string} name
64
+ * @param {string} pattern
65
+ * @param {(options: { relativePath: string }) => string} generate_manifest
66
+ */
67
+ async function generate_edge_function(name, pattern, generate_manifest) {
68
+ const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`);
69
+ const relativePath = path.posix.relative(tmp, builder.getServerDirectory());
70
+
71
+ builder.copy(`${files}/edge.js`, `${tmp}/edge.js`, {
72
+ replace: {
73
+ SERVER: `${relativePath}/index.js`,
74
+ MANIFEST: './manifest.js'
75
+ }
76
+ });
77
+
78
+ write(
79
+ `${tmp}/manifest.js`,
80
+ `export const manifest = ${generate_manifest({ relativePath })};\n`
81
+ );
82
+
83
+ await esbuild.build({
84
+ entryPoints: [`${tmp}/edge.js`],
85
+ outfile: `${dirs.functions}/${name}.func/index.js`,
86
+ target: 'es2020', // TODO verify what the edge runtime supports
87
+ bundle: true,
88
+ platform: 'browser',
89
+ format: 'esm',
90
+ external,
91
+ sourcemap: 'linked',
92
+ banner: { js: 'globalThis.global = globalThis;' }
93
+ });
94
+
95
+ write(
96
+ `${dirs.functions}/${name}.func/.vc-config.json`,
97
+ JSON.stringify({
98
+ runtime: 'edge',
99
+ entrypoint: 'index.js'
100
+ // TODO expose envVarsInUse
101
+ })
102
+ );
103
+
104
+ config.routes.push({ src: pattern, dest: `/${name}` });
105
+ }
106
+
107
+ const generate_function = edge ? generate_edge_function : generate_serverless_function;
108
+
109
+ if (split) {
110
+ await builder.createEntries((route) => {
111
+ return {
112
+ id: route.pattern.toString(), // TODO is `id` necessary?
113
+ filter: (other) => route.pattern.toString() === other.pattern.toString(),
114
+ complete: async (entry) => {
115
+ let sliced_pattern = route.pattern
116
+ .toString()
117
+ // remove leading / and trailing $/
118
+ .slice(1, -2)
119
+ // replace escaped \/ with /
120
+ .replace(/\\\//g, '/');
121
+
122
+ // replace the root route "^/" with "^/?"
123
+ if (sliced_pattern === '^/') {
124
+ sliced_pattern = '^/?';
125
+ }
126
+
127
+ const src = `${sliced_pattern}(?:/__data.json)?$`; // TODO adding /__data.json is a temporary workaround — those endpoints should be treated as distinct routes
128
+
129
+ await generate_function(route.id.slice(1) || 'index', src, entry.generateManifest);
130
+ }
131
+ };
132
+ });
133
+ } else {
134
+ await generate_function('render', '/.*', builder.generateManifest);
135
+ }
136
+
137
+ builder.log.minor('Copying assets...');
138
+
139
+ builder.writeClient(dirs.static);
140
+ builder.writePrerendered(dirs.static);
141
+
142
+ builder.log.minor('Writing routes...');
143
+
144
+ write(`${dir}/config.json`, JSON.stringify(config, null, ' '));
145
+ }
146
+ };
147
+ };
148
+
149
+ /**
150
+ * @param {string} file
151
+ * @param {string} data
152
+ */
153
+ function write(file, data) {
154
+ try {
155
+ fs.mkdirSync(path.dirname(file), { recursive: true });
156
+ } catch {
157
+ // do nothing
158
+ }
159
+
160
+ fs.writeFileSync(file, data);
161
+ }
162
+
163
+ function get_node_version() {
164
+ const full = process.version.slice(1); // 'v16.5.0' --> '16.5.0'
165
+ const major = parseInt(full.split('.')[0]); // '16.5.0' --> 16
166
+
167
+ if (major < 16) {
168
+ throw new Error(
169
+ `SvelteKit only supports Node.js version 16 or greater (currently using v${full}). Consult the documentation: https://vercel.com/docs/runtimes#official-runtimes/node-js/node-js-version`
170
+ );
171
+ }
172
+
173
+ return { major, full };
174
+ }
175
+
176
+ // This function is duplicated in adapter-static
177
+ /** @param {import('@sveltejs/kit').Builder} builder */
178
+ function static_vercel_config(builder) {
179
+ /** @type {any[]} */
180
+ const prerendered_redirects = [];
181
+
182
+ /** @type {Record<string, { path: string }>} */
183
+ const overrides = {};
184
+
185
+ for (const [src, redirect] of builder.prerendered.redirects) {
186
+ prerendered_redirects.push({
187
+ src,
188
+ headers: {
189
+ Location: redirect.location
190
+ },
191
+ status: redirect.status
192
+ });
193
+ }
194
+
195
+ for (const [path, page] of builder.prerendered.pages) {
196
+ if (path.endsWith('/') && path !== '/') {
197
+ prerendered_redirects.push(
198
+ { src: path, dest: path.slice(0, -1) },
199
+ { src: path.slice(0, -1), status: 308, headers: { Location: path } }
200
+ );
201
+
202
+ overrides[page.file] = { path: path.slice(1, -1) };
203
+ } else {
204
+ overrides[page.file] = { path: path.slice(1) };
205
+ }
206
+ }
207
+
208
+ return {
209
+ version: 3,
210
+ routes: [
211
+ ...prerendered_redirects,
212
+ {
213
+ src: `/${builder.getAppPath()}/immutable/.+`,
214
+ headers: {
215
+ 'cache-control': 'public, immutable, max-age=31536000'
216
+ }
217
+ },
218
+ {
219
+ handle: 'filesystem'
220
+ }
221
+ ],
222
+ overrides
223
+ };
224
+ }
225
+
226
+ /**
227
+ * @param {import('@sveltejs/kit').Builder} builder
228
+ * @param {string} entry
229
+ * @param {string} dir
230
+ * @param {string} runtime
231
+ */
232
+ async function create_function_bundle(builder, entry, dir, runtime) {
233
+ fs.rmSync(dir, { force: true, recursive: true });
234
+
235
+ let base = entry;
236
+ while (base !== (base = path.dirname(base)));
237
+
238
+ const traced = await nodeFileTrace([entry], { base });
239
+
240
+ /** @type {Map<string, string[]>} */
241
+ const resolution_failures = new Map();
242
+
243
+ traced.warnings.forEach((error) => {
244
+ // pending https://github.com/vercel/nft/issues/284
245
+ if (error.message.startsWith('Failed to resolve dependency node:')) return;
246
+
247
+ // parse errors are likely not js and can safely be ignored,
248
+ // such as this html file in "main" meant for nw instead of node:
249
+ // https://github.com/vercel/nft/issues/311
250
+ if (error.message.startsWith('Failed to parse')) return;
251
+
252
+ if (error.message.startsWith('Failed to resolve dependency')) {
253
+ const match = /Cannot find module '(.+?)' loaded from (.+)/;
254
+ const [, module, importer] = match.exec(error.message) ?? [, error.message, '(unknown)'];
255
+
256
+ if (!resolution_failures.has(importer)) {
257
+ resolution_failures.set(importer, []);
258
+ }
259
+
260
+ resolution_failures.get(importer).push(module);
261
+ } else {
262
+ throw error;
263
+ }
264
+ });
265
+
266
+ if (resolution_failures.size > 0) {
267
+ const cwd = process.cwd();
268
+ builder.log.warn(
269
+ 'The following modules failed to locate dependencies that may (or may not) be required for your app to work:'
270
+ );
271
+
272
+ for (const [importer, modules] of resolution_failures) {
273
+ console.error(` ${path.relative(cwd, importer)}`);
274
+ for (const module of modules) {
275
+ console.error(` - \u001B[1m\u001B[36m${module}\u001B[39m\u001B[22m`);
276
+ }
277
+ }
278
+ }
279
+
280
+ // find common ancestor directory
281
+ let common_parts;
282
+
283
+ for (const file of traced.fileList) {
284
+ if (common_parts) {
285
+ const parts = file.split(path.sep);
286
+
287
+ for (let i = 0; i < common_parts.length; i += 1) {
288
+ if (parts[i] !== common_parts[i]) {
289
+ common_parts = common_parts.slice(0, i);
290
+ break;
291
+ }
292
+ }
293
+ } else {
294
+ common_parts = path.dirname(file).split(path.sep);
295
+ }
296
+ }
297
+
298
+ const ancestor = base + common_parts.join(path.sep);
299
+
300
+ for (const file of traced.fileList) {
301
+ const source = base + file;
302
+ const dest = path.join(dir, path.relative(ancestor, source));
303
+
304
+ const stats = fs.statSync(source);
305
+ const is_dir = stats.isDirectory();
306
+
307
+ const realpath = fs.realpathSync(source);
308
+
309
+ try {
310
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
311
+ } catch {
312
+ // do nothing
313
+ }
314
+
315
+ if (source !== realpath) {
316
+ const realdest = path.join(dir, path.relative(ancestor, realpath));
317
+ fs.symlinkSync(path.relative(path.dirname(dest), realdest), dest, is_dir ? 'dir' : 'file');
318
+ } else if (!is_dir) {
319
+ fs.copyFileSync(source, dest);
320
+ }
321
+ }
322
+
323
+ write(
324
+ `${dir}/.vc-config.json`,
325
+ JSON.stringify({
326
+ runtime,
327
+ handler: path.relative(base + ancestor, entry),
328
+ launcherType: 'Nodejs'
329
+ })
330
+ );
331
+
332
+ write(`${dir}/package.json`, JSON.stringify({ type: 'module' }));
333
+ }
334
+
335
+ export default plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/adapter-vercel",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -19,6 +19,7 @@
19
19
  "types": "index.d.ts",
20
20
  "files": [
21
21
  "files",
22
+ "index.js",
22
23
  "index.d.ts"
23
24
  ],
24
25
  "dependencies": {
@@ -28,7 +29,7 @@
28
29
  "devDependencies": {
29
30
  "@types/node": "^16.18.6",
30
31
  "typescript": "^4.9.4",
31
- "@sveltejs/kit": "^1.1.1"
32
+ "@sveltejs/kit": "^1.1.3"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "@sveltejs/kit": "^1.0.0"