diff --git a/.changeset/healthy-worms-double.md b/.changeset/healthy-worms-double.md new file mode 100644 index 000000000..9f9528ea8 --- /dev/null +++ b/.changeset/healthy-worms-double.md @@ -0,0 +1,20 @@ +--- +'@sveltejs/vite-plugin-svelte': major +--- + +move plugin options in svelte.config.js into "vitePlugin" + +update your svelte.config.js and wrap [plugin options](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#plugin-options) with `vitePlugin` + +```diff +// svelte.config.js + + compilerOptions: {...}, + preprocess: {...}, + extensions: [...], + onwarn: () => {...}, + kit: {}, ++ vitePlugin: { + // include, exclude, emitCss, hot, ignorePluginPreprocessors, disableDependencyReinclusion, experimental ++ } +``` diff --git a/docs/config.md b/docs/config.md index e3e3db29d..d6024e057 100644 --- a/docs/config.md +++ b/docs/config.md @@ -3,6 +3,7 @@ `vite-plugin-svelte` accepts inline options that can be used to change its behaviour. An object can be passed to the first argument of the `svelte` plugin: ```js +// vite.config.js export default defineConfig({ plugins: [ svelte({ @@ -18,7 +19,7 @@ Explore the various options below! ### Config file resolving -Besides inline options, `vite-plugin-svelte` will also automatically resolve options from a Svelte config file if one exists. The default search paths are: +Besides inline options in Vite config, `vite-plugin-svelte` will also automatically resolve options from a Svelte config file if one exists. The default search paths are: - `svelte.config.js` - `svelte.config.mjs` @@ -27,6 +28,7 @@ Besides inline options, `vite-plugin-svelte` will also automatically resolve opt To set a specific config file, use the `configFile` inline option. The path can be absolute or relative to the [Vite root](https://vitejs.dev/config/#root). For example: ```js +// vite.config.js export default defineConfig({ plugins: [ svelte({ @@ -42,12 +44,16 @@ A basic Svelte config looks like this: // svelte.config.js export default { // svelte options + extensions: ['.svelte'], compilerOptions: {}, preprocess: [], - // plugin options onwarn: (warning, handler) => handler(warning), - // experimental options - experimental: {} + // plugin options + vitePlugin: { + exclude: [], + // experimental options + experimental: {} + } }; ``` @@ -65,6 +71,7 @@ Depending on Node's mode, make sure you're using the correct extension and synta Use `configFile: false` to prevent `vite-plugin-svelte` from reading the config file or restarting the Vite dev server when it changes. ```js +// vite.config.js export default defineConfig({ plugins: [ svelte({ @@ -98,6 +105,7 @@ These options are specific to the Svelte compiler and are generally shared acros **Example:** ```js + // vite.config.js import sveltePreprocess from 'svelte-preprocess'; export default defineConfig({ @@ -109,22 +117,6 @@ These options are specific to the Svelte compiler and are generally shared acros }); ``` -## Plugin options - -These options are specific to the Vite plugin itself. - -### include - -- **Type:** `string | string[]` - -A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patterns, which specifies the files the plugin should operate on. By default, all svelte files are included. - -### exclude - -- **Type:** `string | string[]` - -A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patterns, which specifies the files to be ignored by the plugin. By default, no files are ignored. - ### extensions - **Type:** `string[]` @@ -132,13 +124,6 @@ A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patt A list of file extensions to be compiled by Svelte. Useful for custom extensions like `.svg` and `.svx`. -### emitCss - -- **Type:** `boolean` -- **Default:** `true` - - Emit Svelte styles as virtual CSS files for Vite and other plugins to process. - ### onwarn - **Type:** `(warning: Warning, defaultHandler?: (warning: Warning) => void) => void` - See [Warning](https://github.com/sveltejs/svelte/blob/ce550adef65a7e04c381b11c24f07a2ae1c25783/src/compiler/interfaces.ts#L121-L130) @@ -163,6 +148,29 @@ A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patt }); ``` +## Plugin options + +These options are specific to the Vite plugin itself. + +### include + +- **Type:** `string | string[]` + +A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patterns, which specifies the files the plugin should operate on. By default, all svelte files are included. + +### exclude + +- **Type:** `string | string[]` + +A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patterns, which specifies the files to be ignored by the plugin. By default, no files are ignored. + +### emitCss + +- **Type:** `boolean` +- **Default:** `true` + + Emit Svelte styles as virtual CSS files for Vite and other plugins to process. + ### hot - **Type:** `boolean | SvelteHotOptions` - See [svelte-hmr](https://github.com/sveltejs/svelte-hmr#options) @@ -197,7 +205,10 @@ A [picomatch pattern](https://github.com/micromatch/picomatch), or array of patt These options are considered experimental and breaking changes to them can occur in any release! Specify them under the `experimental` option. +Either in Vite config: + ```js +// vite.config.js export default defineConfig({ plugins: [ svelte({ @@ -209,6 +220,19 @@ export default defineConfig({ }); ``` +or in Svelte config: + +```js +// svelte.config.js +export default { + vitePlugin: { + experimental: { + // experimental options + } + } +}; +``` + ### useVitePreprocess - **Type:** `boolean` @@ -247,6 +271,7 @@ export default defineConfig({ **Example:** ```js + // vite.config.js export default defineConfig({ plugins: [ svelte({ @@ -321,6 +346,7 @@ export default defineConfig({ **Example:** ```js + // vite.config.js export default defineConfig({ plugins: [ svelte({ diff --git a/packages/e2e-tests/configfile-custom/svelte.config.custom.cjs b/packages/e2e-tests/configfile-custom/svelte.config.custom.cjs index 350c434bf..229a265a1 100644 --- a/packages/e2e-tests/configfile-custom/svelte.config.custom.cjs +++ b/packages/e2e-tests/configfile-custom/svelte.config.custom.cjs @@ -1,4 +1,6 @@ console.log('custom svelte config loaded cjs') module.exports = { - emitCss: false + vitePlugin: { + emitCss: false + } }; diff --git a/packages/e2e-tests/configfile-custom/svelte.config.custom.mjs b/packages/e2e-tests/configfile-custom/svelte.config.custom.mjs index ab4f0c9c7..523b097d5 100644 --- a/packages/e2e-tests/configfile-custom/svelte.config.custom.mjs +++ b/packages/e2e-tests/configfile-custom/svelte.config.custom.mjs @@ -1,4 +1,6 @@ console.log('custom svelte config loaded mjs') export default { - emitCss: false + vitePlugin: { + emitCss: false + } } diff --git a/packages/e2e-tests/hmr/__tests__/hmr.spec.ts b/packages/e2e-tests/hmr/__tests__/hmr.spec.ts index b65331b32..d1fc383f9 100644 --- a/packages/e2e-tests/hmr/__tests__/hmr.spec.ts +++ b/packages/e2e-tests/hmr/__tests__/hmr.spec.ts @@ -149,7 +149,7 @@ if (!isBuild) { }); test('should work with emitCss: false in svelte config', async () => { - addFile('svelte.config.cjs', `module.exports={emitCss:false}`); + addFile('svelte.config.cjs', `module.exports={vitePlugin:{emitCss:false}}`); await sleep(isWin ? 1000 : 500); // adding config restarts server, give it some time await page.goto(viteTestUrl, { waitUntil: 'networkidle' }); await sleep(50); diff --git a/packages/e2e-tests/inspector-kit/svelte.config.js b/packages/e2e-tests/inspector-kit/svelte.config.js index 9aebfc057..50b429c20 100644 --- a/packages/e2e-tests/inspector-kit/svelte.config.js +++ b/packages/e2e-tests/inspector-kit/svelte.config.js @@ -1,9 +1,11 @@ /** @type {import('@sveltejs/kit').Config} */ const config = { kit: {}, - experimental: { - inspector: { - showToggleButton: 'always' + vitePlugin: { + experimental: { + inspector: { + showToggleButton: 'always' + } } } }; diff --git a/packages/vite-plugin-svelte/src/index.ts b/packages/vite-plugin-svelte/src/index.ts index 1c34d508f..f3a05f4a6 100644 --- a/packages/vite-plugin-svelte/src/index.ts +++ b/packages/vite-plugin-svelte/src/index.ts @@ -227,6 +227,8 @@ export { loadSvelteConfig } from './utils/load-svelte-config'; export { Options, + PluginOptions, + SvelteOptions, Preprocessor, PreprocessorGroup, CompileOptions, diff --git a/packages/vite-plugin-svelte/src/utils/load-svelte-config.ts b/packages/vite-plugin-svelte/src/utils/load-svelte-config.ts index c0e205746..fa2ecd438 100644 --- a/packages/vite-plugin-svelte/src/utils/load-svelte-config.ts +++ b/packages/vite-plugin-svelte/src/utils/load-svelte-config.ts @@ -3,7 +3,7 @@ import path from 'path'; import fs from 'fs'; import { pathToFileURL } from 'url'; import { log } from './log'; -import { Options } from './options'; +import { Options, SvelteOptions } from './options'; import { UserConfig } from 'vite'; // used to require cjs config in esm. @@ -29,7 +29,7 @@ const dynamicImportDefault = new Function( export async function loadSvelteConfig( viteConfig?: UserConfig, inlineOptions?: Partial -): Promise | undefined> { +): Promise | undefined> { if (inlineOptions?.configFile === false) { return; } diff --git a/packages/vite-plugin-svelte/src/utils/options.ts b/packages/vite-plugin-svelte/src/utils/options.ts index aff42aabe..3599c99b3 100644 --- a/packages/vite-plugin-svelte/src/utils/options.ts +++ b/packages/vite-plugin-svelte/src/utils/options.ts @@ -19,8 +19,7 @@ import type { Processed // eslint-disable-next-line node/no-missing-import } from 'svelte/types/compiler/preprocess'; -// eslint-disable-next-line node/no-missing-import -import type { KitConfig } from '@sveltejs/kit'; + import path from 'path'; import { findRootSvelteDependencies, needsOptimization, SvelteDependency } from './dependencies'; import { createRequire } from 'module'; @@ -28,29 +27,93 @@ import { esbuildSveltePlugin, facadeEsbuildSveltePluginName } from './esbuild'; import { addExtraPreprocessors } from './preprocess'; import deepmerge from 'deepmerge'; -const knownOptions = new Set([ - 'configFile', +const allowedPluginOptions = new Set([ 'include', 'exclude', - 'extensions', 'emitCss', - 'compilerOptions', - 'onwarn', - 'preprocess', 'hot', 'ignorePluginPreprocessors', 'disableDependencyReinclusion', - 'experimental', - 'kit' + 'experimental' +]); + +const knownRootOptions = new Set(['extensions', 'compilerOptions', 'preprocess', 'onwarn']); + +const allowedInlineOptions = new Set([ + 'configFile', + 'kit', // only for internal use by sveltekit + ...allowedPluginOptions, + ...knownRootOptions ]); export function validateInlineOptions(inlineOptions?: Partial) { - const invalidKeys = Object.keys(inlineOptions || {}).filter((key) => !knownOptions.has(key)); + const invalidKeys = Object.keys(inlineOptions || {}).filter( + (key) => !allowedInlineOptions.has(key) + ); if (invalidKeys.length) { - log.warn(`invalid plugin options "${invalidKeys.join(', ')}" in config`, inlineOptions); + log.warn(`invalid plugin options "${invalidKeys.join(', ')}" in inline config`, inlineOptions); } } +function convertPluginOptions(config?: Partial): Partial | undefined { + if (!config) { + return; + } + const invalidRootOptions = Object.keys(config).filter((key) => allowedPluginOptions.has(key)); + if (invalidRootOptions.length > 0) { + throw new Error( + `Invalid options in svelte config. Move the following options into 'vitePlugin:{...}': ${invalidRootOptions.join( + ', ' + )}` + ); + } + if (!config.vitePlugin) { + return config; + } + const pluginOptions = config.vitePlugin; + const pluginOptionKeys = Object.keys(pluginOptions); + + const rootOptionsInPluginOptions = pluginOptionKeys.filter((key) => knownRootOptions.has(key)); + if (rootOptionsInPluginOptions.length > 0) { + throw new Error( + `Invalid options in svelte config under vitePlugin:{...}', move them to the config root : ${rootOptionsInPluginOptions.join( + ', ' + )}` + ); + } + const duplicateOptions = pluginOptionKeys.filter((key) => + Object.prototype.hasOwnProperty.call(config, key) + ); + if (duplicateOptions.length > 0) { + throw new Error( + `Invalid duplicate options in svelte config under vitePlugin:{...}', they are defined in root too and must only exist once: ${duplicateOptions.join( + ', ' + )}` + ); + } + const unknownPluginOptions = pluginOptionKeys.filter((key) => !allowedPluginOptions.has(key)); + if (unknownPluginOptions.length > 0) { + log.warn( + `ignoring unknown plugin options in svelte config under vitePlugin:{...}: ${unknownPluginOptions.join( + ', ' + )}` + ); + unknownPluginOptions.forEach((unkownOption) => { + // @ts-ignore + delete pluginOptions[unkownOption]; + }); + } + + const result: Options = { + ...config, + ...pluginOptions + }; + // @ts-expect-error it exists + delete result.vitePlugin; + + return result; +} + // used in config phase, merges the default options, svelte config, and inline options export async function preResolveOptions( inlineOptions: Partial = {}, @@ -65,7 +128,10 @@ export async function preResolveOptions( extensions: ['.svelte'], emitCss: true }; - const svelteConfig = await loadSvelteConfig(viteConfigWithResolvedRoot, inlineOptions); + const svelteConfig = convertPluginOptions( + await loadSvelteConfig(viteConfigWithResolvedRoot, inlineOptions) + ); + const extraOptions: Partial = { root: viteConfigWithResolvedRoot.root!, isBuild: viteEnv.command === 'build', @@ -199,14 +265,17 @@ function removeIgnoredOptions(options: ResolvedOptions) { // some SvelteKit options need compilerOptions to work, so set them here. function addSvelteKitOptions(options: ResolvedOptions) { + // @ts-expect-error kit is not typed to avoid dependency on sveltekit if (options?.kit != null) { - const hydratable = options.kit.browser?.hydrate !== false; + // @ts-expect-error kit is not typed to avoid dependency on sveltekit + const kit_browser_hydrate = options.kit.browser?.hydrate; + const hydratable = kit_browser_hydrate !== false; if ( options.compilerOptions.hydratable != null && options.compilerOptions.hydratable !== hydratable ) { log.warn( - `Conflicting values "compilerOptions.hydratable: ${options.compilerOptions.hydratable}" and "kit.browser.hydrate: ${options.kit.browser?.hydrate}" in your svelte config. You should remove "compilerOptions.hydratable".` + `Conflicting values "compilerOptions.hydratable: ${options.compilerOptions.hydratable}" and "kit.browser.hydrate: ${kit_browser_hydrate}" in your svelte config. You should remove "compilerOptions.hydratable".` ); } log.debug(`Setting compilerOptions.hydratable: ${hydratable} for SvelteKit`); @@ -390,7 +459,10 @@ export function patchResolvedViteConfig(viteConfig: ResolvedConfig, options: Res Object.assign(facadeEsbuildSveltePlugin, esbuildSveltePlugin(options)); } } -export interface Options { + +export type Options = Omit & PluginOptionsInline; + +interface PluginOptionsInline extends PluginOptions { /** * Path to a svelte config file, either absolute or relative to Vite root * @@ -399,7 +471,9 @@ export interface Options { * @see https://vitejs.dev/config/#root */ configFile?: string | false; +} +export interface PluginOptions { /** * A `picomatch` pattern, or array of patterns, which specifies the files the plugin should * operate on. By default, all svelte files are included. @@ -416,20 +490,6 @@ export interface Options { */ exclude?: Arrayable; - /** - * A list of file extensions to be compiled by Svelte - * - * @default ['.svelte'] - */ - extensions?: string[]; - - /** - * An array of preprocessors to transform the Svelte source code before compilation - * - * @see https://svelte.dev/docs#svelte_preprocess - */ - preprocess?: Arrayable; - /** * Emit Svelte styles as virtual CSS files for Vite and other plugins to process * @@ -437,20 +497,6 @@ export interface Options { */ emitCss?: boolean; - /** - * The options to be passed to the Svelte compiler. A few options are set by default, - * including `dev` and `css`. However, some options are non-configurable, like - * `filename`, `format`, `generate`, and `cssHash` (in dev). - * - * @see https://svelte.dev/docs#svelte_compile - */ - compilerOptions?: Omit; - - /** - * Handles warning emitted from the Svelte compiler - */ - onwarn?: (warning: Warning, defaultHandler?: (warning: Warning) => void) => void; - /** * Enable or disable Hot Module Replacement. * @@ -495,11 +541,41 @@ export interface Options { * These options are considered experimental and breaking changes to them can occur in any release */ experimental?: ExperimentalOptions; +} + +export interface SvelteOptions { + /** + * A list of file extensions to be compiled by Svelte + * + * @default ['.svelte'] + */ + extensions?: string[]; + + /** + * An array of preprocessors to transform the Svelte source code before compilation + * + * @see https://svelte.dev/docs#svelte_preprocess + */ + preprocess?: Arrayable; + + /** + * The options to be passed to the Svelte compiler. A few options are set by default, + * including `dev` and `css`. However, some options are non-configurable, like + * `filename`, `format`, `generate`, and `cssHash` (in dev). + * + * @see https://svelte.dev/docs#svelte_compile + */ + compilerOptions?: Omit; + + /** + * Handles warning emitted from the Svelte compiler + */ + onwarn?: (warning: Warning, defaultHandler?: (warning: Warning) => void) => void; /** - * Options for SvelteKit + * Options for vite-plugin-svelte */ - kit?: KitConfig; + vitePlugin?: PluginOptions; } /**