kit icon indicating copy to clipboard operation
kit copied to clipboard

Cannot use `read` from `$app/server` for assets imported (directly or transitively) by only a route page component

Open jarrodldavis opened this issue 2 years ago • 1 comments

Describe the bug

If I have a route page component like this:

<script>
    import url from './lorem-ipsum.txt?url';

    console.log('url:', url);

    if (import.meta.env.SSR) {
        async function read_and_print() {
            const { read } = await import("$app/server");
            const response = read(url);
            const text = await response.text();
            console.log('text:', text);
        }
        read_and_print();
    }
</script>

<a download="lorem-ipsum.txt" href={url}>Download Text</a>

The read call works, but only when using vite dev. When using vite build + vite preview, the asset is included in the build output, but the read call reports that the asset doesn't exist:

file:///workspaces/sveltekit-read-asset-from-page/.svelte-kit/output/server/chunks/index.js:41
  throw new Error(`Asset does not exist: ${file}`);
        ^

Error: Asset does not exist: _app/immutable/assets/lorem-ipsum.863mxCz_.txt
    at read (file:///workspaces/sveltekit-read-asset-from-page/.svelte-kit/output/server/chunks/index.js:41:9)
    at read_and_print (file:///workspaces/sveltekit-read-asset-from-page/.svelte-kit/output/server/entries/pages/text/_page.svelte.js:8:24)

Node.js v20.11.0
 ELIFECYCLE  Command failed with exit code 1.

Reproduction

I've created a reproduction SvelteKit project that demonstrates the above example:

https://github.com/jarrodldavis/sveltekit-read-asset-from-page/tree/main/src/routes/text

as well as an example closer to how I discovered this issue:

https://github.com/jarrodldavis/sveltekit-read-asset-from-page/tree/main/src/routes/rust/page

Here, I am using a custom Vite plugin to on-the-fly produce a JavaScript module that consumes the output of wasm-pack to import, load, and execute a Rust WebAssembly module. That Vite plugin produces code like this for SSR builds:

import init from "./pkg/page_only.js";
import url from "./pkg/page_only_bg.wasm?url";
console.log('url:', url);
import { read } from "$app/server";
const response = read(url);
response.headers.set('content-type', 'application/wasm');
await init(response);
export * from "./pkg/page_only.js";

So that, for example, I can do something like this in a page component:

<script>
    import { greet } from '$lib/page-only/Cargo.toml';

    greet();
</script>

<button type="button" on:click={greet}>Greet</button>

Logs

No response

System Info

I created the above reproduction project in a default GitHub Codespace.


  System:
    OS: Linux 6.2 Ubuntu 20.04.6 LTS (Focal Fossa)
    CPU: (2) x64 AMD EPYC 7763 64-Core Processor
    Memory: 5.48 GB / 7.74 GB
    Container: Yes
    Shell: 5.0.17 - /bin/bash
  Binaries:
    Node: 20.11.0 - ~/nvm/current/bin/node
    Yarn: 1.22.19 - /usr/bin/yarn
    npm: 10.2.4 - ~/nvm/current/bin/npm
    pnpm: 8.14.1 - ~/nvm/current/bin/pnpm
  npmPackages:
    @sveltejs/adapter-node: ^4.0.1 => 4.0.1 
    @sveltejs/kit: ^2.0.0 => 2.5.0 
    @sveltejs/vite-plugin-svelte: ^3.0.0 => 3.0.2 
    svelte: ^4.2.7 => 4.2.9 
    vite: ^5.0.3 => 5.0.12 

Severity

serious, but I can work around it

Additional Information

After some spelunking, it seems the issue is here:

https://github.com/sveltejs/kit/blob/f7c4f36e833dce4ae0f79a6c1634810edd744fe3/packages/kit/src/core/generate_manifest/find_server_assets.js#L30-L45

Only assets imported by route endpoints and server load functions are added as server assets, which is what read relies on to determine a path is a valid Vite-bundled static asset:

https://github.com/sveltejs/kit/blob/f7c4f36e833dce4ae0f79a6c1634810edd744fe3/packages/kit/src/runtime/app/server/index.js#L57-L69

Naively, I was able to fix the issue with this change:

diff --git a/src/core/generate_manifest/find_server_assets.js b/src/core/generate_manifest/find_server_assets.js
index 3a562f4b53a01301aa36bea61d11edf248d0890c..2009a241596811397166b8cd24e2550bf3499b0a 100644
--- a/src/core/generate_manifest/find_server_assets.js
+++ b/src/core/generate_manifest/find_server_assets.js
@@ -41,6 +41,7 @@ export function find_server_assets(build_data, routes) {
 
 	for (const n of used_nodes) {
 		const node = build_data.manifest_data.nodes[n];
+		if (node?.component) add_assets(node.component);
 		if (node?.server) add_assets(node.server);
 	}
 

But I assume there are good reasons why this isn't done (including that every asset imported by every page would be considered a server asset even when only used client-side).

jarrodldavis avatar Feb 04 '24 08:02 jarrodldavis

You're right in that it was done purposely to avoid including every page asset as a server asset since Vercel serverless functions have a size limit (and have better cold starts with smaller sizes). I wonder if in this case we can throw an error if it's being used in a page, or have assets imported and read with read copied as a server asset.

teemingc avatar Feb 12 '24 15:02 teemingc