File size: 7,298 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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
import * as svelte from 'svelte/compiler';
// @ts-ignore
import { createMakeHot } from 'svelte-hmr';
import { safeBase64Hash } from './hash.js';
import { log } from './log.js';
import {
checkPreprocessDependencies,
createInjectScopeEverythingRulePreprocessorGroup
} from './preprocess.js';
import { mapToRelative } from './sourcemaps.js';
import { enhanceCompileError } from './error.js';
import { isSvelte5 } from './svelte-version.js';
// TODO this is a patched version of https://github.com/sveltejs/vite-plugin-svelte/pull/796/files#diff-3bce0b33034aad4b35ca094893671f7e7ddf4d27254ae7b9b0f912027a001b15R10
// which is closer to the other regexes in at least not falling into commented script
// but ideally would be shared exactly with svelte and other tools that use it
const scriptLangRE =
/<!--[^]*?-->|<script (?:[^>]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=["']?([^"' >]+)["']?[^>]*>/g;
/**
* @param {Function} [makeHot]
* @returns {import('../types/compile.d.ts').CompileSvelte}
*/
export const _createCompileSvelte = (makeHot) => {
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
let stats;
const devStylePreprocessor = createInjectScopeEverythingRulePreprocessorGroup();
/** @type {import('../types/compile.d.ts').CompileSvelte} */
return async function compileSvelte(svelteRequest, code, options) {
const { filename, normalizedFilename, cssId, ssr, raw } = svelteRequest;
const { emitCss = true } = options;
/** @type {string[]} */
const dependencies = [];
/** @type {import('svelte/types/compiler/interfaces').Warning[]} */
const warnings = [];
if (options.stats) {
if (options.isBuild) {
if (!stats) {
// build is either completely ssr or csr, create stats collector on first compile
// it is then finished in the buildEnd hook.
stats = options.stats.startCollection(`${ssr ? 'ssr' : 'dom'} compile`, {
logInProgress: () => false
});
}
} else {
// dev time ssr, it's a ssr request and there are no stats, assume new page load and start collecting
if (ssr && !stats) {
stats = options.stats.startCollection('ssr compile');
}
// stats are being collected but this isn't an ssr request, assume page loaded and stop collecting
if (!ssr && stats) {
stats.finish();
stats = undefined;
}
// TODO find a way to trace dom compile during dev
// problem: we need to call finish at some point but have no way to tell if page load finished
// also they for hmr updates too
}
}
/** @type {import('svelte/compiler').CompileOptions} */
const compileOptions = {
...options.compilerOptions,
filename,
// @ts-expect-error svelte5 uses server/client, svelte4 uses ssr/dom
generate: isSvelte5 ? (ssr ? 'server' : 'client') : ssr ? 'ssr' : 'dom'
};
if (options.hot && options.emitCss) {
const hash = `s-${safeBase64Hash(normalizedFilename)}`;
compileOptions.cssHash = () => hash;
}
let preprocessed;
let preprocessors = options.preprocess;
if (!options.isBuild && options.emitCss && options.hot) {
// inject preprocessor that ensures css hmr works better
if (!Array.isArray(preprocessors)) {
preprocessors = preprocessors
? [preprocessors, devStylePreprocessor]
: [devStylePreprocessor];
} else {
preprocessors = preprocessors.concat(devStylePreprocessor);
}
}
if (preprocessors) {
try {
preprocessed = await svelte.preprocess(code, preprocessors, { filename }); // full filename here so postcss works
} catch (e) {
e.message = `Error while preprocessing ${filename}${e.message ? ` - ${e.message}` : ''}`;
throw e;
}
if (preprocessed.dependencies?.length) {
const checked = checkPreprocessDependencies(filename, preprocessed.dependencies);
if (checked.warnings.length) {
warnings.push(...checked.warnings);
}
if (checked.dependencies.length) {
dependencies.push(...checked.dependencies);
}
}
if (preprocessed.map) compileOptions.sourcemap = preprocessed.map;
}
if (typeof preprocessed?.map === 'object') {
mapToRelative(preprocessed?.map, filename);
}
if (raw && svelteRequest.query.type === 'preprocessed') {
// @ts-expect-error shortcut
return /** @type {import('../types/compile.d.ts').CompileData} */ {
preprocessed: preprocessed ?? { code }
};
}
const finalCode = preprocessed ? preprocessed.code : code;
const dynamicCompileOptions = await options?.dynamicCompileOptions?.({
filename,
code: finalCode,
compileOptions
});
if (dynamicCompileOptions && log.debug.enabled) {
log.debug(
`dynamic compile options for ${filename}: ${JSON.stringify(dynamicCompileOptions)}`,
undefined,
'compile'
);
}
const finalCompileOptions = dynamicCompileOptions
? {
...compileOptions,
...dynamicCompileOptions
}
: compileOptions;
const endStat = stats?.start(filename);
/** @type {import('svelte/types/compiler/interfaces').CompileResult} */
let compiled;
try {
compiled = svelte.compile(finalCode, finalCompileOptions);
} catch (e) {
enhanceCompileError(e, code, preprocessors);
throw e;
}
if (endStat) {
endStat();
}
mapToRelative(compiled.js?.map, filename);
mapToRelative(compiled.css?.map, filename);
if (warnings.length) {
if (!compiled.warnings) {
compiled.warnings = [];
}
compiled.warnings.push(...warnings);
}
if (!raw) {
// wire css import and code for hmr
const hasCss = compiled.css?.code?.trim().length > 0;
// compiler might not emit css with mode none or it may be empty
if (emitCss && hasCss) {
// TODO properly update sourcemap?
compiled.js.code += `\nimport ${JSON.stringify(cssId)};\n`;
}
// only apply hmr when not in ssr context and hot options are set
if (!ssr && makeHot) {
compiled.js.code = makeHot({
id: filename,
compiledCode: compiled.js.code,
//@ts-expect-error hot isn't a boolean at this point
hotOptions: { ...options.hot, injectCss: options.hot?.injectCss === true && hasCss },
compiled,
originalCode: code,
compileOptions: finalCompileOptions
});
}
}
let lang = 'js';
for (const match of code.matchAll(scriptLangRE)) {
if (match[1]) {
lang = match[1];
break;
}
}
return {
filename,
normalizedFilename,
lang,
// @ts-ignore
compiled,
ssr,
dependencies,
preprocessed: preprocessed ?? { code }
};
};
};
/**
* @param {import('../types/options.d.ts').ResolvedOptions} options
* @returns {Function | undefined}
*/
function buildMakeHot(options) {
const needsMakeHot =
!isSvelte5 && options.hot !== false && options.isServe && !options.isProduction;
if (needsMakeHot) {
// @ts-ignore
const hotApi = options?.hot?.hotApi;
// @ts-ignore
const adapter = options?.hot?.adapter;
return createMakeHot({
walk: svelte.walk,
hotApi,
adapter,
hotOptions: { noOverlay: true, .../** @type {object} */ (options.hot) }
});
}
}
/**
* @param {import('../types/options.d.ts').ResolvedOptions} options
* @returns {import('../types/compile.d.ts').CompileSvelte}
*/
export function createCompileSvelte(options) {
const makeHot = buildMakeHot(options);
return _createCompileSvelte(makeHot);
}
|