@sveltejs/vite-plugin-svelte
Version:
The official [Svelte](https://svelte.dev) plugin for [Vite](https://vitejs.dev).
136 lines (125 loc) • 4.19 kB
JavaScript
/** @import { VitePreprocessOptions } from './public.js' */
/** @import { Preprocessor as SveltePreprocessor, PreprocessorGroup } from 'svelte/compiler' */
/** @import { InlineConfig, ResolvedConfig } from 'vite' */
import process from 'node:process';
import * as vite from 'vite';
import { mapToRelative, removeLangSuffix } from './utils/sourcemaps.js';
const {
isCSSRequest,
preprocessCSS,
resolveConfig,
//@ts-ignore rolldown types don't exist
transformWithOxc
} = vite;
/**
* @typedef {(code: string, filename: string) => Promise<{ code: string; map?: any; deps?: Set<string> }>} CssTransform
*/
const supportedScriptLangs = ['ts'];
export const lang_sep = '.vite-preprocess';
/**
* @param {VitePreprocessOptions} [opts]
* @returns {PreprocessorGroup}
*/
export function vitePreprocess(opts) {
/** @type {PreprocessorGroup} */
const preprocessor = { name: 'vite-preprocess' };
if (opts?.script === true) {
preprocessor.script = viteScript().script;
}
if (opts?.style !== false) {
const styleOpts = typeof opts?.style == 'object' ? opts?.style : undefined;
preprocessor.style = viteStyle(styleOpts).style;
}
return preprocessor;
}
/**
* @returns {{ script: SveltePreprocessor }}
*/
function viteScript() {
return {
async script({ attributes, content, filename = '' }) {
if (typeof attributes.lang !== 'string' || !supportedScriptLangs.includes(attributes.lang)) {
return;
}
const lang = /** @type {'ts'} */ (attributes.lang);
const { code, map } = await transformWithOxc(content, filename, {
lang,
target: 'esnext',
// oxc strips value imports it considers unused, but in Svelte files
// these imports may be referenced in the template which oxc cannot see.
// onlyRemoveTypeImports tells oxc to only strip type-only imports
// (import type {...}, import { type X }) and preserve all value imports.
typescript: { onlyRemoveTypeImports: true }
});
mapToRelative(map, filename);
return {
code,
map
};
}
};
}
/**
* @param {ResolvedConfig | InlineConfig} config
* @returns {{ style: SveltePreprocessor }}
*/
function viteStyle(config = {}) {
/** @type {Promise<CssTransform> | CssTransform} */
let cssTransform;
const style = /** @type {SveltePreprocessor} */ (
async ({ attributes, content, filename = '' }) => {
const ext = attributes.lang ? `.${attributes.lang}` : '.css';
if (attributes.lang && !isCSSRequest(ext)) return;
if (!cssTransform) {
cssTransform = createCssTransform(style, config).then((t) => (cssTransform = t));
}
const transform = await cssTransform;
const suffix = `${lang_sep}${ext}`;
const moduleId = `${filename}${suffix}`;
const { code, map, deps } = await transform(content, moduleId);
removeLangSuffix(map, suffix);
mapToRelative(map, filename);
const dependencies = deps ? Array.from(deps).filter((d) => !d.endsWith(suffix)) : undefined;
return {
code,
map: map ?? undefined,
dependencies
};
}
);
// @ts-expect-error tag so can be found by v-p-s
style.__resolvedConfig = null;
return { style };
}
/**
* @param {SveltePreprocessor} style
* @param {ResolvedConfig | InlineConfig} config
* @returns {Promise<CssTransform>}
*/
async function createCssTransform(style, config) {
/** @type {ResolvedConfig} */
let resolvedConfig;
// @ts-expect-error special prop added if running in v-p-s
if (style.__resolvedConfig) {
// @ts-expect-error not typed
resolvedConfig = style.__resolvedConfig;
} else if (isResolvedConfig(config)) {
resolvedConfig = config;
} else {
// default to "build" if no NODE_ENV is set to avoid running in dev mode for svelte-check etc.
const useBuild = !process.env.NODE_ENV || process.env.NODE_ENV === 'production';
const command = useBuild ? 'build' : 'serve';
const defaultMode = useBuild ? 'production' : 'development';
resolvedConfig = await resolveConfig(config, command, defaultMode, defaultMode, false);
}
return async (code, filename) => {
return preprocessCSS(code, filename, resolvedConfig);
};
}
/**
* @param {any} config
* @returns {config is ResolvedConfig}
*/
function isResolvedConfig(config) {
return !!config.inlineConfig;
}