|
|
|
import { normalizePath } from 'vite'; |
|
import { isDebugNamespaceEnabled, log } from './log.js'; |
|
import { loadSvelteConfig } from './load-svelte-config.js'; |
|
import { |
|
FAQ_LINK_MISSING_EXPORTS_CONDITION, |
|
SVELTE_EXPORT_CONDITIONS, |
|
SVELTE_HMR_IMPORTS, |
|
SVELTE_IMPORTS, |
|
SVELTE_RESOLVE_MAIN_FIELDS, |
|
VITE_RESOLVE_MAIN_FIELDS |
|
} from './constants.js'; |
|
|
|
import path from 'node:path'; |
|
import { esbuildSveltePlugin, facadeEsbuildSveltePluginName } from './esbuild.js'; |
|
import { addExtraPreprocessors } from './preprocess.js'; |
|
import deepmerge from 'deepmerge'; |
|
import { |
|
crawlFrameworkPkgs, |
|
isDepExcluded, |
|
isDepExternaled, |
|
isDepIncluded, |
|
isDepNoExternaled |
|
} from 'vitefu'; |
|
|
|
import { isCommonDepWithoutSvelteField } from './dependencies.js'; |
|
import { VitePluginSvelteStats } from './vite-plugin-svelte-stats.js'; |
|
import { VitePluginSvelteCache } from './vite-plugin-svelte-cache.js'; |
|
import { isSvelte5, isSvelte5WithHMRSupport } from './svelte-version.js'; |
|
|
|
const allowedPluginOptions = new Set([ |
|
'include', |
|
'exclude', |
|
'emitCss', |
|
'hot', |
|
'ignorePluginPreprocessors', |
|
'disableDependencyReinclusion', |
|
'prebundleSvelteLibraries', |
|
'inspector', |
|
'dynamicCompileOptions', |
|
'experimental' |
|
]); |
|
|
|
const knownRootOptions = new Set(['extensions', 'compilerOptions', 'preprocess', 'onwarn']); |
|
|
|
const allowedInlineOptions = new Set(['configFile', ...allowedPluginOptions, ...knownRootOptions]); |
|
|
|
|
|
|
|
|
|
export function validateInlineOptions(inlineOptions) { |
|
const invalidKeys = Object.keys(inlineOptions || {}).filter( |
|
(key) => !allowedInlineOptions.has(key) |
|
); |
|
if (invalidKeys.length) { |
|
log.warn(`invalid plugin options "${invalidKeys.join(', ')}" in inline config`, inlineOptions); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function convertPluginOptions(config) { |
|
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) => { |
|
|
|
delete pluginOptions[unkownOption]; |
|
}); |
|
} |
|
|
|
const result = { |
|
...config, |
|
...pluginOptions |
|
}; |
|
|
|
delete result.vitePlugin; |
|
|
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function preResolveOptions(inlineOptions, viteUserConfig, viteEnv) { |
|
if (!inlineOptions) { |
|
inlineOptions = {}; |
|
} |
|
|
|
const viteConfigWithResolvedRoot = { |
|
...viteUserConfig, |
|
root: resolveViteRoot(viteUserConfig) |
|
}; |
|
const isBuild = viteEnv.command === 'build'; |
|
|
|
const defaultOptions = { |
|
extensions: ['.svelte'], |
|
emitCss: true, |
|
prebundleSvelteLibraries: !isBuild |
|
}; |
|
const svelteConfig = convertPluginOptions( |
|
await loadSvelteConfig(viteConfigWithResolvedRoot, inlineOptions) |
|
); |
|
|
|
const extraOptions = { |
|
root: viteConfigWithResolvedRoot.root, |
|
isBuild, |
|
isServe: viteEnv.command === 'serve', |
|
isDebug: process.env.DEBUG != null |
|
}; |
|
|
|
const merged = ( |
|
mergeConfigs(defaultOptions, svelteConfig, inlineOptions, extraOptions) |
|
); |
|
|
|
|
|
if (svelteConfig?.configFile) { |
|
merged.configFile = svelteConfig.configFile; |
|
} |
|
return merged; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function mergeConfigs(...configs) { |
|
|
|
let result = {}; |
|
for (const config of configs.filter((x) => x != null)) { |
|
result = deepmerge(result, (config), { |
|
|
|
arrayMerge: (target, source) => source ?? target |
|
}); |
|
} |
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function resolveOptions(preResolveOptions, viteConfig, cache) { |
|
const css = preResolveOptions.emitCss ? 'external' : 'injected'; |
|
|
|
const defaultOptions = { |
|
compilerOptions: { |
|
css, |
|
dev: !viteConfig.isProduction |
|
} |
|
}; |
|
const hot = |
|
!viteConfig.isProduction && !preResolveOptions.isBuild && viteConfig.server.hmr !== false; |
|
if (isSvelte5) { |
|
if (isSvelte5WithHMRSupport) { |
|
|
|
defaultOptions.compilerOptions.hmr = hot; |
|
} |
|
} else { |
|
defaultOptions.hot = !hot |
|
? false |
|
: { |
|
injectCss: css === 'injected', |
|
partialAccept: !!viteConfig.experimental?.hmrPartialAccept |
|
}; |
|
} |
|
|
|
const extraOptions = { |
|
root: viteConfig.root, |
|
isProduction: viteConfig.isProduction |
|
}; |
|
const merged = ( |
|
mergeConfigs(defaultOptions, preResolveOptions, extraOptions) |
|
); |
|
|
|
removeIgnoredOptions(merged); |
|
handleDeprecatedOptions(merged); |
|
addExtraPreprocessors(merged, viteConfig); |
|
enforceOptionsForHmr(merged, viteConfig); |
|
enforceOptionsForProduction(merged); |
|
|
|
if (log.debug.enabled && isDebugNamespaceEnabled('stats')) { |
|
merged.stats = new VitePluginSvelteStats(cache); |
|
} |
|
return merged; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function enforceOptionsForHmr(options, viteConfig) { |
|
if (options.hot && viteConfig.server.hmr === false) { |
|
log.warn( |
|
'vite config server.hmr is false but hot is true. Forcing hot to false as it would not work.' |
|
); |
|
options.hot = false; |
|
} |
|
if (isSvelte5) { |
|
if (options.hot && isSvelte5WithHMRSupport) { |
|
log.warn( |
|
'svelte 5 has hmr integrated in core. Please remove the hot option and use compilerOptions.hmr instead' |
|
); |
|
delete options.hot; |
|
|
|
options.compilerOptions.hmr = true; |
|
} |
|
} else { |
|
if (options.hot) { |
|
if (!options.compilerOptions.dev) { |
|
log.warn('hmr is enabled but compilerOptions.dev is false, forcing it to true'); |
|
options.compilerOptions.dev = true; |
|
} |
|
if (options.emitCss) { |
|
if (options.hot !== true && options.hot.injectCss) { |
|
log.warn('hmr and emitCss are enabled but hot.injectCss is true, forcing it to false'); |
|
options.hot.injectCss = false; |
|
} |
|
const css = options.compilerOptions.css; |
|
if (css === true || css === 'injected') { |
|
const forcedCss = 'external'; |
|
log.warn( |
|
`hmr and emitCss are enabled but compilerOptions.css is ${css}, forcing it to ${forcedCss}` |
|
); |
|
options.compilerOptions.css = forcedCss; |
|
} |
|
} else { |
|
if (options.hot === true || !options.hot.injectCss) { |
|
log.warn( |
|
'hmr with emitCss disabled requires option hot.injectCss to be enabled, forcing it to true' |
|
); |
|
if (options.hot === true) { |
|
options.hot = { injectCss: true }; |
|
} else { |
|
options.hot.injectCss = true; |
|
} |
|
} |
|
const css = options.compilerOptions.css; |
|
if (!(css === true || css === 'injected')) { |
|
const forcedCss = 'injected'; |
|
log.warn( |
|
`hmr with emitCss disabled requires compilerOptions.css to be enabled, forcing it to ${forcedCss}` |
|
); |
|
options.compilerOptions.css = forcedCss; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
function enforceOptionsForProduction(options) { |
|
if (options.isProduction) { |
|
if (options.hot) { |
|
log.warn('options.hot is enabled but does not work on production build, forcing it to false'); |
|
options.hot = false; |
|
} |
|
if (options.compilerOptions.dev) { |
|
log.warn( |
|
'you are building for production but compilerOptions.dev is true, forcing it to false' |
|
); |
|
options.compilerOptions.dev = false; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
function removeIgnoredOptions(options) { |
|
const ignoredCompilerOptions = ['generate', 'format', 'filename']; |
|
if (options.hot && options.emitCss) { |
|
ignoredCompilerOptions.push('cssHash'); |
|
} |
|
const passedCompilerOptions = Object.keys(options.compilerOptions || {}); |
|
const passedIgnored = passedCompilerOptions.filter((o) => ignoredCompilerOptions.includes(o)); |
|
if (passedIgnored.length) { |
|
log.warn( |
|
`The following Svelte compilerOptions are controlled by vite-plugin-svelte and essential to its functionality. User-specified values are ignored. Please remove them from your configuration: ${passedIgnored.join( |
|
', ' |
|
)}` |
|
); |
|
passedIgnored.forEach((ignored) => { |
|
|
|
delete options.compilerOptions[ignored]; |
|
}); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
function handleDeprecatedOptions(options) { |
|
const experimental = (options.experimental); |
|
if (experimental) { |
|
for (const promoted of ['prebundleSvelteLibraries', 'inspector', 'dynamicCompileOptions']) { |
|
if (experimental[promoted]) { |
|
|
|
options[promoted] = experimental[promoted]; |
|
delete experimental[promoted]; |
|
log.warn( |
|
`Option "experimental.${promoted}" is no longer experimental and has moved to "${promoted}". Please update your Svelte or Vite config.` |
|
); |
|
} |
|
} |
|
if (experimental.generateMissingPreprocessorSourcemaps) { |
|
log.warn('experimental.generateMissingPreprocessorSourcemaps has been removed.'); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resolveViteRoot(viteConfig) { |
|
return normalizePath(viteConfig.root ? path.resolve(viteConfig.root) : process.cwd()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function buildExtraViteConfig(options, config) { |
|
|
|
|
|
if (!config.resolve) { |
|
config.resolve = {}; |
|
} |
|
config.resolve.mainFields = [ |
|
...SVELTE_RESOLVE_MAIN_FIELDS, |
|
...(config.resolve.mainFields ?? VITE_RESOLVE_MAIN_FIELDS) |
|
]; |
|
|
|
|
|
const extraViteConfig = { |
|
resolve: { |
|
dedupe: [...SVELTE_IMPORTS, ...SVELTE_HMR_IMPORTS], |
|
conditions: [...SVELTE_EXPORT_CONDITIONS] |
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
const extraSvelteConfig = buildExtraConfigForSvelte(config); |
|
const extraDepsConfig = await buildExtraConfigForDependencies(options, config); |
|
|
|
extraViteConfig.optimizeDeps = { |
|
include: [ |
|
...extraSvelteConfig.optimizeDeps.include, |
|
...extraDepsConfig.optimizeDeps.include.filter( |
|
(dep) => !isDepExcluded(dep, extraSvelteConfig.optimizeDeps.exclude) |
|
) |
|
], |
|
exclude: [ |
|
...extraSvelteConfig.optimizeDeps.exclude, |
|
...extraDepsConfig.optimizeDeps.exclude.filter( |
|
(dep) => !isDepIncluded(dep, extraSvelteConfig.optimizeDeps.include) |
|
) |
|
] |
|
}; |
|
|
|
extraViteConfig.ssr = { |
|
external: [ |
|
...extraSvelteConfig.ssr.external, |
|
...extraDepsConfig.ssr.external.filter( |
|
(dep) => !isDepNoExternaled(dep, extraSvelteConfig.ssr.noExternal) |
|
) |
|
], |
|
noExternal: [ |
|
...extraSvelteConfig.ssr.noExternal, |
|
...extraDepsConfig.ssr.noExternal.filter( |
|
(dep) => !isDepExternaled(dep, extraSvelteConfig.ssr.external) |
|
) |
|
] |
|
}; |
|
|
|
|
|
if (options.prebundleSvelteLibraries) { |
|
extraViteConfig.optimizeDeps = { |
|
...extraViteConfig.optimizeDeps, |
|
|
|
|
|
extensions: options.extensions ?? ['.svelte'], |
|
|
|
|
|
|
|
esbuildOptions: { |
|
plugins: [{ name: facadeEsbuildSveltePluginName, setup: () => {} }] |
|
} |
|
}; |
|
} |
|
|
|
|
|
if ( |
|
(options.hot == null || |
|
options.hot === true || |
|
(options.hot && options.hot.partialAccept !== false)) && |
|
config.experimental?.hmrPartialAccept !== false |
|
) { |
|
log.debug('enabling "experimental.hmrPartialAccept" in vite config', undefined, 'config'); |
|
extraViteConfig.experimental = { hmrPartialAccept: true }; |
|
} |
|
validateViteConfig(extraViteConfig, config, options); |
|
return extraViteConfig; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function validateViteConfig(extraViteConfig, config, options) { |
|
const { prebundleSvelteLibraries, isBuild } = options; |
|
if (prebundleSvelteLibraries) { |
|
|
|
const isEnabled = (option) => option !== true && option !== (isBuild ? 'build' : 'dev'); |
|
|
|
const logWarning = (name, value, recommendation) => |
|
log.warn.once( |
|
`Incompatible options: \`prebundleSvelteLibraries: true\` and vite \`${name}: ${JSON.stringify( |
|
value |
|
)}\` ${isBuild ? 'during build.' : '.'} ${recommendation}` |
|
); |
|
const viteOptimizeDepsDisabled = config.optimizeDeps?.disabled ?? 'build'; |
|
const isOptimizeDepsEnabled = isEnabled(viteOptimizeDepsDisabled); |
|
if (!isBuild && !isOptimizeDepsEnabled) { |
|
logWarning( |
|
'optimizeDeps.disabled', |
|
viteOptimizeDepsDisabled, |
|
'Forcing `optimizeDeps.disabled: "build"`. Disable prebundleSvelteLibraries or update your vite config to enable optimizeDeps during dev.' |
|
); |
|
if (!extraViteConfig.optimizeDeps) { |
|
extraViteConfig.optimizeDeps = {}; |
|
} |
|
extraViteConfig.optimizeDeps.disabled = 'build'; |
|
} else if (isBuild && isOptimizeDepsEnabled) { |
|
logWarning( |
|
'optimizeDeps.disabled', |
|
viteOptimizeDepsDisabled, |
|
'Disable optimizeDeps or prebundleSvelteLibraries for build if you experience errors.' |
|
); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async function buildExtraConfigForDependencies(options, config) { |
|
|
|
const packagesWithoutSvelteExportsCondition = new Set(); |
|
const depsConfig = await crawlFrameworkPkgs({ |
|
root: options.root, |
|
isBuild: options.isBuild, |
|
viteUserConfig: config, |
|
isFrameworkPkgByJson(pkgJson) { |
|
let hasSvelteCondition = false; |
|
if (typeof pkgJson.exports === 'object') { |
|
|
|
JSON.stringify(pkgJson.exports, (key, value) => { |
|
if (SVELTE_EXPORT_CONDITIONS.includes(key)) { |
|
hasSvelteCondition = true; |
|
} |
|
return value; |
|
}); |
|
} |
|
const hasSvelteField = !!pkgJson.svelte; |
|
if (hasSvelteField && !hasSvelteCondition) { |
|
packagesWithoutSvelteExportsCondition.add(`${pkgJson.name}@${pkgJson.version}`); |
|
} |
|
return hasSvelteCondition || hasSvelteField; |
|
}, |
|
isSemiFrameworkPkgByJson(pkgJson) { |
|
return !!pkgJson.dependencies?.svelte || !!pkgJson.peerDependencies?.svelte; |
|
}, |
|
isFrameworkPkgByName(pkgName) { |
|
const isNotSveltePackage = isCommonDepWithoutSvelteField(pkgName); |
|
if (isNotSveltePackage) { |
|
return false; |
|
} else { |
|
return undefined; |
|
} |
|
} |
|
}); |
|
if ( |
|
!options.experimental?.disableSvelteResolveWarnings && |
|
packagesWithoutSvelteExportsCondition?.size > 0 |
|
) { |
|
log.warn( |
|
`WARNING: The following packages have a svelte field in their package.json but no exports condition for svelte.\n\n${[ |
|
...packagesWithoutSvelteExportsCondition |
|
].join('\n')}\n\nPlease see ${FAQ_LINK_MISSING_EXPORTS_CONDITION} for details.` |
|
); |
|
} |
|
log.debug('extra config for dependencies generated by vitefu', depsConfig, 'config'); |
|
|
|
if (options.prebundleSvelteLibraries) { |
|
|
|
depsConfig.optimizeDeps.exclude = []; |
|
|
|
const userExclude = config.optimizeDeps?.exclude; |
|
depsConfig.optimizeDeps.include = !userExclude |
|
? [] |
|
: depsConfig.optimizeDeps.include.filter((dep) => { |
|
|
|
|
|
return ( |
|
dep.includes('>') && |
|
dep |
|
.split('>') |
|
.slice(0, -1) |
|
.some((d) => isDepExcluded(d.trim(), userExclude)) |
|
); |
|
}); |
|
} |
|
if (options.disableDependencyReinclusion === true) { |
|
depsConfig.optimizeDeps.include = depsConfig.optimizeDeps.include.filter( |
|
(dep) => !dep.includes('>') |
|
); |
|
} else if (Array.isArray(options.disableDependencyReinclusion)) { |
|
const disabledDeps = options.disableDependencyReinclusion; |
|
depsConfig.optimizeDeps.include = depsConfig.optimizeDeps.include.filter((dep) => { |
|
if (!dep.includes('>')) return true; |
|
const trimDep = dep.replace(/\s+/g, ''); |
|
return disabledDeps.some((disabled) => trimDep.includes(`${disabled}>`)); |
|
}); |
|
} |
|
|
|
log.debug('post-processed extra config for dependencies', depsConfig, 'config'); |
|
|
|
return depsConfig; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function buildExtraConfigForSvelte(config) { |
|
|
|
|
|
const include = []; |
|
const exclude = ['svelte-hmr']; |
|
if (!isDepExcluded('svelte', config.optimizeDeps?.exclude ?? [])) { |
|
const svelteImportsToInclude = SVELTE_IMPORTS.filter((x) => x !== 'svelte/ssr'); |
|
log.debug( |
|
`adding bare svelte packages to optimizeDeps.include: ${svelteImportsToInclude.join(', ')} `, |
|
undefined, |
|
'config' |
|
); |
|
include.push(...svelteImportsToInclude); |
|
} else { |
|
log.debug( |
|
'"svelte" is excluded in optimizeDeps.exclude, skipped adding it to include.', |
|
undefined, |
|
'config' |
|
); |
|
} |
|
|
|
const noExternal = []; |
|
|
|
const external = []; |
|
|
|
|
|
if (!isDepExternaled('svelte', config.ssr?.external ?? [])) { |
|
noExternal.push('svelte', /^svelte\//); |
|
} |
|
return { optimizeDeps: { include, exclude }, ssr: { noExternal, external } }; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function patchResolvedViteConfig(viteConfig, options) { |
|
if (options.preprocess) { |
|
for (const preprocessor of arraify(options.preprocess)) { |
|
if (preprocessor.style && '__resolvedConfig' in preprocessor.style) { |
|
preprocessor.style.__resolvedConfig = viteConfig; |
|
} |
|
} |
|
} |
|
|
|
|
|
const facadeEsbuildSveltePlugin = viteConfig.optimizeDeps.esbuildOptions?.plugins?.find( |
|
(plugin) => plugin.name === facadeEsbuildSveltePluginName |
|
); |
|
if (facadeEsbuildSveltePlugin) { |
|
Object.assign(facadeEsbuildSveltePlugin, esbuildSveltePlugin(options)); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function arraify(value) { |
|
return Array.isArray(value) ? value : [value]; |
|
} |
|
|