Svelte: write fully preprocessed code to pre-compiled files

This commit is contained in:
Alice Gaudon 2021-05-11 13:52:48 +02:00
parent e95595f5a3
commit 42f7ebba05

View File

@ -30,10 +30,7 @@ export default class SvelteViewEngine extends ViewEngine {
private readonly fileCache: FileCache = new FileCache(); private readonly fileCache: FileCache = new FileCache();
private readonly dependencyCache: Record<string, Set<string>> = {}; private readonly dependencyCache: Record<string, Set<string>> = {};
private readonly backendCallsCache: Record<string, { private readonly preprocessingCache: Record<string, PreprocessingCacheEntry> = {};
replacedBackendCall: string,
backendCalls: string[],
}> = {};
public constructor( public constructor(
targetDir: string, targetDir: string,
@ -43,12 +40,12 @@ export default class SvelteViewEngine extends ViewEngine {
} }
public async onFileChange(file: string): Promise<void> { public async onFileChange(file: string): Promise<void> {
delete this.backendCallsCache[this.toCanonicalName(file)]; delete this.preprocessingCache[this.toCanonicalName(file)];
} }
public async onFileRemove(file: string): Promise<void> { public async onFileRemove(file: string): Promise<void> {
const canonicalName = this.toCanonicalName(file); const canonicalName = this.toCanonicalName(file);
delete this.backendCallsCache[canonicalName]; delete this.preprocessingCache[canonicalName];
delete this.dependencyCache[canonicalName]; delete this.dependencyCache[canonicalName];
Object.values(this.dependencyCache).forEach(set => set.delete(canonicalName)); Object.values(this.dependencyCache).forEach(set => set.delete(canonicalName));
await super.onFileRemove(file); await super.onFileRemove(file);
@ -124,14 +121,14 @@ export default class SvelteViewEngine extends ViewEngine {
const allBackendCalls: string[] = []; const allBackendCalls: string[] = [];
for (const dependency of this.resolveDependencies(source, canonicalName)) { for (const dependency of this.resolveDependencies(source, canonicalName)) {
allBackendCalls.push(...(await this.replaceBackendCalls(dependency)).backendCalls); allBackendCalls.push(...(await this.preprocess(dependency)).backendCalls);
} }
const {replacedBackendCall, backendCalls} = await this.replaceBackendCalls(canonicalName, source); const {backendCalls, code} = await this.preprocess(canonicalName, source);
allBackendCalls.push(...backendCalls); allBackendCalls.push(...backendCalls);
// Server Side Render (initial HTML and CSS, no-js) // Server Side Render (initial HTML and CSS, no-js)
const ssr = await this.compileSsr(canonicalName, intermediateFile, replacedBackendCall); const ssr = await this.compileSsr(canonicalName, intermediateFile, code);
const separator = SvelteViewEngine.getPreCompileSeparator(canonicalName); const separator = SvelteViewEngine.getPreCompileSeparator(canonicalName);
const finalCode = [ const finalCode = [
@ -176,34 +173,59 @@ export default class SvelteViewEngine extends ViewEngine {
return dependencies; return dependencies;
} }
private async replaceBackendCalls(canonicalViewName: string, code?: string): Promise<{ private async preprocess(canonicalName: string, code?: string): Promise<PreprocessingCacheEntry> {
replacedBackendCall: string,
backendCalls: string[],
}> {
// Cache // Cache
if (Object.keys(this.backendCallsCache).indexOf(canonicalViewName) >= 0) { if (Object.keys(this.preprocessingCache).indexOf(canonicalName) >= 0) {
return this.backendCallsCache[canonicalViewName]; return this.preprocessingCache[canonicalName];
} }
const file = await this.resolveFileFromCanonicalName(canonicalName);
logger.info(canonicalName + ' > ', `Preprocessing ${file}`);
// mkdir output file dir // mkdir output file dir
const outputFile = path.join(this.targetDir, canonicalViewName); const outputFile = path.join(this.targetDir, canonicalName);
await fs.mkdir(path.dirname(outputFile), {recursive: true}); await fs.mkdir(path.dirname(outputFile), {recursive: true});
// Read source file if code was not already provided // Read source file if code was not already provided
if (!code) { if (!code) {
const file = await this.resolveFileFromCanonicalName(canonicalViewName);
code = await this.fileCache.get(file, !config.get<boolean>('view.cache')); code = await this.fileCache.get(file, !config.get<boolean>('view.cache'));
} }
// Replace backend calls
const replacedBackendCalls = await this.replaceBackendCalls(canonicalName, code);
// Preprocess svelte
logger.info(canonicalName + ' > ', 'Svelte preprocessing');
const preprocessed = await preprocess(
replacedBackendCalls.code,
sveltePreprocess({
typescript: {
tsconfigFile: 'tsconfig.svelte.json',
},
}),
{
filename: outputFile,
},
);
// Write to output file
await fs.writeFile(outputFile, preprocessed.code);
return this.preprocessingCache[canonicalName] = {
backendCalls: replacedBackendCalls.backendCalls,
code: preprocessed.code,
};
}
private async replaceBackendCalls(canonicalName: string, code: string): Promise<PreprocessingCacheEntry> {
logger.info(canonicalName + ' > ', 'Replacing backend calls');
// Skip replace if there is no swaf export // Skip replace if there is no swaf export
if (!code.match(/export[ \n]+let[ \n]+locals[ \n]*=[ \n]*{[ \n]*}/)) { if (!code.match(/export[ \n]+let[ \n]+locals[ \n]*=[ \n]*{[ \n]*}/)) {
const generated = { return {
replacedBackendCall: code,
backendCalls: [], backendCalls: [],
code: code,
}; };
await fs.writeFile(outputFile, generated.replacedBackendCall);
this.backendCallsCache[canonicalViewName] = generated;
return generated;
} }
@ -251,14 +273,10 @@ export default class SvelteViewEngine extends ViewEngine {
} }
output = output.split(BACKEND_CODE_PREFIX_TEMPORARY_HOLDER).join(BACKEND_CODE_PREFIX); output = output.split(BACKEND_CODE_PREFIX_TEMPORARY_HOLDER).join(BACKEND_CODE_PREFIX);
const generated = { return {
replacedBackendCall: output,
backendCalls: [...backendCalls], backendCalls: [...backendCalls],
code: output,
}; };
await fs.writeFile(outputFile, generated.replacedBackendCall);
this.backendCallsCache[canonicalViewName] = generated;
return generated;
} }
private async compileSsr(canonicalName: string, file: string, code: string): Promise<{ private async compileSsr(canonicalName: string, file: string, code: string): Promise<{
@ -266,23 +284,9 @@ export default class SvelteViewEngine extends ViewEngine {
css: CssResult, css: CssResult,
html: string, html: string,
}> { }> {
// Svelte preprocess
logger.info(canonicalName + ' > ', 'Preprocessing svelte', file);
const preprocessed = await preprocess(
code,
sveltePreprocess({
typescript: {
tsconfigFile: 'tsconfig.svelte.json',
},
}),
{
filename: file,
},
);
// Svelte compile // Svelte compile
logger.info(canonicalName + ' > ', 'Compiling svelte ssr', file); logger.info(canonicalName + ' > ', 'Compiling svelte ssr', file);
const svelteSsr = compile(preprocessed.code, { const svelteSsr = compile(code, {
dev: config.get<boolean>('view.dev'), dev: config.get<boolean>('view.dev'),
generate: 'ssr', generate: 'ssr',
format: 'cjs', format: 'cjs',
@ -299,3 +303,7 @@ export default class SvelteViewEngine extends ViewEngine {
} }
} }
type PreprocessingCacheEntry = {
backendCalls: string[],
code: string,
};