Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-kids-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-vercel': patch
---

fix: get ISR working on Vercel
14 changes: 14 additions & 0 deletions packages/adapter-vercel/files/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,25 @@ await server.init({
env: /** @type {Record<string, string>} */ (process.env)
});

const DATA_SUFFIX = '/__data.json';

/**
* @param {import('http').IncomingMessage} req
* @param {import('http').ServerResponse} res
*/
export default async (req, res) => {
if (req.url) {
const [path, search] = req.url.split('?');

const params = new URLSearchParams(search);
const pathname = params.get('__pathname');

if (pathname) {
params.delete('__pathname');
req.url = `${pathname}${path.endsWith(DATA_SUFFIX) ? DATA_SUFFIX : ''}?${params}`;
}
}

/** @type {Request} */
let request;

Expand Down
118 changes: 87 additions & 31 deletions packages/adapter-vercel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,6 @@ const plugin = function (defaults = {}) {
`${dirs.functions}/${name}.func`,
config
);

if (config.isr) {
write(
`${dirs.functions}/${name}.prerender-config.json`,
JSON.stringify(
{
expiration: config.isr.expiration,
group: config.isr.group,
bypassToken: config.isr.bypassToken,
allowQuery: config.isr.allowQuery
},
null,
'\t'
)
);
}
}

/**
Expand Down Expand Up @@ -158,6 +142,22 @@ const plugin = function (defaults = {}) {
/** @type {Map<string, string>} */
const functions = new Map();

// every route needs a `group`, so that a page and its __data.json are invalidated
// as a pair. automatically assign group ids to routes that don't have one
let group_id = 0;

// ensure automatically assigned group ids are unique
for (const route of builder.routes) {
if (route.prerender === true) continue;

if (route.config?.isr?.group !== undefined) {
group_id = route.config.isr.group;
Comment thread
Rich-Harris marked this conversation as resolved.
Outdated
}
}

/** @type {Map<import('@sveltejs/kit').RouteDefinition<import('.').Config>, import('.').ServerlessConfig['isr']>} */
const isr_config = new Map();

// group routes by config
for (const route of builder.routes) {
if (route.prerender === true) continue;
Expand All @@ -175,6 +175,14 @@ const plugin = function (defaults = {}) {

const config = { runtime, ...defaults, ...route.config };

if (config.isr) {
if (config.isr.group === undefined) {
config.isr.group = ++group_id;
}

isr_config.set(route, config.isr);
}

const hash = hash_config(config);

// first, check there are no routes with incompatible configs that will be merged
Expand Down Expand Up @@ -238,12 +246,61 @@ const plugin = function (defaults = {}) {
src = '^/?';
}

src += '(?:/__data.json)?$';

const name = functions.get(pattern);
if (name) {
static_config.routes.push({ src, dest: `/${name}` });
functions.delete(pattern);
const isr = isr_config.get(route);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't have any route config, we'll never get into here because functions is empty (because of line 216)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch. fixed

if (isr) {
if (isr.allowQuery?.includes('__pathname')) {
throw new Error('__pathname is a reserved query parameter');
Comment thread
Rich-Harris marked this conversation as resolved.
Outdated
}

const base = `${dirs.functions}/${route.id.slice(1)}`;
builder.mkdirp(base);

const target = `${dirs.functions}/${name}.func`;
const relative = path.relative(path.dirname(base), target);

// create a symlink to the actual function, but use the
// route name so that we can derive the correct URL
fs.symlinkSync(relative, `${base}.func`);
fs.symlinkSync(`../${relative}`, `${base}/__data.json.func`);

let i = 1;
const pathname = route.segments
.map((segment) => {
return segment.dynamic ? `$${i++}` : segment.content;
})
.join('/');

const json = JSON.stringify(
{
expiration: isr.expiration,
group: isr.group,
bypassToken: isr.bypassToken,
allowQuery: ['__pathname', ...(isr.allowQuery ?? [])],
passQuery: true
},
null,
'\t'
);

write(`${base}.prerender-config.json`, json);
write(`${base}/__data.json.prerender-config.json`, json);

const q = `?__pathname=/${pathname}`;

static_config.routes.push({
src: src + '$',
dest: `${route.id}${q}`
});

static_config.routes.push({
src: src + '/__data.json$',
dest: `${route.id}/__data.json${q}`
});
} else {
static_config.routes.push({ src: src + '(?:/__data.json)?$', dest: `/${name}` });
}
}
}

Expand Down Expand Up @@ -400,22 +457,21 @@ async function create_function_bundle(builder, entry, dir, config) {
}
}

const files = Array.from(traced.fileList);

// find common ancestor directory
/** @type {string[]} */
let common_parts = [];
let common_parts = files[0]?.split(path.sep) ?? [];

for (const file of traced.fileList) {
if (common_parts) {
const parts = file.split(path.sep);
for (let i = 1; i < files.length; i += 1) {
const file = files[i];
const parts = file.split(path.sep);

for (let i = 0; i < common_parts.length; i += 1) {
if (parts[i] !== common_parts[i]) {
common_parts = common_parts.slice(0, i);
break;
}
for (let i = 0; i < common_parts.length; i += 1) {
if (parts[i] !== common_parts[i]) {
common_parts = common_parts.slice(0, i);
break;
Comment thread
Rich-Harris marked this conversation as resolved.
Outdated
}
} else {
common_parts = path.dirname(file).split(path.sep);
}
}

Expand Down