Add support for view engines, PNPM
This commit is contained in:
@@ -4,7 +4,7 @@ import {HTTPKernelModule} from "./HTTPKernelModule";
|
||||
import {Logging} from "../../service/Logging";
|
||||
import {AppClass} from "../../lifecycle/AppClass";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {http} from "../response/HTTPErrorResponseFactory";
|
||||
import {error} from "../response/ErrorResponseFactory";
|
||||
|
||||
/**
|
||||
* Interface for fluently registering kernel modules into the kernel.
|
||||
@@ -73,7 +73,7 @@ export class HTTPKernel extends AppClass {
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.logging.error(e)
|
||||
await http(HTTPStatus.REQUEST_TIMEOUT).write(request)
|
||||
await error(e).status(HTTPStatus.INTERNAL_SERVER_ERROR).write(request)
|
||||
}
|
||||
|
||||
this.logging.verbose('Finished kernel lifecycle')
|
||||
|
||||
22
src/http/response/ViewResponseFactory.ts
Normal file
22
src/http/response/ViewResponseFactory.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {Container} from "@extollo/di";
|
||||
import {ResponseFactory} from "./ResponseFactory";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {ViewEngine} from "../../views/ViewEngine";
|
||||
|
||||
export function view(name: string, data?: {[key: string]: any}): ViewResponseFactory {
|
||||
return new ViewResponseFactory(name, data)
|
||||
}
|
||||
|
||||
export class ViewResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
public readonly viewName: string,
|
||||
public readonly data?: {[key: string]: any}
|
||||
) { super() }
|
||||
|
||||
public async write(request: Request) {
|
||||
const viewEngine = <ViewEngine> Container.getContainer().make(ViewEngine)
|
||||
request.response.body = await viewEngine.renderByName(this.viewName, this.data || {})
|
||||
request.response.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
return request
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export * from './http/response/JSONResponseFactory'
|
||||
export * from './http/response/ResponseFactory'
|
||||
export * from './http/response/StringResponseFactory'
|
||||
export * from './http/response/TemporaryRedirectResponseFactory'
|
||||
export * from './http/response/ViewResponseFactory'
|
||||
|
||||
export * from './http/routing/ActivatedRoute'
|
||||
export * from './http/routing/Route'
|
||||
@@ -51,3 +52,7 @@ export * from './service/Config'
|
||||
export * from './service/Controllers'
|
||||
export * from './service/HTTPServer'
|
||||
export * from './service/Routing'
|
||||
|
||||
export * from './views/ViewEngine'
|
||||
export * from './views/ViewEngineFactory'
|
||||
export * from './views/PugViewEngine'
|
||||
|
||||
@@ -6,4 +6,12 @@ export class Config extends CanonicalRecursive {
|
||||
protected appPath: string[] = ['configs']
|
||||
protected suffix: string = '.config.js'
|
||||
protected canonicalItem: string = 'config'
|
||||
|
||||
public async up() {
|
||||
await super.up()
|
||||
|
||||
if ( this.get('server.debug', false) ) {
|
||||
Error.stackTraceLimit = Infinity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {InjectSessionHTTPModule} from "../http/kernel/module/InjectSessionHTTPMo
|
||||
import {PersistSessionHTTPModule} from "../http/kernel/module/PersistSessionHTTPModule";
|
||||
import {MountActivatedRouteHTTPModule} from "../http/kernel/module/MountActivatedRouteHTTPModule";
|
||||
import {ExecuteResolvedRouteHandlerHTTPModule} from "../http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule";
|
||||
import {error} from "../http/response/ErrorResponseFactory";
|
||||
|
||||
@Singleton()
|
||||
export class HTTPServer extends Unit {
|
||||
@@ -76,7 +77,12 @@ export class HTTPServer extends Unit {
|
||||
.run()
|
||||
.catch(e => this.logging.error(e))
|
||||
|
||||
await this.kernel.handle(extolloReq)
|
||||
try {
|
||||
await this.kernel.handle(extolloReq)
|
||||
} catch (e) {
|
||||
await error(e).write(extolloReq)
|
||||
}
|
||||
|
||||
await extolloReq.response.send()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {Unit} from "../lifecycle/Unit"
|
||||
import {Logging} from "./Logging"
|
||||
import {Route} from "../http/routing/Route";
|
||||
import {HTTPMethod} from "../http/lifecycle/Request";
|
||||
import {ViewEngineFactory} from "../views/ViewEngineFactory";
|
||||
|
||||
@Singleton()
|
||||
export class Routing extends Unit {
|
||||
@@ -13,6 +14,8 @@ export class Routing extends Unit {
|
||||
protected compiledRoutes: Collection<Route> = new Collection<Route>()
|
||||
|
||||
public async up() {
|
||||
this.app().registerFactory(new ViewEngineFactory());
|
||||
|
||||
for await ( const entry of this.path.walk() ) {
|
||||
if ( !entry.endsWith('.routes.js') ) {
|
||||
this.logging.debug(`Skipping routes file with invalid suffix: ${entry}`)
|
||||
|
||||
33
src/views/PugViewEngine.ts
Normal file
33
src/views/PugViewEngine.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {ViewEngine} from "./ViewEngine"
|
||||
import {Injectable} from "@extollo/di"
|
||||
import * as pug from "pug"
|
||||
|
||||
@Injectable()
|
||||
export class PugViewEngine extends ViewEngine {
|
||||
protected compileCache: {[key: string]: ((locals?: pug.LocalsObject) => string)} = {}
|
||||
|
||||
public renderString(templateString: string, locals: { [p: string]: any }): string | Promise<string> {
|
||||
return pug.compile(templateString, this.getOptions())(locals)
|
||||
}
|
||||
|
||||
public renderByName(templateName: string, locals: { [p: string]: any }): string | Promise<string> {
|
||||
let compiled = this.compileCache[templateName]
|
||||
if ( compiled ) return compiled(locals)
|
||||
|
||||
if ( !templateName.endsWith('.pug') ) templateName += '.pug'
|
||||
const filePath = this.path.concat(...templateName.split(':'))
|
||||
compiled = pug.compileFile(filePath.toLocal, this.getOptions())
|
||||
|
||||
this.compileCache[templateName] = compiled
|
||||
return compiled(locals)
|
||||
}
|
||||
|
||||
protected getOptions() {
|
||||
return {
|
||||
basedir: this.path.toLocal,
|
||||
debug: this.debug,
|
||||
compileDebug: this.debug,
|
||||
globals: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/views/ViewEngine.ts
Normal file
23
src/views/ViewEngine.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import {AppClass} from "../lifecycle/AppClass"
|
||||
import {Config} from "../service/Config"
|
||||
import {Container} from "@extollo/di"
|
||||
import {UniversalPath} from "@extollo/util"
|
||||
|
||||
export abstract class ViewEngine extends AppClass {
|
||||
protected readonly config: Config
|
||||
protected readonly debug: boolean
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.config = Container.getContainer().make(Config)
|
||||
this.debug = (this.config.get('server.mode', 'production') === 'development'
|
||||
|| this.config.get('server.debug', false))
|
||||
}
|
||||
|
||||
public get path(): UniversalPath {
|
||||
return this.app().appPath(...['resources', 'views']) // FIXME allow configuring
|
||||
}
|
||||
|
||||
public abstract renderString(templateString: string, locals: {[key: string]: any}): string | Promise<string>
|
||||
public abstract renderByName(templateName: string, locals: {[key: string]: any}): string | Promise<string>
|
||||
}
|
||||
66
src/views/ViewEngineFactory.ts
Normal file
66
src/views/ViewEngineFactory.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
AbstractFactory,
|
||||
Container,
|
||||
DependencyRequirement,
|
||||
PropertyDependency,
|
||||
isInstantiable,
|
||||
DEPENDENCY_KEYS_METADATA_KEY,
|
||||
DEPENDENCY_KEYS_PROPERTY_METADATA_KEY
|
||||
} from "@extollo/di"
|
||||
import {Collection, ErrorWithContext} from "@extollo/util"
|
||||
import {Logging} from "../service/Logging";
|
||||
import {Config} from "../service/Config";
|
||||
import {ViewEngine} from "./ViewEngine";
|
||||
import {PugViewEngine} from "./PugViewEngine";
|
||||
|
||||
export class ViewEngineFactory extends AbstractFactory {
|
||||
protected readonly logging: Logging
|
||||
protected readonly config: Config
|
||||
|
||||
constructor() {
|
||||
super({})
|
||||
this.logging = Container.getContainer().make<Logging>(Logging)
|
||||
this.config = Container.getContainer().make<Config>(Config)
|
||||
}
|
||||
|
||||
produce(dependencies: any[], parameters: any[]): ViewEngine {
|
||||
return new (this.getViewEngineClass())
|
||||
}
|
||||
|
||||
match(something: any) {
|
||||
return something === ViewEngine
|
||||
}
|
||||
|
||||
getDependencyKeys(): Collection<DependencyRequirement> {
|
||||
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getViewEngineClass())
|
||||
if ( meta ) return meta
|
||||
return new Collection<DependencyRequirement>()
|
||||
}
|
||||
|
||||
getInjectedProperties(): Collection<PropertyDependency> {
|
||||
const meta = new Collection<PropertyDependency>()
|
||||
let currentToken = this.getViewEngineClass()
|
||||
|
||||
do {
|
||||
const loadedMeta = Reflect.getMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, currentToken)
|
||||
if ( loadedMeta ) meta.concat(loadedMeta)
|
||||
currentToken = Object.getPrototypeOf(currentToken)
|
||||
} while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype)
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
protected getViewEngineClass() {
|
||||
const ViewEngineClass = this.config.get('server.view_engine.driver', PugViewEngine)
|
||||
|
||||
if ( !isInstantiable(ViewEngineClass) || !(ViewEngineClass.prototype instanceof ViewEngine) ) {
|
||||
const e = new ErrorWithContext('Provided session class does not extend from @extollo/lib.ViewEngine');
|
||||
e.context = {
|
||||
config_key: 'server.view_engine.driver',
|
||||
class: ViewEngineClass.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
return ViewEngineClass
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user