@sveltejs/kit
Version:
SvelteKit is the fastest way to build Svelte apps
130 lines (105 loc) • 4.12 kB
JavaScript
/** @import { ManifestData, ServerMetadata } from 'types' */
import fs from 'node:fs';
import path from 'node:path';
import { posixify } from '../../../utils/filesystem.js';
import { dedent } from '../../../core/sync/utils.js';
import { import_peer } from '../../../utils/import.js';
/**
* Moves the remote files to a sibling file and rewrites the original remote file to import from that sibling file,
* enhancing the remote functions with their hashed ID.
* This is not done through a self-import like during DEV because we want to treeshake prerendered remote functions
* later, which wouldn't work if we do a self-import and iterate over all exports (since we're reading them then).
* @param {string} out
* @param {ManifestData} manifest_data
*/
export function build_remotes(out, manifest_data) {
const dir = `${out}/server/remote`;
for (const remote of manifest_data.remotes) {
const entry = `${dir}/${remote.hash}.js`;
const tmp = `${remote.hash}.tmp.js`;
fs.renameSync(entry, `${dir}/${tmp}`);
fs.writeFileSync(
entry,
dedent`
import * as $$_self_$$ from './${tmp}';
for (const [name, fn] of Object.entries($$_self_$$)) {
fn.__.id = '${remote.hash}/' + name;
fn.__.name = name;
}
export * from './${tmp}';
`
);
}
}
/**
* For each remote module, checks if there are treeshakeable prerendered remote functions,
* then accomplishes the treeshaking by rewriting the remote files to only include the non-prerendered imports,
* replacing the prerendered remote functions with a dummy function that should never be called,
* and doing a Vite build. This will not treeshake perfectly yet as everything except the remote files are treated as external,
* so it will not go into those files to check what can be treeshaken inside them.
* @param {string} out
* @param {ManifestData} manifest_data
* @param {ServerMetadata} metadata
*/
export async function treeshake_prerendered_remotes(out, manifest_data, metadata) {
if (manifest_data.remotes.length === 0) {
return;
}
const dir = posixify(`${out}/server/remote`);
const vite = /** @type {typeof import('vite')} */ (await import_peer('vite'));
const remote_entry = posixify(`${out}/server/remote-entry.js`);
const prefix = 'optimized/';
const input = {
// include this file in the bundle, so that Rollup understands
// that functions like `prerender` are side-effect free
[path.basename(remote_entry.slice(0, -3))]: remote_entry
};
for (const remote of manifest_data.remotes) {
const exports = metadata.remotes.get(remote.hash);
if (!exports) throw new Error('An impossible situation occurred');
/** @type {string[]} */
const dynamic = [];
/** @type {string[]} */
const prerendered = [];
for (const [name, value] of exports) {
(value.dynamic ? dynamic : prerendered).push(name);
}
const remote_file = posixify(`${dir}/${remote.hash}.js`);
fs.writeFileSync(
remote_file,
dedent`
import { ${dynamic.join(', ')} } from './${remote.hash}.tmp.js';
import { prerender } from '../${path.basename(remote_entry)}';
${prerendered.map((name) => `export const ${name} = prerender('unchecked', () => { throw new Error('Unexpectedly called prerender function. Did you forget to set { dynamic: true } ?') });`).join('\n')}
for (const [name, fn] of Object.entries({ ${Array.from(exports.keys()).join(', ')} })) {
fn.__.id = '${remote.hash}/' + name;
fn.__.name = name;
}
export { ${dynamic.join(', ')} };
`
);
input[prefix + remote.hash] = remote_file;
}
const bundle = await vite.build({
configFile: false,
build: {
ssr: true,
rollupOptions: {
external: (id) => {
if (id[0] === '.') return;
return !id.startsWith(dir);
},
input
}
}
});
// @ts-expect-error TypeScript doesn't know what type `bundle` is
for (const chunk of bundle.output) {
if (chunk.name.startsWith(prefix)) {
fs.writeFileSync(`${dir}/${chunk.fileName.slice(prefix.length)}`, chunk.code);
}
}
for (const remote of manifest_data.remotes) {
fs.unlinkSync(`${dir}/${remote.hash}.tmp.js`);
}
}