typescriptvitesveltesveltekit

Are dynamic imports at runtime in the browser (ESM) possible with Svelte?


I'm updating a Svelte component/framework library (using Vite / Sveltekit) that was created using npm create svelte. I'm using the default compilation setup.

The library must be able to render internally a Svelte component. However Svelte 5 changed the way this happen and I want to preserve compatibility no matter if the application using the library uses Svelte 4 or 5.

I added in the library a condition around the version. It gives something like this:

import { VERSION } from 'svelte/compiler';

const SVELTE_MAJOR_VERSION = parseInt(VERSION.split('.')[0]);

const { html, head } = await (async () => {
    if (SVELTE_MAJOR_VERSION < 5) {
        return SSR.render({ id, initialPage });
    }
    else {
        const { render } = await import('svelte/server');
        return render(SSR, { props: { id, initialPage } });
    }
})();

return {
    body: html,
    head: [head],
};

The package import('svelte/server') doesn't exist under Svelte 4 which is why I must somehow import it conditionally/dynamically. It seems that I can build successfully if I use Svelte 5 as a dependency, however I can't build it using Svelte 4. Also, the dist files generated when using Svelte 5 are not usable under a Svelte 4 application either.

In case this is of any help, here is the output of the failed build using Svelte 4 as a dependency.

vite v5.2.11 building for production...
āœ“ 16 modules transformed.
x Build failed in 199ms
error during build:
Error: [commonjs--resolver] Missing "./server" specifier in "svelte" package
    at e (file:///Users/james/Projects/inertia/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.11/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:47597:25)
    at n (file:///Users/james/Projects/inertia/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.11/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:47597:627)
    at o (file:///Users/james/Projects/inertia/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.11/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:47597:1297)
    at resolveExportsOrImports (file:///Users/james/Projects/inertia/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.11/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:48287:20)
    at resolveDeepImport (file:///Users/james/Projects/inertia/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.11/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:48306:31)
    at tryNodeResolve (file:///Users/james/Projects/inertia/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.11/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:48031:20)
    at Object.resolveId (file:///Users/james/Projects/inertia/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.11/node_modules/vite/dist/node/chunks/dep-cNe07EU9.js:47781:28)
    at file:///Users/james/Projects/inertia/node_modules/.pnpm/rollup@4.17.2/node_modules/rollup/dist/es/shared/node-entry.js:19778:40
    at async PluginDriver.hookFirstAndGetPlugin (file:///Users/james/Projects/inertia/node_modules/.pnpm/rollup@4.17.2/node_modules/rollup/dist/es/shared/node-entry.js:19678:28)
    at async resolveId (file:///Users/james/Projects/inertia/node_modules/.pnpm/rollup@4.17.2/node_modules/rollup/dist/es/shared/node-entry.js:18359:26)

Solution

  • It appears doing this will work

    async function dynamicImport(modulePath: string) {
      try {
        return await import(/* @vite-ignore */ modulePath)
      } catch {
        return null
      }
    }
    
    // Somewhere 
    const { render } = await dynamicImport('svelte/server');
    

    But this wouldn't

    async function dynamicImport() {
      try {
        return await import(/* @vite-ignore */ 'svelte/server')
      } catch {
        return null
      }
    }
    
    
    // Somewhere
    const { render } = await dynamicImport();
    

    My guess is because vite can't guess the import before it actually happens if we use a function without dynamic parameter