153 lines
4.7 KiB
JavaScript
153 lines
4.7 KiB
JavaScript
import * as svelte from 'svelte/compiler';
|
|
import { safeBase64Hash } from './hash.js';
|
|
import { log } from './log.js';
|
|
|
|
import { mapToRelative } from './sourcemaps.js';
|
|
import { enhanceCompileError } from './error.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]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/g;
|
|
|
|
/**
|
|
* @returns {import('../types/compile.d.ts').CompileSvelte}
|
|
*/
|
|
export function createCompileSvelte() {
|
|
/** @type {import('../types/vite-plugin-svelte-stats.d.ts').StatCollection | undefined} */
|
|
let stats;
|
|
/** @type {import('../types/compile.d.ts').CompileSvelte} */
|
|
return async function compileSvelte(svelteRequest, code, options, sourcemap) {
|
|
const { filename, normalizedFilename, cssId, ssr, raw } = svelteRequest;
|
|
const { emitCss = true } = options;
|
|
/** @type {import('svelte/compiler').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,
|
|
generate: ssr ? 'server' : 'client'
|
|
};
|
|
|
|
let finalCode = code;
|
|
if (compileOptions.hmr && options.emitCss) {
|
|
const hash = `s-${safeBase64Hash(normalizedFilename)}`;
|
|
compileOptions.cssHash = () => hash;
|
|
const closeStylePos = code.lastIndexOf('</style>');
|
|
if (closeStylePos > -1) {
|
|
// inject rule that forces compile to attach scope class to every node in the template
|
|
// this reduces the amount of js hot updates when editing css in .svelte files
|
|
finalCode = finalCode.slice(0, closeStylePos) + ' *{}' + finalCode.slice(closeStylePos);
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (sourcemap) {
|
|
finalCompileOptions.sourcemap = sourcemap;
|
|
}
|
|
const endStat = stats?.start(filename);
|
|
/** @type {import('svelte/compiler').CompileResult} */
|
|
let compiled;
|
|
try {
|
|
compiled = svelte.compile(finalCode, { ...finalCompileOptions, filename });
|
|
|
|
// patch output with partial accept until svelte does it
|
|
// TODO remove later
|
|
if (
|
|
options.server?.config.experimental.hmrPartialAccept &&
|
|
compiled.js.code.includes('import.meta.hot.accept(')
|
|
) {
|
|
compiled.js.code = compiled.js.code.replaceAll(
|
|
'import.meta.hot.accept(',
|
|
'import.meta.hot.acceptExports(["default"],'
|
|
);
|
|
}
|
|
} catch (e) {
|
|
enhanceCompileError(e, code, options.preprocess);
|
|
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 > 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`;
|
|
}
|
|
}
|
|
|
|
let lang = 'js';
|
|
for (const match of code.matchAll(scriptLangRE)) {
|
|
if (match[2]) {
|
|
lang = match[2];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
filename,
|
|
normalizedFilename,
|
|
cssId,
|
|
lang,
|
|
compiled,
|
|
ssr
|
|
};
|
|
};
|
|
}
|