Add svelte as a view engine to swaf #33
@ -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();
|
||||
|
@ -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]});
|
||||
|
@ -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>;
|
||||
|
Loading…
Reference in New Issue
Block a user