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();
public constructor(
private readonly publicAssetsDir: string,
private readonly viewEngine: ViewEngine,
) {
super();
@ -20,9 +21,9 @@ export default class FrontendToolsComponent extends ApplicationComponent {
public async start(app: Express): Promise<void> {
// Cache public assets
if (config.get<boolean>('asset_cache')) {
logger.info('Caching assets from', this.viewEngine.getPublicDir(), '...');
logger.info('Caching assets from', this.publicAssetsDir, '...');
await readdirRecursively(
this.viewEngine.getPublicDir(),
this.publicAssetsDir,
async file => await this.publicAssetsCache.load(file),
);
} else {
@ -30,12 +31,7 @@ export default class FrontendToolsComponent extends ApplicationComponent {
}
// Setup express view engine
app.engine(this.viewEngine.getExtension(), (path, options, callback) => {
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');
this.viewEngine.setup(app, true);
}
public async stop(): Promise<void> {
@ -45,7 +41,7 @@ export default class FrontendToolsComponent extends ApplicationComponent {
public async handle(router: Router): Promise<void> {
router.use((req, res, next) => {
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();

View File

@ -10,6 +10,7 @@ import {sveltePreprocess} from "svelte-preprocess/dist/autoProcess";
import requireFromString from "require-from-string";
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';
@ -33,13 +34,23 @@ export default class SvelteViewEngine extends ViewEngine {
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(
buildDir: string,
publicDir: string,
devWatchedViewPath: string,
private readonly buildDir: string,
private readonly publicDir: string,
devWatchedViewDir: 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 {
@ -57,7 +68,7 @@ export default class SvelteViewEngine extends ViewEngine {
const canonicalViewName = this.toCanonicalName(file);
// 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'));
// Root template
@ -139,7 +150,7 @@ export default class SvelteViewEngine extends ViewEngine {
public async preCompile(canonicalName: string, alsoCompileDependents: boolean): Promise<void> {
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);
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,
].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.writeFile(swafViewFile, finalCode);
@ -208,7 +219,7 @@ export default class SvelteViewEngine extends ViewEngine {
}
// 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});
// Read source file if code was not already provided
@ -350,17 +361,17 @@ export default class SvelteViewEngine extends ViewEngine {
// Prepare output dir
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 input = canonicalViewNames.map(name => path.join(this.getBuildDir(), name));
const input = canonicalViewNames.map(name => path.join(this.buildDir, name));
if (!this.rollup) {
const args = [
'rollup',
'-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');
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;
/**
* @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 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).
* @protected
*/
protected constructor(
private readonly buildDir: string,
private readonly publicDir: string,
private readonly devWatchedViewDir: string,
...additionalViewPaths: string[]
) {
@ -40,21 +36,18 @@ export default abstract class ViewEngine {
path.resolve(__dirname, '../../views'),
path.resolve(__dirname, '../views'),
].filter(dir => fs.existsSync(dir));
if (!fs.existsSync(this.buildDir)) {
fs.mkdirSync(this.buildDir, {recursive: true});
}
}
public abstract getExtension(): string;
public abstract render(
file: string,
locals: Record<string, unknown>,
callback: (err: Error | null, output?: string) => void,
): Promise<void>;
public setup(app: Express): void {
public setup(app: Express, main: boolean): void {
app.engine(this.getExtension(), (path, options, callback) => {
// Props (locals)
const locals = Object.assign(ViewEngine.getGlobals(), options);
@ -62,24 +55,21 @@ export default abstract class ViewEngine {
this.render(path, locals, callback)
.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[] {
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 afterPreCompile?(watch: boolean): Promise<void>;