|
import { text } from '../../../exports/index.js'; |
|
import { compact } from '../../../utils/array.js'; |
|
import { get_status, normalize_error } from '../../../utils/error.js'; |
|
import { add_data_suffix } from '../../../utils/url.js'; |
|
import { Redirect } from '../../control.js'; |
|
import { redirect_response, static_error_page, handle_error_and_jsonify } from '../utils.js'; |
|
import { |
|
handle_action_json_request, |
|
handle_action_request, |
|
is_action_json_request, |
|
is_action_request |
|
} from './actions.js'; |
|
import { load_data, load_server_data } from './load_data.js'; |
|
import { render_response } from './render.js'; |
|
import { respond_with_error } from './respond_with_error.js'; |
|
import { get_option } from '../../../utils/options.js'; |
|
import { get_data_json } from '../data/index.js'; |
|
import { load_page_nodes } from './load_page_nodes.js'; |
|
|
|
|
|
|
|
|
|
const MAX_DEPTH = 10; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function render_page(event, page, options, manifest, state, resolve_opts) { |
|
if (state.depth > MAX_DEPTH) { |
|
|
|
return text(`Not found: ${event.url.pathname}`, { |
|
status: 404 |
|
}); |
|
} |
|
|
|
if (is_action_json_request(event)) { |
|
const node = await manifest._.nodes[page.leaf](); |
|
return handle_action_json_request(event, options, node?.server); |
|
} |
|
|
|
try { |
|
const nodes = await load_page_nodes(page, manifest); |
|
|
|
const leaf_node = (nodes.at(-1)); |
|
|
|
let status = 200; |
|
|
|
|
|
let action_result = undefined; |
|
|
|
if (is_action_request(event)) { |
|
|
|
|
|
action_result = await handle_action_request(event, leaf_node.server); |
|
if (action_result?.type === 'redirect') { |
|
return redirect_response(action_result.status, action_result.location); |
|
} |
|
if (action_result?.type === 'error') { |
|
status = get_status(action_result.error); |
|
} |
|
if (action_result?.type === 'failure') { |
|
status = action_result.status; |
|
} |
|
} |
|
|
|
const should_prerender_data = nodes.some((node) => node?.server?.load); |
|
const data_pathname = add_data_suffix(event.url.pathname); |
|
|
|
|
|
|
|
|
|
const should_prerender = get_option(nodes, 'prerender') ?? false; |
|
if (should_prerender) { |
|
const mod = leaf_node.server; |
|
if (mod?.actions) { |
|
throw new Error('Cannot prerender pages with actions'); |
|
} |
|
} else if (state.prerendering) { |
|
|
|
return new Response(undefined, { |
|
status: 204 |
|
}); |
|
} |
|
|
|
|
|
|
|
state.prerender_default = should_prerender; |
|
|
|
|
|
const fetched = []; |
|
|
|
|
|
|
|
|
|
if (get_option(nodes, 'ssr') === false && !(state.prerendering && should_prerender_data)) { |
|
return await render_response({ |
|
branch: [], |
|
fetched, |
|
page_config: { |
|
ssr: false, |
|
csr: get_option(nodes, 'csr') ?? true |
|
}, |
|
status, |
|
error: null, |
|
event, |
|
options, |
|
manifest, |
|
state, |
|
resolve_opts |
|
}); |
|
} |
|
|
|
|
|
const branch = []; |
|
|
|
|
|
let load_error = null; |
|
|
|
|
|
const server_promises = nodes.map((node, i) => { |
|
if (load_error) { |
|
|
|
throw load_error; |
|
} |
|
|
|
return Promise.resolve().then(async () => { |
|
try { |
|
if (node === leaf_node && action_result?.type === 'error') { |
|
|
|
|
|
throw action_result.error; |
|
} |
|
|
|
return await load_server_data({ |
|
event, |
|
state, |
|
node, |
|
parent: async () => { |
|
|
|
const data = {}; |
|
for (let j = 0; j < i; j += 1) { |
|
const parent = await server_promises[j]; |
|
if (parent) Object.assign(data, parent.data); |
|
} |
|
return data; |
|
} |
|
}); |
|
} catch (e) { |
|
load_error = (e); |
|
throw load_error; |
|
} |
|
}); |
|
}); |
|
|
|
const csr = get_option(nodes, 'csr') ?? true; |
|
|
|
|
|
const load_promises = nodes.map((node, i) => { |
|
if (load_error) throw load_error; |
|
return Promise.resolve().then(async () => { |
|
try { |
|
return await load_data({ |
|
event, |
|
fetched, |
|
node, |
|
parent: async () => { |
|
const data = {}; |
|
for (let j = 0; j < i; j += 1) { |
|
Object.assign(data, await load_promises[j]); |
|
} |
|
return data; |
|
}, |
|
resolve_opts, |
|
server_data_promise: server_promises[i], |
|
state, |
|
csr |
|
}); |
|
} catch (e) { |
|
load_error = (e); |
|
throw load_error; |
|
} |
|
}); |
|
}); |
|
|
|
|
|
for (const p of server_promises) p.catch(() => {}); |
|
for (const p of load_promises) p.catch(() => {}); |
|
|
|
for (let i = 0; i < nodes.length; i += 1) { |
|
const node = nodes[i]; |
|
|
|
if (node) { |
|
try { |
|
const server_data = await server_promises[i]; |
|
const data = await load_promises[i]; |
|
|
|
branch.push({ node, server_data, data }); |
|
} catch (e) { |
|
const err = normalize_error(e); |
|
|
|
if (err instanceof Redirect) { |
|
if (state.prerendering && should_prerender_data) { |
|
const body = JSON.stringify({ |
|
type: 'redirect', |
|
location: err.location |
|
}); |
|
|
|
state.prerendering.dependencies.set(data_pathname, { |
|
response: text(body), |
|
body |
|
}); |
|
} |
|
|
|
return redirect_response(err.status, err.location); |
|
} |
|
|
|
const status = get_status(err); |
|
const error = await handle_error_and_jsonify(event, options, err); |
|
|
|
while (i--) { |
|
if (page.errors[i]) { |
|
const index = (page.errors[i]); |
|
const node = await manifest._.nodes[index](); |
|
|
|
let j = i; |
|
while (!branch[j]) j -= 1; |
|
|
|
return await render_response({ |
|
event, |
|
options, |
|
manifest, |
|
state, |
|
resolve_opts, |
|
page_config: { ssr: true, csr: true }, |
|
status, |
|
error, |
|
branch: compact(branch.slice(0, j + 1)).concat({ |
|
node, |
|
data: null, |
|
server_data: null |
|
}), |
|
fetched |
|
}); |
|
} |
|
} |
|
|
|
|
|
|
|
return static_error_page(options, status, error.message); |
|
} |
|
} else { |
|
|
|
|
|
branch.push(null); |
|
} |
|
} |
|
|
|
if (state.prerendering && should_prerender_data) { |
|
|
|
let { data, chunks } = get_data_json( |
|
event, |
|
options, |
|
branch.map((node) => node?.server_data) |
|
); |
|
|
|
if (chunks) { |
|
for await (const chunk of chunks) { |
|
data += chunk; |
|
} |
|
} |
|
|
|
state.prerendering.dependencies.set(data_pathname, { |
|
response: text(data), |
|
body: data |
|
}); |
|
} |
|
|
|
const ssr = get_option(nodes, 'ssr') ?? true; |
|
|
|
return await render_response({ |
|
event, |
|
options, |
|
manifest, |
|
state, |
|
resolve_opts, |
|
page_config: { |
|
csr: get_option(nodes, 'csr') ?? true, |
|
ssr |
|
}, |
|
status, |
|
error: null, |
|
branch: ssr === false ? [] : compact(branch), |
|
action_result, |
|
fetched |
|
}); |
|
} catch (e) { |
|
|
|
|
|
return await respond_with_error({ |
|
event, |
|
options, |
|
manifest, |
|
state, |
|
status: 500, |
|
error: e, |
|
resolve_opts |
|
}); |
|
} |
|
} |
|
|