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();
|
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();
|
||||||
|
@ -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]});
|
||||||
|
@ -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>;
|
||||||
|
Loading…
Reference in New Issue
Block a user