@sveltejs/adapter-vercel 1.0.3 → 1.0.4
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.
- package/index.js +335 -0
- package/package.json +2 -1
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
|
+
"version": "1.0.4",
|
|
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": {
|