diff --git a/.changeset/spotty-jokes-notice.md b/.changeset/spotty-jokes-notice.md new file mode 100644 index 000000000..abf6d4513 --- /dev/null +++ b/.changeset/spotty-jokes-notice.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/vite-plugin-svelte': minor +--- + +feat: enable optimizer for server environments during dev diff --git a/packages/vite-plugin-svelte/src/plugins/configure.js b/packages/vite-plugin-svelte/src/plugins/configure.js index 68f5e080a..b9cd6a0c5 100644 --- a/packages/vite-plugin-svelte/src/plugins/configure.js +++ b/packages/vite-plugin-svelte/src/plugins/configure.js @@ -39,6 +39,11 @@ export function configure(api, inlineOptions) { */ let preOptions; + /** + * @type {import('vite').DepOptimizationConfig | undefined} + */ + let optimizeDeps = undefined; + /** @type {import('vite').Plugin} */ return { name: 'vite-plugin-svelte:config', @@ -59,9 +64,11 @@ export function configure(api, inlineOptions) { // extra vite config const extraViteConfig = await buildExtraViteConfig(preOptions, config); log.debug('additional vite config', extraViteConfig, 'config'); + optimizeDeps = extraViteConfig.optimizeDeps; return extraViteConfig; } }, + configResolved: { order: 'pre', handler(config) { @@ -87,6 +94,12 @@ export function configure(api, inlineOptions) { ensureConfigEnvironmentConditions(name, config, opts); // @ts-expect-error the function above should make `resolve.conditions` non-nullable config.resolve.conditions.push('svelte'); + if (config.consumer === 'server' && optimizeDeps !== undefined) { + // optimizeDeps is not inherited by server environments so return it here + return { + optimizeDeps + }; + } }, configureServer(server) { diff --git a/packages/vite-plugin-svelte/src/plugins/setup-optimizer.js b/packages/vite-plugin-svelte/src/plugins/setup-optimizer.js index 56468a5ac..c09273a3a 100644 --- a/packages/vite-plugin-svelte/src/plugins/setup-optimizer.js +++ b/packages/vite-plugin-svelte/src/plugins/setup-optimizer.js @@ -32,7 +32,9 @@ export function setupOptimizer(api) { return { name: 'vite-plugin-svelte:setup-optimizer', apply: 'serve', - config() { + configEnvironment(name, config) { + // fall back to vite behavior when consumer isn't set + const consumer = (config.consumer ?? name === 'client') ? 'client' : 'server'; /** @type {import('vite').UserConfig['optimizeDeps']} */ const optimizeDeps = { // Experimental Vite API to allow these extensions to be scanned and prebundled @@ -43,17 +45,18 @@ export function setupOptimizer(api) { // the added plugins are patched in configResolved below if (rolldownVersion) { //@ts-ignore rolldown types not finished + optimizeDeps.rollupOptions = { plugins: [ - placeholderRolldownOptimizerPlugin(optimizeSveltePluginName), - placeholderRolldownOptimizerPlugin(optimizeSvelteModulePluginName) + rolldownOptimizerPlugin(api, consumer, true), + rolldownOptimizerPlugin(api, consumer, false) ] }; } else { optimizeDeps.esbuildOptions = { plugins: [ - { name: optimizeSveltePluginName, setup: () => {} }, - { name: optimizeSvelteModulePluginName, setup: () => {} } + esbuildOptimizerPlugin(api, consumer, true), + esbuildOptimizerPlugin(api, consumer, false) ] }; } @@ -61,25 +64,6 @@ export function setupOptimizer(api) { }, configResolved(c) { viteConfig = c; - const optimizeDeps = c.optimizeDeps; - if (rolldownVersion) { - const plugins = - // @ts-expect-error not typed - optimizeDeps.rollupOptions?.plugins?.filter((p) => - [optimizeSveltePluginName, optimizeSvelteModulePluginName].includes(p.name) - ) ?? []; - for (const plugin of plugins) { - patchRolldownOptimizerPlugin(plugin, api.options); - } - } else { - const plugins = - optimizeDeps.esbuildOptions?.plugins?.filter((p) => - [optimizeSveltePluginName, optimizeSvelteModulePluginName].includes(p.name) - ) ?? []; - for (const plugin of plugins) { - patchESBuildOptimizerPlugin(plugin, api.options); - } - } }, async buildStart() { if (!api.options.prebundleSvelteLibraries) return; @@ -94,54 +78,72 @@ export function setupOptimizer(api) { } /** - * @param {EsbuildPlugin} plugin - * @param {import('../types/options.d.ts').ResolvedOptions} options + * @param {import('../types/plugin-api.d.ts').PluginAPI} api + * @param {'server'|'client'} consumer + * @param {boolean} components + * @return {EsbuildPlugin} */ -function patchESBuildOptimizerPlugin(plugin, options) { - const components = plugin.name === optimizeSveltePluginName; +function esbuildOptimizerPlugin(api, consumer, components) { + const name = components ? optimizeSveltePluginName : optimizeSvelteModulePluginName; const compileFn = components ? compileSvelte : compileSvelteModule; const statsName = components ? 'prebundle library components' : 'prebundle library modules'; const filter = components ? /\.svelte(?:\?.*)?$/ : /\.svelte\.[jt]s(?:\?.*)?$/; - plugin.setup = (build) => { - if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return; + const generate = consumer === 'server' ? 'server' : 'client'; - /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ - let statsCollection; - build.onStart(() => { - statsCollection = options.stats?.startCollection(statsName, { - logResult: (c) => c.stats.length > 1 + return { + name, + setup(build) { + if (build.initialOptions.plugins?.some((v) => v.name === 'vite:dep-scan')) return; + + /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ + let statsCollection; + build.onStart(() => { + statsCollection = api.options.stats?.startCollection(statsName, { + logResult: (c) => c.stats.length > 1 + }); }); - }); - build.onLoad({ filter }, async ({ path: filename }) => { - const code = readFileSync(filename, 'utf8'); - try { - const result = await compileFn(options, { filename, code }, statsCollection); - const contents = result.map - ? result.code + '//# sourceMappingURL=' + result.map.toUrl() - : result.code; - return { contents }; - } catch (e) { - return { errors: [toESBuildError(e, options)] }; - } - }); - build.onEnd(() => { - statsCollection?.finish(); - }); + build.onLoad({ filter }, async ({ path: filename }) => { + const code = readFileSync(filename, 'utf8'); + try { + const result = await compileFn( + api.options, + { filename, code }, + generate, + statsCollection + ); + const contents = result.map + ? result.code + '//# sourceMappingURL=' + result.map.toUrl() + : result.code; + return { contents }; + } catch (e) { + return { errors: [toESBuildError(e, api.options)] }; + } + }); + build.onEnd(() => { + statsCollection?.finish(); + }); + } }; } /** - * @param {RollupPlugin} plugin - * @param {import('../types/options.d.ts').ResolvedOptions} options + * @param {import('../types/plugin-api.d.ts').PluginAPI} api + * @param {'server'|'client'} consumer + * @param {boolean} components + * @return {import('vite').Rollup.Plugin} */ -function patchRolldownOptimizerPlugin(plugin, options) { - const components = plugin.name === optimizeSveltePluginName; +function rolldownOptimizerPlugin(api, consumer, components) { + const name = components ? optimizeSveltePluginName : optimizeSvelteModulePluginName; const compileFn = components ? compileSvelte : compileSvelteModule; const statsName = components ? 'prebundle library components' : 'prebundle library modules'; const includeRe = components ? /^[^?#]+\.svelte(?:[?#]|$)/ : /^[^?#]+\.svelte\.[jt]s(?:[?#]|$)/; + const generate = consumer === 'server' ? 'server' : 'client'; /** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */ let statsCollection; - + /**@type {import('vite').Rollup.Plugin}*/ + const plugin = { + name + }; plugin.options = (opts) => { // @ts-expect-error plugins is an array here const isScanner = opts.plugins.some( @@ -160,14 +162,14 @@ function patchRolldownOptimizerPlugin(plugin, options) { */ async handler(code, filename) { try { - return await compileFn(options, { filename, code }, statsCollection); + return await compileFn(api.options, { filename, code }, generate, statsCollection); } catch (e) { - throw toRollupError(e, options); + throw toRollupError(e, api.options); } } }; plugin.buildStart = () => { - statsCollection = options.stats?.startCollection(statsName, { + statsCollection = api.options.stats?.startCollection(statsName, { logResult: (c) => c.stats.length > 1 }); }; @@ -176,15 +178,17 @@ function patchRolldownOptimizerPlugin(plugin, options) { }; } }; + return plugin; } /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @param {{ filename: string, code: string }} input + * @param {'client'|'server'} generate * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection] * @returns {Promise} */ -async function compileSvelte(options, { filename, code }, statsCollection) { +async function compileSvelte(options, { filename, code }, generate, statsCollection) { let css = options.compilerOptions.css; if (css !== 'injected') { // TODO ideally we'd be able to externalize prebundled styles too, but for now always put them in the js @@ -196,7 +200,7 @@ async function compileSvelte(options, { filename, code }, statsCollection) { ...options.compilerOptions, css, filename, - generate: 'client' + generate }; if (compileOptions.hmr && options.emitCss) { @@ -252,15 +256,16 @@ async function compileSvelte(options, { filename, code }, statsCollection) { /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @param {{ filename: string; code: string }} input + * @param {'client'|'server'} generate * @param {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection} [statsCollection] * @returns {Promise} */ -async function compileSvelteModule(options, { filename, code }, statsCollection) { +async function compileSvelteModule(options, { filename, code }, generate, statsCollection) { const endStat = statsCollection?.start(filename); const compiled = svelte.compileModule(code, { dev: options.compilerOptions?.dev ?? true, // default to dev: true because prebundling is only used in dev filename, - generate: 'client' + generate }); if (endStat) { endStat(); @@ -311,21 +316,6 @@ async function svelteMetadataChanged(cacheDir, options) { return currentSvelteMetadata !== existingSvelteMetadata; } -/** - * - * @param {string} name - * @returns {import('vite').Rollup.Plugin} - */ -function placeholderRolldownOptimizerPlugin(name) { - return { - name, - options() {}, - buildStart() {}, - buildEnd() {}, - transform: { filter: { id: /^$/ }, handler() {} } - }; -} - /** * @param {import('../types/options.d.ts').ResolvedOptions} options * @returns {Partial}