From e385986acaa138f48121172f9f98f1053f35ad70 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Thu, 29 Apr 2021 15:46:48 +0200 Subject: [PATCH] Svelte: refactor many symbols and safe eval backend calls arguments Also allow dumping function contents by outputing them directly --- src/frontend/SvelteViewEngine.ts | 115 ++++++++++++++++--------------- views/home.svelte | 17 ++--- views/home_dep.svelte | 16 +++-- views/layouts/svelte_layout.html | 11 ++- 4 files changed, 86 insertions(+), 73 deletions(-) diff --git a/src/frontend/SvelteViewEngine.ts b/src/frontend/SvelteViewEngine.ts index e4fdde1..e315f92 100644 --- a/src/frontend/SvelteViewEngine.ts +++ b/src/frontend/SvelteViewEngine.ts @@ -12,8 +12,9 @@ import {CssResult} from "svelte/types/compiler/interfaces"; import * as child_process from "child_process"; import fs from "fs"; -const BACKEND_CODE_PREFIX = 'swaf.'; -const COMPILED_SVELTE_EXTENSION = '.swafview'; +const BACKEND_CODE_PREFIX = 'locals.'; +const BACKEND_CODE_PREFIX_TEMPORARY_HOLDER = '$$locals$$'; +const COMPILED_SVELTE_EXTENSION = '.precompiled'; export default class SvelteViewEngine extends ViewEngine { public static getPreCompileSeparator(canonicalViewName: string): string { @@ -27,9 +28,9 @@ export default class SvelteViewEngine extends ViewEngine { private readonly fileCache: FileCache = new FileCache(); private readonly dependencyCache: Record> = {}; - private readonly backendCodeCache: Record = {}; private rollup?: child_process.ChildProcess; @@ -76,47 +77,48 @@ export default class SvelteViewEngine extends ViewEngine { // Pre-compiled parts const [ - backendLines, + backendCalls, head, html, css, ] = view.split(SvelteViewEngine.getPreCompileSeparator(canonicalViewName)); const localMap: Record = {}; - backendLines.split('\n').forEach(line => { - const key = line.substring(1, line.indexOf(',') >= 0 ? line.indexOf(',') - 1 : line.length - 1); - if (line.indexOf('[') >= 0) { - const args = line.substring(line.indexOf('[') + 1, line.length - 1) + backendCalls.split('\n').forEach(code => { + const key = code.substring(1, code.indexOf(',') >= 0 ? code.indexOf(',') - 1 : code.length - 1); + if (code.indexOf('`[') >= 0) { + const args = code.substring(code.indexOf('`[') + 2, code.length - 2) .split(/, *?/) .map(arg => { if (arg.startsWith("'")) return '"' + arg.substring(1, arg.length - 1) + '"'; return arg; }) - .map(arg => JSON.parse(arg)); + .map(arg => Function(`"use strict";const locals = arguments[0];return (${arg});`)(locals)); // Uses named parameter locals const f = locals[key]; if (typeof f !== 'function') throw new Error(key + ' is not a function.'); - localMap[`'${key}', ${JSON.stringify(args)}`] = f.call(locals, ...args); + localMap[`'${key}', \`[${code.substring(code.indexOf('`[') + 2, code.length - 2)}]\``] = f.call(locals, ...args); } else { localMap[`'${key}'`] = locals[key]; } }); - const props = JSON.stringify(localMap); + const actualLocals = JSON.stringify(localMap, (key, value) => { + return typeof value === 'function' ? + value.toString() : + value; + }); // Replaces const replaceMap: Record = { canonicalViewName: canonicalViewName, - props: props, + locals: actualLocals, head: head, html: html, css: css, }; return rawOutput.replace( new RegExp(Object.keys(replaceMap).map(str => `%${str}%`).join('|'), 'g'), - (substring) => { - console.log(substring); - return replaceMap[substring.slice(1, substring.length - 1)]; - }, + (substring) => replaceMap[substring.slice(1, substring.length - 1)], ); } @@ -132,20 +134,20 @@ export default class SvelteViewEngine extends ViewEngine { logger.info(canonicalName + ' > ', 'Pre-compiling', file, '->', intermediateFile); const source = await this.fileCache.get(file, !config.get('view.cache')); - const allBackendLines: string[] = []; + const allBackendCalls: string[] = []; for (const dependency of this.resolveDependencies(source, canonicalName)) { - allBackendLines.push(...(await this.replaceBackendCode(dependency)).backendLines); + allBackendCalls.push(...(await this.replaceBackendCalls(dependency)).backendCalls); } - const {backendReplacedCode, backendLines} = await this.replaceBackendCode(canonicalName, source); - allBackendLines.push(...backendLines); + const {replacedBackendCall, backendCalls} = await this.replaceBackendCalls(canonicalName, source); + allBackendCalls.push(...backendCalls); // Server Side Render (initial HTML and CSS, no-js) - const ssr = await this.compileSsr(canonicalName, intermediateFile, backendReplacedCode); + const ssr = await this.compileSsr(canonicalName, intermediateFile, replacedBackendCall); const separator = SvelteViewEngine.getPreCompileSeparator(canonicalName); const finalCode = [ - [...new Set(allBackendLines).values()].join('\n'), + [...new Set(allBackendCalls).values()].join('\n'), ssr.head, ssr.html, ssr.css.code, @@ -186,13 +188,13 @@ export default class SvelteViewEngine extends ViewEngine { return dependencies; } - private async replaceBackendCode(canonicalViewName: string, code?: string): Promise<{ - backendReplacedCode: string, - backendLines: string[], + private async replaceBackendCalls(canonicalViewName: string, code?: string): Promise<{ + replacedBackendCall: string, + backendCalls: string[], }> { // Cache - if (Object.keys(this.backendCodeCache).indexOf(canonicalViewName) >= 0) { - return this.backendCodeCache[canonicalViewName]; + if (Object.keys(this.backendCallsCache).indexOf(canonicalViewName) >= 0) { + return this.backendCallsCache[canonicalViewName]; } // mkdir output file dir @@ -206,19 +208,19 @@ export default class SvelteViewEngine extends ViewEngine { } // Skip replace if there is no swaf export - if (!code.match(/export[ \n]+let[ \n]+swaf[ \n]*=[ \n]*{[ \n]*}/)) { + if (!code.match(/export[ \n]+let[ \n]+locals[ \n]*=[ \n]*{[ \n]*}/)) { const generated = { - backendReplacedCode: code, - backendLines: [], + replacedBackendCall: code, + backendCalls: [], }; - await afs.writeFile(outputFile, generated.backendReplacedCode); - this.backendCodeCache[canonicalViewName] = generated; + await afs.writeFile(outputFile, generated.replacedBackendCall); + this.backendCallsCache[canonicalViewName] = generated; return generated; } let output = code; - const backendLines = new Set(); + const backendCalls = new Set(); let index = 0; while ((index = output.indexOf(BACKEND_CODE_PREFIX, index + 1)) >= 0) { @@ -247,31 +249,32 @@ export default class SvelteViewEngine extends ViewEngine { endIndex++; } - let backendLine = output.substring(startIndex, endIndex); - if (backendLine.match(/([^()]+)\((.+?)\)/)) { - backendLine = backendLine.replace(/([^()]+)\((.+?)\)/, "'$1', [$2]"); + let backendCall = output.substring(startIndex, endIndex); + if (backendCall.match(/([^()]+)\((.+?)\)/)) { + backendCall = backendCall.replace(/([^()]+)\((.+?)\)/, "'$1', `[$2]`"); } else { - backendLine = backendLine.replace(/([^()]+)/, "'$1'"); + backendCall = backendCall.replace(/([^()]+)/, "'$1'"); } + backendCalls.add(backendCall); - backendLines.add(backendLine); output = output.substring(0, index) + - 'swaf(' + backendLine + ')' + + 'locals(' + backendCall.split(BACKEND_CODE_PREFIX).join(BACKEND_CODE_PREFIX_TEMPORARY_HOLDER) + ')' + output.substring(endIndex, output.length); } + output = output.split(BACKEND_CODE_PREFIX_TEMPORARY_HOLDER).join(BACKEND_CODE_PREFIX); const generated = { - backendReplacedCode: output, - backendLines: [...backendLines], + replacedBackendCall: output, + backendCalls: [...backendCalls], }; - await afs.writeFile(outputFile, generated.backendReplacedCode); - this.backendCodeCache[canonicalViewName] = generated; + await afs.writeFile(outputFile, generated.replacedBackendCall); + this.backendCallsCache[canonicalViewName] = generated; return generated; } public async afterPreCompile(watch: boolean): Promise { - await this.bundle(watch, ...Object.keys(this.backendCodeCache)); + await this.bundle(watch, ...Object.keys(this.backendCallsCache)); } @@ -280,12 +283,12 @@ export default class SvelteViewEngine extends ViewEngine { } public async onFileChange(file: string): Promise { - delete this.backendCodeCache[this.toCanonicalName(file)]; + delete this.backendCallsCache[this.toCanonicalName(file)]; } public async onFileRemove(file: string): Promise { const canonicalName = this.toCanonicalName(file); - delete this.backendCodeCache[canonicalName]; + delete this.backendCallsCache[canonicalName]; delete this.dependencyCache[canonicalName]; Object.values(this.dependencyCache).forEach(set => set.delete(canonicalName)); await this.stopRollup(); @@ -322,14 +325,16 @@ export default class SvelteViewEngine extends ViewEngine { const globals = ViewEngine.getGlobals(); delete require.cache[path.resolve(file)]; - return requireFromString(svelteSsr.js.code, file).default.render({ - swaf: (key: string, args?: unknown[]) => { - if (!args) return globals[key]; + const localsFunction = (key: string, rawArgs?: string) => { + if (!rawArgs) return globals[key]; + const args = Function(`"use strict";const locals = Object.assign(arguments[0], arguments[1]);return (${rawArgs});`)(localsFunction, globals) as string[]; - const f = globals[key]; - if (typeof f !== 'function') throw new Error(key + ' is not a function.'); - return f.call(globals, ...args); - }, + const f = globals[key]; + if (typeof f !== 'function') throw new Error(key + ' is not a function.'); + return f.call(globals, ...args); + }; + return requireFromString(svelteSsr.js.code, file).default.render({ + locals: localsFunction, }); } diff --git a/views/home.svelte b/views/home.svelte index 90d702e..170a791 100644 --- a/views/home.svelte +++ b/views/home.svelte @@ -2,13 +2,14 @@ import HomeDep from "./home_dep.svelte"; import Layout from "./layout.svelte"; - export let swaf = {}; + export let locals = {}; let count = 5; function handleClick() { count++; } + let depTest; @@ -34,24 +35,24 @@ -

Direct access: {swaf.direct}

+

Direct access: {locals.direct}

-{#if swaf.route('auth') === '/'} +{#if locals.route('auth') === '/'} We're home! {:else} - We're somewhere else... {swaf.route('auth')} + We're somewhere else... {locals.route('auth')} {/if} -

The route to auth is {swaf.route('auth')}

+

The route to auth is {locals.route('auth')}

-

\swaf.notcode

+

\locals.notcode

-

{`{\\swaf.escaped}`}

+

{`{\\locals.escaped}`}

Blue!

- +

Dependency test: {depTest}

diff --git a/views/home_dep.svelte b/views/home_dep.svelte index c258474..68cc9b2 100644 --- a/views/home_dep.svelte +++ b/views/home_dep.svelte @@ -1,6 +1,8 @@