File size: 3,689 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { isCSSRequest, preprocessCSS, resolveConfig, transformWithEsbuild } from 'vite';
import { mapToRelative, removeLangSuffix } from './utils/sourcemaps.js';

/**
 * @typedef {(code: string, filename: string) => Promise<{ code: string; map?: any; deps?: Set<string> }>} CssTransform
 */

const supportedScriptLangs = ['ts'];

export const lang_sep = '.vite-preprocess';

/**
 * @param {import('./public.d.ts').VitePreprocessOptions} [opts]
 * @returns {import('svelte/compiler').PreprocessorGroup}
 */
export function vitePreprocess(opts) {
	/** @type {import('svelte/compiler').PreprocessorGroup} */
	const preprocessor = { name: 'vite-preprocess' };
	if (opts?.script !== false) {
		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: import('svelte/compiler').Preprocessor }}
 */
function viteScript() {
	return {
		async script({ attributes, content, filename = '' }) {
			const lang = /** @type {string} */ (attributes.lang);
			if (!supportedScriptLangs.includes(lang)) return;
			const { code, map } = await transformWithEsbuild(content, filename, {
				loader: /** @type {import('vite').ESBuildOptions['loader']} */ (lang),
				target: 'esnext',
				tsconfigRaw: {
					compilerOptions: {
						// svelte typescript needs this flag to work with type imports
						importsNotUsedAsValues: 'preserve',
						preserveValueImports: true
					}
				}
			});

			mapToRelative(map, filename);

			return {
				code,
				map
			};
		}
	};
}

/**
 * @param {import('vite').ResolvedConfig | import('vite').InlineConfig} config
 * @returns {{ style: import('svelte/compiler').Preprocessor }}
 */
function viteStyle(config = {}) {
	/** @type {Promise<CssTransform> | CssTransform} */
	let cssTransform;
	/** @type {import('svelte/compiler').Preprocessor} */
	const style = 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 {import('svelte/compiler').Preprocessor} style
 * @param {import('vite').ResolvedConfig | import('vite').InlineConfig} config
 * @returns {Promise<CssTransform>}
 */
async function createCssTransform(style, config) {
	/** @type {import('vite').ResolvedConfig} */
	let resolvedConfig;
	// @ts-expect-error special prop added if running in v-p-s
	if (style.__resolvedConfig) {
		// @ts-expect-error
		resolvedConfig = style.__resolvedConfig;
	} else if (isResolvedConfig(config)) {
		resolvedConfig = config;
	} else {
		resolvedConfig = await resolveConfig(
			config,
			process.env.NODE_ENV === 'production' ? 'build' : 'serve'
		);
	}
	return async (code, filename) => {
		return preprocessCSS(code, filename, resolvedConfig);
	};
}

/**
 * @param {any} config
 * @returns {config is import('vite').ResolvedConfig}
 */
function isResolvedConfig(config) {
	return !!config.inlineConfig;
}