Skip to content

Commit b99b88c

Browse files
authored
fix: more robust remote files generation (#14682)
We tried very hard to get manualChunks to work (see #14632 for the latest attempt) but it just doesn't quite work out. What we need is to actually have Vite/Rollup know about the proper entry points to then resolve everything from there. The problem previously was that we didn't know of a good way to know which entry points we have on the fly. Turns out, `this.emitFile` is our savior (https://rollupjs.org/plugin-development/#this-emitfile). We can use it to create entry points while traversing the module graph and have Vite/Rollup wire up everything correctly. Fixes #14679 Fixes #14708 Fixes #14736
1 parent c63e158 commit b99b88c

File tree

2 files changed

+32
-107
lines changed

2 files changed

+32
-107
lines changed

.changeset/quick-pens-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: more robust remote files generation

packages/kit/src/exports/vite/index.js

Lines changed: 27 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import {
4141
import { import_peer } from '../../utils/import.js';
4242
import { compact } from '../../utils/array.js';
4343
import { should_ignore } from './static_analysis/utils.js';
44-
import { rollupVersion } from 'vite';
4544

4645
const cwd = process.cwd();
4746

@@ -636,102 +635,30 @@ async function kit({ svelte_config }) {
636635
/** @type {Array<{ hash: string, file: string }>} */
637636
const remotes = [];
638637

639-
/**
640-
* A set of modules that imported by `.remote.ts` modules. By forcing these modules
641-
* into their own chunks, we ensure that each chunk created for a `.remote.ts`
642-
* module _only_ contains that module, hopefully avoiding any circular
643-
* dependency woes that arise from treating chunks as entries
644-
*/
645-
const imported_by_remotes = new Set();
646-
let uid = 1;
638+
/** @type {Map<string, string>} Maps remote hash -> original module id */
639+
const remote_original_by_hash = new Map();
640+
641+
/** @type {Set<string>} Track which remote hashes have already been emitted */
642+
const emitted_remote_hashes = new Set();
647643

648644
/** @type {import('vite').Plugin} */
649645
const plugin_remote = {
650646
name: 'vite-plugin-sveltekit-remote',
651647

652-
moduleParsed(info) {
653-
if (svelte_config.kit.moduleExtensions.some((ext) => info.id.endsWith(`.remote${ext}`))) {
654-
for (const id of info.importedIds) {
655-
imported_by_remotes.add(id);
656-
}
657-
}
648+
resolveId(id) {
649+
if (id.startsWith('\0sveltekit-remote:')) return id;
658650
},
659651

660-
config(config) {
661-
if (!config.build?.ssr) {
662-
// only set manualChunks for the SSR build
663-
return;
664-
}
665-
666-
// Ensure build.rollupOptions.output exists
667-
config.build ??= {};
668-
config.build.rollupOptions ??= {};
669-
config.build.rollupOptions.output ??= {};
670-
671-
if (Array.isArray(config.build.rollupOptions.output)) {
672-
// TODO I have no idea how this could occur
673-
throw new Error('rollupOptions.output cannot be an array');
674-
}
675-
676-
// Set up manualChunks to isolate *.remote.ts files
677-
const { manualChunks } = config.build.rollupOptions.output;
678-
679-
const [major, minor] = rollupVersion.split('.').map(Number);
680-
const is_outdated_rollup = major === 4 && minor < 52;
681-
if (is_outdated_rollup) {
682-
console.warn(
683-
'Rollup >=4.52.0 is recommended when using SvelteKit remote functions as it fixes some bugs related to code-splitting. Current version: ' +
684-
rollupVersion
685-
);
686-
}
687-
688-
config.build.rollupOptions.output = {
689-
...config.build.rollupOptions.output,
690-
manualChunks(id, meta) {
691-
// Check if this is a *.remote.ts file
692-
if (svelte_config.kit.moduleExtensions.some((ext) => id.endsWith(`.remote${ext}`))) {
693-
const relative = posixify(path.relative(cwd, id));
694-
695-
return `remote-${hash(relative)}`;
696-
}
697-
698-
// With onlyExplicitManualChunks Rollup will keep any manual chunk's dependencies out of that chunk.
699-
// This option only exists on more recent Rollup versions; use this as a fallback for older versions.
700-
if (is_outdated_rollup) {
701-
// Prevent core runtime and env from ending up in a remote chunk, which could break because of initialization order
702-
if (id === `${runtime_directory}/app/server/index.js`) {
703-
return 'app-server';
704-
}
705-
if (id === `${runtime_directory}/shared-server.js`) {
706-
return 'app-shared-server';
707-
}
708-
if (imported_by_remotes.has(id)) {
709-
return `chunk-${uid++}`;
710-
}
711-
}
712-
713-
// If there was an existing manualChunks function, call it
714-
if (typeof manualChunks === 'function') {
715-
return manualChunks(id, meta);
716-
}
717-
718-
// If manualChunks is an object, check if this module matches any patterns
719-
if (manualChunks) {
720-
for (const name in manualChunks) {
721-
const patterns = manualChunks[name];
722-
723-
// TODO is `id.includes(pattern)` correct?
724-
if (patterns.some((pattern) => id.includes(pattern))) {
725-
return name;
726-
}
727-
}
728-
}
729-
}
730-
};
731-
732-
if (!is_outdated_rollup) {
733-
// @ts-expect-error only exists in more recent Rollup versions https://rollupjs.org/configuration-options/#output-onlyexplicitmanualchunks
734-
config.build.rollupOptions.output.onlyExplicitManualChunks = true;
652+
load(id) {
653+
// On-the-fly generated entry point for remote file just forwards the original module
654+
// We're not using manualChunks because it can cause problems with circular dependencies
655+
// (e.g. https://github.com/sveltejs/kit/issues/14679) and module ordering in general
656+
// (e.g. https://github.com/sveltejs/kit/issues/14590).
657+
if (id.startsWith('\0sveltekit-remote:')) {
658+
const hash_id = id.slice('\0sveltekit-remote:'.length);
659+
const original = remote_original_by_hash.get(hash_id);
660+
if (!original) throw new Error(`Expected to find metadata for remote file ${id}`);
661+
return `import * as m from ${s(original)};\nexport default m;`;
735662
}
736663
},
737664

@@ -746,7 +673,6 @@ async function kit({ svelte_config }) {
746673
}
747674

748675
const file = posixify(path.relative(cwd, id));
749-
750676
const remote = {
751677
hash: hash(file),
752678
file
@@ -770,10 +696,17 @@ async function kit({ svelte_config }) {
770696
}
771697
`;
772698

699+
// Emit a dedicated entry chunk for this remote in SSR builds (prod only)
773700
if (!dev_server) {
774-
// in prod, prevent the functions from being treeshaken. This will
775-
// be replaced with an `export default` in the `writeBundle` hook
776-
code += `$$_export_$$($$_self_$$);`;
701+
remote_original_by_hash.set(remote.hash, id);
702+
if (!emitted_remote_hashes.has(remote.hash)) {
703+
this.emitFile({
704+
type: 'chunk',
705+
id: `\0sveltekit-remote:${remote.hash}`,
706+
name: `remote-${remote.hash}`
707+
});
708+
emitted_remote_hashes.add(remote.hash);
709+
}
777710
}
778711

779712
return code;
@@ -826,19 +759,6 @@ async function kit({ svelte_config }) {
826759
return {
827760
code: result
828761
};
829-
},
830-
831-
writeBundle() {
832-
for (const remote of remotes) {
833-
const file = `${out}/server/chunks/remote-${remote.hash}.js`;
834-
const code = fs.readFileSync(file, 'utf-8');
835-
836-
fs.writeFileSync(
837-
file,
838-
// build process might have minified/adjusted the $$_self_$$ variable, but not the fake global $$_export_$$ function
839-
code.replace(/\$\$_export_\$\$\((.+?)\)/, (_, name) => `export default ${name};`)
840-
);
841-
}
842762
}
843763
};
844764

0 commit comments

Comments
 (0)