diff --git a/.changeset/loud-pets-cheat.md b/.changeset/loud-pets-cheat.md
new file mode 100644
index 000000000..73f5420ae
--- /dev/null
+++ b/.changeset/loud-pets-cheat.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/vite-plugin-svelte': minor
+---
+
+Add `experimental.prebundleSvelteLibraries` option
diff --git a/docs/config.md b/docs/config.md
index e9dad78fc..0833612d5 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -193,6 +193,19 @@ export default defineConfig({
Use extra preprocessors that delegate style and TypeScript preprocessing to native Vite plugins. Do not use together with `svelte-preprocess`!
+### prebundleSvelteLibraries
+
+- **Type:** `boolean`
+- **Default:** `false`
+
+ Force Vite to pre-bundle Svelte libraries. Currently, `vite-plugin-svelte` implements a complex mechanism to address pre-bundling Svelte libraries, which had an impact on large Svelte component libraries and the initial page load. See the [FAQ](./faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies) for more information.
+
+ Setting this option to `true` will directly pre-bundle Svelte libraries, which should improve initial page load performance. However, please note some caveats:
+
+ 1. Deeply importing Svelte components is not supported. Either import all components from one entrypoint, or always stick to deep imports, otherwise it could cause multiple instance of the Svelte library running.
+
+ 2. When updating the Svelte compiler options in `svelte.config.js` or `vite.config.js`, delete the `node_modules/.vite` folder to trigger pre-bundling in Vite again.
+
### generateMissingPreprocessorSourcemaps
- **Type:** `boolean`
diff --git a/docs/faq.md b/docs/faq.md
index aceb76233..997e2500e 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -95,6 +95,8 @@ For reference, check out [windicss](https://github.com/windicss/vite-plugin-wind
Pre-bundling dependencies is an [optimization in Vite](https://vitejs.dev/guide/dep-pre-bundling.html). It is required for CJS dependencies, as Vite's development server only works with ES modules on the client side.
-Thanks to [a new API in Vite](https://github.com/vitejs/vite/pull/4634), [vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte/pull/157) automatically handles pre-bundling these for you.
+Historically, Svelte components had issues being pre-bundled, causing [deduplication issues](https://github.com/vitejs/vite/issues/3910) and [CJS interoperability issues](https://github.com/vitejs/vite/issues/3024). Since Vite 2.5.2, [a new API in Vite](https://github.com/vitejs/vite/pull/4634) allowed us to [automatically handle Svelte library pre-bundling](https://github.com/sveltejs/vite-plugin-svelte/pull/157) for you.
+
+Today, this feature had served us well, however a caveat remained that large Svelte component libraries often slows down the initial page load. If this affects you, try setting [experimental.prebundleSvelteLibraries](./config.md#prebundleSvelteLibraries) option to `true` to speed things up. (Note that it's an experimental option that may not always work)
In case you still run into errors like `The requested module 'xxx' does not provide an export named 'yyy'`, please check our [open issues](https://github.com/sveltejs/vite-plugin-svelte/issues).
diff --git a/packages/playground/big-component-library/README.md b/packages/playground/big-component-library/README.md
new file mode 100644
index 000000000..919823b3a
--- /dev/null
+++ b/packages/playground/big-component-library/README.md
@@ -0,0 +1,3 @@
+# do not use as a starter
+
+This example is an app using a large component library
diff --git a/packages/playground/big-component-library/index.html b/packages/playground/big-component-library/index.html
new file mode 100644
index 000000000..7a46a8161
--- /dev/null
+++ b/packages/playground/big-component-library/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Svelte + Vite App
+
+
+
+
+
+
diff --git a/packages/playground/big-component-library/jsconfig.json b/packages/playground/big-component-library/jsconfig.json
new file mode 100644
index 000000000..42585941e
--- /dev/null
+++ b/packages/playground/big-component-library/jsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "moduleResolution": "node",
+ "target": "esnext",
+ "module": "esnext",
+ /**
+ * svelte-preprocess cannot figure out whether you have
+ * a value or a type, so tell TypeScript to enforce using
+ * `import type` instead of `import` for Types.
+ */
+ "importsNotUsedAsValues": "error",
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ /**
+ * To have warnings / errors of the Svelte compiler at the
+ * correct position, enable source maps by default.
+ */
+ "sourceMap": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable this if you'd like to use dynamic types.
+ */
+ "checkJs": true
+ },
+ /**
+ * Use global.d.ts instead of compilerOptions.types
+ * to avoid limiting type declarations.
+ */
+ "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
+}
diff --git a/packages/playground/big-component-library/package.json b/packages/playground/big-component-library/package.json
new file mode 100644
index 000000000..5b8fbdfef
--- /dev/null
+++ b/packages/playground/big-component-library/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "playground-big-component-library",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "serve": "vite preview"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "workspace:*",
+ "carbon-components-svelte": "^0.45.1",
+ "svelte": "^3.43.2",
+ "vite": "^2.6.7"
+ }
+}
diff --git a/packages/playground/big-component-library/src/App.svelte b/packages/playground/big-component-library/src/App.svelte
new file mode 100644
index 000000000..88770231a
--- /dev/null
+++ b/packages/playground/big-component-library/src/App.svelte
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Content 1
+ Content 2
+ Content 3
+
diff --git a/packages/playground/big-component-library/src/main.js b/packages/playground/big-component-library/src/main.js
new file mode 100644
index 000000000..53f5b9373
--- /dev/null
+++ b/packages/playground/big-component-library/src/main.js
@@ -0,0 +1,8 @@
+import App from './App.svelte';
+import 'carbon-components-svelte/css/white.css';
+
+const app = new App({
+ target: document.getElementById('app')
+});
+
+export default app;
diff --git a/packages/playground/big-component-library/vite.config.js b/packages/playground/big-component-library/vite.config.js
new file mode 100644
index 000000000..b642449be
--- /dev/null
+++ b/packages/playground/big-component-library/vite.config.js
@@ -0,0 +1,12 @@
+import { svelte } from '@sveltejs/vite-plugin-svelte';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ plugins: [
+ svelte({
+ experimental: {
+ prebundleSvelteLibraries: true
+ }
+ })
+ ]
+});
diff --git a/packages/vite-plugin-svelte/src/utils/esbuild.ts b/packages/vite-plugin-svelte/src/utils/esbuild.ts
new file mode 100644
index 000000000..8375d7583
--- /dev/null
+++ b/packages/vite-plugin-svelte/src/utils/esbuild.ts
@@ -0,0 +1,94 @@
+import { promises as fs } from 'fs';
+import { compile, preprocess } from 'svelte/compiler';
+import { DepOptimizationOptions } from 'vite';
+import { Compiled } from './compile';
+import { log } from './log';
+import { CompileOptions, ResolvedOptions } from './options';
+
+type EsbuildOptions = NonNullable;
+type EsbuildPlugin = NonNullable[number];
+type EsbuildPluginBuild = Parameters[0];
+
+export function esbuildSveltePlugin(options: ResolvedOptions): EsbuildPlugin {
+ return {
+ name: 'vite-plugin-svelte:optimize-svelte',
+ setup(build) {
+ disableVitePrebundleSvelte(build);
+
+ const svelteExtensions = (options.extensions ?? ['.svelte']).map((ext) => ext.slice(1));
+ const svelteFilter = new RegExp(`\\.(` + svelteExtensions.join('|') + `)(\\?.*)?$`);
+
+ build.onLoad({ filter: svelteFilter }, async ({ path: filename }) => {
+ const code = await fs.readFile(filename, 'utf8');
+ const contents = await compileSvelte(options, { filename, code });
+ return { contents };
+ });
+ }
+ };
+}
+
+function disableVitePrebundleSvelte(build: EsbuildPluginBuild) {
+ const viteDepPrebundlePlugin = build.initialOptions.plugins?.find(
+ (v) => v.name === 'vite:dep-pre-bundle'
+ );
+
+ if (!viteDepPrebundlePlugin) return;
+
+ // Prevent vite:dep-pre-bundle from externalizing svelte files
+ const _setup = viteDepPrebundlePlugin.setup.bind(viteDepPrebundlePlugin);
+ viteDepPrebundlePlugin.setup = function (build) {
+ const _onResolve = build.onResolve.bind(build);
+ build.onResolve = function (options, callback) {
+ if (options.filter.source.includes('svelte')) {
+ options.filter = new RegExp(
+ options.filter.source.replace('|svelte', ''),
+ options.filter.flags
+ );
+ }
+ return _onResolve(options, callback);
+ };
+ return _setup(build);
+ };
+}
+
+async function compileSvelte(
+ options: ResolvedOptions,
+ { filename, code }: { filename: string; code: string }
+): Promise {
+ const compileOptions: CompileOptions = {
+ ...options.compilerOptions,
+ css: true,
+ filename,
+ generate: 'dom'
+ };
+
+ let preprocessed;
+
+ if (options.preprocess) {
+ preprocessed = await preprocess(code, options.preprocess, { filename });
+ if (preprocessed.map) compileOptions.sourcemap = preprocessed.map;
+ }
+
+ const finalCode = preprocessed ? preprocessed.code : code;
+
+ const dynamicCompileOptions = await options.experimental?.dynamicCompileOptions?.({
+ filename,
+ code: finalCode,
+ compileOptions
+ });
+
+ if (dynamicCompileOptions && log.debug.enabled) {
+ log.debug(`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`);
+ }
+
+ const finalCompileOptions = dynamicCompileOptions
+ ? {
+ ...compileOptions,
+ ...dynamicCompileOptions
+ }
+ : compileOptions;
+
+ const compiled = compile(finalCode, finalCompileOptions) as Compiled;
+
+ return compiled.js.code + '//# sourceMappingURL=' + compiled.js.map.toUrl();
+}
diff --git a/packages/vite-plugin-svelte/src/utils/options.ts b/packages/vite-plugin-svelte/src/utils/options.ts
index 001b16a14..7e46666a5 100644
--- a/packages/vite-plugin-svelte/src/utils/options.ts
+++ b/packages/vite-plugin-svelte/src/utils/options.ts
@@ -15,6 +15,7 @@ import {
import path from 'path';
import { findRootSvelteDependencies, needsOptimization, SvelteDependency } from './dependencies';
import { createRequire } from 'module';
+import { esbuildSveltePlugin } from './esbuild';
const knownOptions = new Set([
'configFile',
@@ -217,8 +218,6 @@ function buildOptimizeDepsForSvelte(
options: ResolvedOptions,
optimizeDeps?: DepOptimizationOptions
): DepOptimizationOptions {
- // only svelte component libraries needs to be processed for optimizeDeps, js libraries work fine
- svelteDeps = svelteDeps.filter((dep) => dep.type === 'component-library');
// include svelte imports for optimization unless explicitly excluded
const include: string[] = [];
const exclude: string[] = ['svelte-hmr'];
@@ -241,6 +240,20 @@ function buildOptimizeDepsForSvelte(
log.debug('"svelte" is excluded in optimizeDeps.exclude, skipped adding it to include.');
}
+ // If we prebundle svelte libraries, we can skip the whole prebundling dance below
+ if (options.experimental.prebundleSvelteLibraries) {
+ return {
+ include,
+ exclude,
+ esbuildOptions: {
+ plugins: [esbuildSveltePlugin(options)]
+ }
+ };
+ }
+
+ // only svelte component libraries needs to be processed for optimizeDeps, js libraries work fine
+ svelteDeps = svelteDeps.filter((dep) => dep.type === 'component-library');
+
const svelteDepsToExclude = Array.from(new Set(svelteDeps.map((dep) => dep.name))).filter(
(dep) => !isIncluded(dep)
);
@@ -420,6 +433,13 @@ export interface ExperimentalOptions {
*/
useVitePreprocess?: boolean;
+ /**
+ * Force Vite to pre-bundle Svelte libraries
+ *
+ * @default false
+ */
+ prebundleSvelteLibraries?: boolean;
+
/**
* If a preprocessor does not provide a sourcemap, a best-effort fallback sourcemap will be provided.
* This option requires `diff-match-patch` to be installed as a peer dependency.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6a2176afc..0bdc1b1f4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -335,6 +335,18 @@ importers:
svelte: 3.43.2
vite: 2.6.7
+ packages/playground/big-component-library:
+ specifiers:
+ '@sveltejs/vite-plugin-svelte': workspace:*
+ carbon-components-svelte: ^0.45.1
+ svelte: ^3.43.2
+ vite: ^2.6.7
+ devDependencies:
+ '@sveltejs/vite-plugin-svelte': link:../../vite-plugin-svelte
+ carbon-components-svelte: 0.45.1
+ svelte: 3.43.2
+ vite: 2.6.7
+
packages/playground/kit-demo-app:
specifiers:
'@fontsource/fira-mono': ^4.5.0
@@ -2032,6 +2044,17 @@ packages:
resolution: {integrity: sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w==}
dev: true
+ /carbon-components-svelte/0.45.1:
+ resolution: {integrity: sha512-U01DMtcWyaDt3kYmnVp+D/spT8u3waKbUH4Hjd3ygTbJI1YzZY6JojqFPKu1YxKw1DBbMZUp+y3/6Fx40s6Uig==}
+ dependencies:
+ carbon-icons-svelte: 10.38.0
+ flatpickr: 4.6.9
+ dev: true
+
+ /carbon-icons-svelte/10.38.0:
+ resolution: {integrity: sha512-uEckbbNHFtDzKz+QRx6GxvZcezXXqfDOiaE7mupkiRPkBbALrA9CPJZbI5Q2XW5mpn8lgN3IlTm2u1iJEAlVJw==}
+ dev: true
+
/chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -3290,6 +3313,10 @@ packages:
rimraf: 3.0.2
dev: true
+ /flatpickr/4.6.9:
+ resolution: {integrity: sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==}
+ dev: true
+
/flatted/3.2.2:
resolution: {integrity: sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==}
dev: true