swaf/src/components/FrontendToolsComponent.ts

134 lines
4.3 KiB
TypeScript

import config from "config";
import {Express, Router} from "express";
import path from "path";
import util from "util";
import ApplicationComponent from "../ApplicationComponent.js";
import {QueryParams, route, RouteParams} from "../common/Routing.js";
import AssetCompiler from "../frontend/AssetCompiler.js";
import AssetPreCompiler from "../frontend/AssetPreCompiler.js";
import Globals from "../frontend/Globals.js";
import ViewEngine from "../frontend/ViewEngine.js";
import {logger} from "../Logger.js";
import {listFilesRecursively} from "../Utils.js";
import FileCache from "../utils/FileCache.js";
export default class FrontendToolsComponent extends ApplicationComponent {
private readonly publicDir: string;
private readonly publicAssetsCache: FileCache = new FileCache();
private readonly assetPreCompilers: AssetPreCompiler[];
private readonly globals: Globals = new Globals();
public constructor(
private readonly assetCompiler: AssetCompiler,
...assetPreCompilers: AssetPreCompiler[]
) {
super();
this.assetPreCompilers = assetPreCompilers;
this.publicDir = this.assetCompiler.targetDir;
for (const assetPreCompiler of this.assetPreCompilers) {
if (assetPreCompiler.isPublic()) {
this.assetCompiler.addExtension(assetPreCompiler.getExtension());
}
assetPreCompiler.setGlobals(this.globals);
}
}
public async init(): Promise<void> {
this.globals.set('route', (
routeName: string,
params: RouteParams = [],
query: QueryParams = '',
absolute: boolean = false,
) => route(routeName, params, query, absolute));
this.globals.set('app_version', this.getApp().getVersion());
this.globals.set('core_version', this.getApp().getCoreVersion());
this.globals.set('app', config.get('app'));
this.globals.set('dump', (val: unknown) => {
return util.inspect(val);
});
this.globals.set('hex', (v: number) => {
return v.toString(16);
});
}
public async start(app: Express): Promise<void> {
// Cache public assets
if (config.get<boolean>('asset_cache')) {
logger.info('Caching assets from', this.publicDir, '...');
for (const file of await listFilesRecursively(this.publicDir)) {
await this.publicAssetsCache.load(file);
}
} else {
logger.info('Asset cache disabled.');
}
this.hookPreCompilers();
// Setup express view engine
let main = true;
for (const assetPreCompiler of this.assetPreCompilers) {
if (assetPreCompiler instanceof ViewEngine) {
assetPreCompiler.setup(app, main);
main = false;
}
}
}
public async stop(): Promise<void> {
for (const assetPreCompiler of this.assetPreCompilers) {
await assetPreCompiler.stop();
}
}
public async handleRoutes(router: Router): Promise<void> {
router.use((req, res, next) => {
res.locals.inlineAsset = (urlPath: string) => {
return this.publicAssetsCache.getOrFail(path.join(this.publicDir, urlPath));
};
next();
});
// Add request context locals
router.use((req, res, next) => {
res.locals.url = req.url;
res.locals.params = req.params;
res.locals.query = req.query;
res.locals.body = req.body;
next();
});
}
public hookPreCompilers(): void {
for (const assetPreCompiler of this.assetPreCompilers) {
assetPreCompiler.onPreCompile(async watch => {
await this.assetCompiler.compile(watch);
});
assetPreCompiler.onInputChange(async restart => {
await this.assetCompiler.stopWatching(restart);
});
}
}
public async preCompileViews(watch: boolean): Promise<void> {
for (const viewEngine of this.assetPreCompilers) {
await viewEngine.preCompileAll(watch);
}
await this.assetCompiler.compile(watch);
if (watch) {
this.hookPreCompilers();
}
}
public getGlobals(): Globals {
return this.globals;
}
}