Add svelte as a view engine to swaf #33

Merged
ashpie merged 97 commits from svelte into develop 2021-11-09 19:31:22 +01:00
3 changed files with 38 additions and 41 deletions
Showing only changes of commit c93ea7691e - Show all commits

View File

@ -12,6 +12,7 @@ export default class FrontendToolsComponent extends ApplicationComponent {
private readonly publicAssetsCache: FileCache = new FileCache(); private readonly publicAssetsCache: FileCache = new FileCache();
public constructor( public constructor(
private readonly publicAssetsDir: string,
private readonly viewEngine: ViewEngine, private readonly viewEngine: ViewEngine,
) { ) {
super(); super();
@ -20,9 +21,9 @@ export default class FrontendToolsComponent extends ApplicationComponent {
public async start(app: Express): Promise<void> { public async start(app: Express): Promise<void> {
// Cache public assets // Cache public assets
if (config.get<boolean>('asset_cache')) { if (config.get<boolean>('asset_cache')) {
logger.info('Caching assets from', this.viewEngine.getPublicDir(), '...'); logger.info('Caching assets from', this.publicAssetsDir, '...');
await readdirRecursively( await readdirRecursively(
this.viewEngine.getPublicDir(), this.publicAssetsDir,
async file => await this.publicAssetsCache.load(file), async file => await this.publicAssetsCache.load(file),
); );
} else { } else {
@ -30,12 +31,7 @@ export default class FrontendToolsComponent extends ApplicationComponent {
} }
// Setup express view engine // Setup express view engine
app.engine(this.viewEngine.getExtension(), (path, options, callback) => { this.viewEngine.setup(app, true);
this.viewEngine.render(path, options as Record<string, unknown>, callback)
.catch(err => callback(err));
});
app.set('views', this.viewEngine.getViewPaths());
app.set('view engine', 'svelte');
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {
@ -45,7 +41,7 @@ export default class FrontendToolsComponent extends ApplicationComponent {
public async handle(router: Router): Promise<void> { public async handle(router: Router): Promise<void> {
router.use((req, res, next) => { router.use((req, res, next) => {
res.locals.inlineAsset = (urlPath: string) => { res.locals.inlineAsset = (urlPath: string) => {
return this.publicAssetsCache.getOrFail(path.join(this.viewEngine.getPublicDir(), urlPath)); return this.publicAssetsCache.getOrFail(path.join(this.publicAssetsDir, urlPath));
}; };
next(); next();

View File

@ -10,6 +10,7 @@ import {sveltePreprocess} from "svelte-preprocess/dist/autoProcess";
import requireFromString from "require-from-string"; import requireFromString from "require-from-string";
import {CssResult} from "svelte/types/compiler/interfaces"; import {CssResult} from "svelte/types/compiler/interfaces";
import * as child_process from "child_process"; import * as child_process from "child_process";
import fs from "fs";
const BACKEND_CODE_PREFIX = 'swaf.'; const BACKEND_CODE_PREFIX = 'swaf.';
const COMPILED_SVELTE_EXTENSION = '.swafview'; const COMPILED_SVELTE_EXTENSION = '.swafview';
@ -33,13 +34,23 @@ export default class SvelteViewEngine extends ViewEngine {
private rollup?: child_process.ChildProcess; private rollup?: child_process.ChildProcess;
/**
* @param buildDir A temporary directory that will contain any non-final or final non-public asset.
* @param publicDir The output directory that should contain all final and public assets.
* @param devWatchedViewDir see {@link ViewEngine}.
* @param additionalViewPaths see {@link ViewEngine}.
*/
public constructor( public constructor(
buildDir: string, private readonly buildDir: string,
publicDir: string, private readonly publicDir: string,
devWatchedViewPath: string, devWatchedViewDir: string,
...additionalViewPaths: string[] ...additionalViewPaths: string[]
) { ) {
super(buildDir, publicDir, devWatchedViewPath, ...additionalViewPaths); super(devWatchedViewDir, ...additionalViewPaths);
if (!fs.existsSync(this.buildDir)) {
fs.mkdirSync(this.buildDir, {recursive: true});
}
} }
public getExtension(): string { public getExtension(): string {
@ -57,7 +68,7 @@ export default class SvelteViewEngine extends ViewEngine {
const canonicalViewName = this.toCanonicalName(file); const canonicalViewName = this.toCanonicalName(file);
// View // View
const actualFile = path.join(this.getBuildDir(), canonicalViewName + COMPILED_SVELTE_EXTENSION); const actualFile = path.join(this.buildDir, canonicalViewName + COMPILED_SVELTE_EXTENSION);
const view = await this.fileCache.get(actualFile, !config.get<boolean>('view.cache')); const view = await this.fileCache.get(actualFile, !config.get<boolean>('view.cache'));
// Root template // Root template
@ -139,7 +150,7 @@ export default class SvelteViewEngine extends ViewEngine {
public async preCompile(canonicalName: string, alsoCompileDependents: boolean): Promise<void> { public async preCompile(canonicalName: string, alsoCompileDependents: boolean): Promise<void> {
const file = await this.resolveFileFromCanonicalName(canonicalName); const file = await this.resolveFileFromCanonicalName(canonicalName);
const intermediateFile = path.join(this.getBuildDir(), canonicalName); const intermediateFile = path.join(this.buildDir, canonicalName);
logger.info(canonicalName + ' > ', 'Pre-compiling', file, '->', intermediateFile); logger.info(canonicalName + ' > ', 'Pre-compiling', file, '->', intermediateFile);
const source = await this.fileCache.get(file, !config.get<boolean>('view.cache')); const source = await this.fileCache.get(file, !config.get<boolean>('view.cache'));
@ -163,7 +174,7 @@ export default class SvelteViewEngine extends ViewEngine {
ssr.css.code, ssr.css.code,
].join(separator); ].join(separator);
const swafViewFile = path.join(this.getBuildDir(), canonicalName + COMPILED_SVELTE_EXTENSION); const swafViewFile = path.join(this.buildDir, canonicalName + COMPILED_SVELTE_EXTENSION);
await afs.mkdir(path.dirname(swafViewFile), {recursive: true}); await afs.mkdir(path.dirname(swafViewFile), {recursive: true});
await afs.writeFile(swafViewFile, finalCode); await afs.writeFile(swafViewFile, finalCode);
@ -208,7 +219,7 @@ export default class SvelteViewEngine extends ViewEngine {
} }
// mkdir output file dir // mkdir output file dir
const outputFile = path.join(this.getBuildDir(), canonicalViewName); const outputFile = path.join(this.buildDir, canonicalViewName);
await afs.mkdir(path.dirname(outputFile), {recursive: true}); await afs.mkdir(path.dirname(outputFile), {recursive: true});
// Read source file if code was not already provided // Read source file if code was not already provided
@ -350,17 +361,17 @@ export default class SvelteViewEngine extends ViewEngine {
// Prepare output dir // Prepare output dir
for (const name of canonicalViewNames) { for (const name of canonicalViewNames) {
await afs.mkdir(path.dirname(path.join(this.getPublicDir(), 'js', name)), {recursive: true}); await afs.mkdir(path.dirname(path.join(this.publicDir, 'js', name)), {recursive: true});
} }
const production = !config.get<boolean>('view.dev'); const production = !config.get<boolean>('view.dev');
const input = canonicalViewNames.map(name => path.join(this.getBuildDir(), name)); const input = canonicalViewNames.map(name => path.join(this.buildDir, name));
if (!this.rollup) { if (!this.rollup) {
const args = [ const args = [
'rollup', 'rollup',
'-c', 'rollup.config.js', '-c', 'rollup.config.js',
'--environment', `ENV:${production ? 'production' : 'dev'},BUILD_DIR:${this.getBuildDir()},PUBLIC_DIR:${this.getPublicDir()},INPUT:${input.join(':')}`, '--environment', `ENV:${production ? 'production' : 'dev'},BUILD_DIR:${this.buildDir},PUBLIC_DIR:${this.publicDir},INPUT:${input.join(':')}`,
]; ];
if (watch) args.push('--watch'); if (watch) args.push('--watch');
this.rollup = child_process.spawn('yarn', args, {stdio: [process.stdin, process.stdout, process.stderr]}); this.rollup = child_process.spawn('yarn', args, {stdio: [process.stdin, process.stdout, process.stderr]});

View File

@ -21,16 +21,12 @@ export default abstract class ViewEngine {
private watcher?: FSWatcher; private watcher?: FSWatcher;
/** /**
* @param buildDir A temporary directory that will contain any non-final or final non-public asset.
* @param publicDir The output directory that should contain all final and public assets.
* @param devWatchedViewDir The directory that should be watched in dev environment. * @param devWatchedViewDir The directory that should be watched in dev environment.
* @param additionalViewPaths By order of priority, the directories that contain all the views of the app. * @param additionalViewPaths By order of priority, the directories that contain all the views of the app.
* Swaf provided views and default ./view directory are automatically added (don't add them yourself). * Swaf provided views and default ./view directory are automatically added (don't add them yourself).
* @protected * @protected
*/ */
protected constructor( protected constructor(
private readonly buildDir: string,
private readonly publicDir: string,
private readonly devWatchedViewDir: string, private readonly devWatchedViewDir: string,
...additionalViewPaths: string[] ...additionalViewPaths: string[]
) { ) {
@ -40,21 +36,18 @@ export default abstract class ViewEngine {
path.resolve(__dirname, '../../views'), path.resolve(__dirname, '../../views'),
path.resolve(__dirname, '../views'), path.resolve(__dirname, '../views'),
].filter(dir => fs.existsSync(dir)); ].filter(dir => fs.existsSync(dir));
if (!fs.existsSync(this.buildDir)) {
fs.mkdirSync(this.buildDir, {recursive: true});
}
} }
public abstract getExtension(): string; public abstract getExtension(): string;
public abstract render( public abstract render(
file: string, file: string,
locals: Record<string, unknown>, locals: Record<string, unknown>,
callback: (err: Error | null, output?: string) => void, callback: (err: Error | null, output?: string) => void,
): Promise<void>; ): Promise<void>;
public setup(app: Express): void { public setup(app: Express, main: boolean): void {
app.engine(this.getExtension(), (path, options, callback) => { app.engine(this.getExtension(), (path, options, callback) => {
// Props (locals) // Props (locals)
const locals = Object.assign(ViewEngine.getGlobals(), options); const locals = Object.assign(ViewEngine.getGlobals(), options);
@ -62,24 +55,21 @@ export default abstract class ViewEngine {
this.render(path, locals, callback) this.render(path, locals, callback)
.catch(err => callback(err)); .catch(err => callback(err));
}); });
const existingViewPaths = app.get('views');
app.set('views', existingViewPaths ?
[...existingViewPaths, ...this.getViewPaths()] :
this.getViewPaths());
if (main) {
app.set('view engine', this.getExtension());
}
} }
public getViewPaths(): string[] { public getViewPaths(): string[] {
return this.viewPaths; return this.viewPaths;
} }
public getBuildDir(): string {
return this.buildDir;
}
public getPublicDir(): string {
return this.publicDir;
}
public getDevWatchedViewDir(): string {
return this.devWatchedViewDir;
}
public preCompile?(canonicalName: string, alsoCompileDependents: boolean): Promise<void>; public preCompile?(canonicalName: string, alsoCompileDependents: boolean): Promise<void>;
public afterPreCompile?(watch: boolean): Promise<void>; public afterPreCompile?(watch: boolean): Promise<void>;