Support registering namespaced view directories; add lib() universal path
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Garrett Mills 2021-06-24 00:14:04 -05:00
parent a69c81ed35
commit 7506d6567d
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
9 changed files with 201 additions and 17 deletions

View File

@ -38,12 +38,14 @@
"typedoc-plugin-pages-fork": "^0.0.1",
"typedoc-plugin-sourcefile-url": "^1.0.6",
"typescript": "^4.2.3",
"copyfiles": "^2.4.1",
"uuid": "^8.3.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prebuild": "pnpm run lint",
"build": "tsc",
"postbuild": "copyfiles -u 1 \"src/resources/**/*\" lib",
"app": "tsc && node lib/index.js",
"prepare": "pnpm run build",
"docs:build": "typedoc --options typedoc.json",

View File

@ -15,6 +15,7 @@ dependencies:
busboy: 0.3.1
cli-table: 0.3.6
colors: 1.4.0
copyfiles: 2.4.1
dotenv: 8.2.0
mkdirp: 1.0.4
negotiator: 0.6.2
@ -383,7 +384,6 @@ packages:
resolution:
integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
/ansi-regex/5.0.0:
dev: true
engines:
node: '>=8'
resolution:
@ -399,7 +399,6 @@ packages:
/ansi-styles/4.3.0:
dependencies:
color-convert: 2.0.1
dev: true
engines:
node: '>=8'
resolution:
@ -568,6 +567,14 @@ packages:
node: '>= 0.2.0'
resolution:
integrity: sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==
/cliui/7.0.4:
dependencies:
string-width: 4.2.2
strip-ansi: 6.0.0
wrap-ansi: 7.0.0
dev: false
resolution:
integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
/code-point-at/1.1.0:
dev: false
engines:
@ -583,7 +590,6 @@ packages:
/color-convert/2.0.1:
dependencies:
color-name: 1.1.4
dev: true
engines:
node: '>=7.0.0'
resolution:
@ -593,7 +599,6 @@ packages:
resolution:
integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
/color-name/1.1.4:
dev: true
resolution:
integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
/colors/1.0.3:
@ -626,6 +631,19 @@ packages:
dev: false
resolution:
integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==
/copyfiles/2.4.1:
dependencies:
glob: 7.1.7
minimatch: 3.0.4
mkdirp: 1.0.4
noms: 0.0.0
through2: 2.0.5
untildify: 4.0.0
yargs: 16.2.0
dev: false
hasBin: true
resolution:
integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==
/core-util-is/1.0.2:
dev: false
resolution:
@ -722,7 +740,6 @@ packages:
resolution:
integrity: sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
/emoji-regex/8.0.0:
dev: true
resolution:
integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
/enquirer/2.3.6:
@ -733,6 +750,12 @@ packages:
node: '>=8.6'
resolution:
integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
/escalade/3.1.1:
dev: false
engines:
node: '>=6'
resolution:
integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
/escape-string-regexp/1.0.5:
dev: true
engines:
@ -986,6 +1009,12 @@ packages:
dev: false
resolution:
integrity: sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
/get-caller-file/2.0.5:
dev: false
engines:
node: 6.* || 8.* || >= 10.*
resolution:
integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
/get-intrinsic/1.1.1:
dependencies:
function-bind: 1.1.1
@ -1168,7 +1197,6 @@ packages:
resolution:
integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
/is-fullwidth-code-point/3.0.0:
dev: true
engines:
node: '>=8'
resolution:
@ -1200,6 +1228,10 @@ packages:
node: '>= 0.4'
resolution:
integrity: sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
/isarray/0.0.1:
dev: false
resolution:
integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
/isarray/1.0.0:
dev: false
resolution:
@ -1391,6 +1423,13 @@ packages:
node: 4.x || >=6.0.0
resolution:
integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
/noms/0.0.0:
dependencies:
inherits: 2.0.4
readable-stream: 1.0.34
dev: false
resolution:
integrity: sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=
/nopt/5.0.0:
dependencies:
abbrev: 1.1.1
@ -1697,6 +1736,15 @@ packages:
dev: true
resolution:
integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
/readable-stream/1.0.34:
dependencies:
core-util-is: 1.0.2
inherits: 2.0.4
isarray: 0.0.1
string_decoder: 0.10.31
dev: false
resolution:
integrity: sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
/readable-stream/2.3.7:
dependencies:
core-util-is: 1.0.2
@ -1737,6 +1785,12 @@ packages:
node: '>=8'
resolution:
integrity: sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
/require-directory/2.1.1:
dev: false
engines:
node: '>=0.10.0'
resolution:
integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
/require-from-string/2.0.2:
dev: true
engines:
@ -1913,11 +1967,14 @@ packages:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.0
dev: true
engines:
node: '>=8'
resolution:
integrity: sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
/string_decoder/0.10.31:
dev: false
resolution:
integrity: sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
/string_decoder/1.1.1:
dependencies:
safe-buffer: 5.1.2
@ -1941,7 +1998,6 @@ packages:
/strip-ansi/6.0.0:
dependencies:
ansi-regex: 5.0.0
dev: true
engines:
node: '>=8'
resolution:
@ -1998,6 +2054,13 @@ packages:
dev: true
resolution:
integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
/through2/2.0.5:
dependencies:
readable-stream: 2.3.7
xtend: 4.0.2
dev: false
resolution:
integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
/to-fast-properties/2.0.0:
dev: false
engines:
@ -2144,6 +2207,12 @@ packages:
node: '>= 10.0.0'
resolution:
integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
/untildify/4.0.0:
dev: false
engines:
node: '>=8'
resolution:
integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
/uri-js/4.4.1:
dependencies:
punycode: 2.1.1
@ -2209,6 +2278,16 @@ packages:
dev: false
resolution:
integrity: sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
/wrap-ansi/7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.2
strip-ansi: 6.0.0
dev: false
engines:
node: '>=10'
resolution:
integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
/wrappy/1.0.2:
resolution:
integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
@ -2218,6 +2297,12 @@ packages:
node: '>=0.4'
resolution:
integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
/y18n/5.0.8:
dev: false
engines:
node: '>=10'
resolution:
integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
/yallist/3.1.1:
dev: false
resolution:
@ -2225,6 +2310,26 @@ packages:
/yallist/4.0.0:
resolution:
integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
/yargs-parser/20.2.9:
dev: false
engines:
node: '>=10'
resolution:
integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
/yargs/16.2.0:
dependencies:
cliui: 7.0.4
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.2
y18n: 5.0.8
yargs-parser: 20.2.9
dev: false
engines:
node: '>=10'
resolution:
integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
/yn/3.1.1:
dev: false
engines:
@ -2250,6 +2355,7 @@ specifiers:
busboy: ^0.3.1
cli-table: ^0.3.6
colors: ^1.4.0
copyfiles: ^2.4.1
dotenv: ^8.2.0
eslint: ^7.27.0
mkdirp: ^1.0.4

View File

@ -1,7 +1,8 @@
import {Instantiable} from './types'
import {DependencyKey, Instantiable} from './types'
import NamedFactory from './factory/NamedFactory'
import {AbstractFactory} from './factory/AbstractFactory'
import {Factory} from './factory/Factory'
import {ClosureFactory} from './factory/ClosureFactory'
export class ContainerBlueprint {
private static instance?: ContainerBlueprint
@ -36,6 +37,16 @@ export class ContainerBlueprint {
return this
}
/**
* Register a producer function as a ClosureFactory with this container.
* @param key
* @param producer
*/
registerProducer(key: DependencyKey, producer: () => any): this {
this.factories.push(() => new ClosureFactory(key, producer))
return this
}
resolve(): AbstractFactory<any>[] {
return this.factories.map(x => x())
}

View File

@ -1,4 +1,5 @@
export * from './util'
export * from './lib'
export * from './di'
export * from './event/types'

8
src/lib.ts Normal file
View File

@ -0,0 +1,8 @@
import {UniversalPath} from './util'
/**
* Get the path to the root of the @extollo/lib package.
*/
export function lib(): UniversalPath {
return new UniversalPath(__dirname)
}

View File

@ -0,0 +1,3 @@
html
body
h1 Extollo Login Page

View File

@ -5,6 +5,8 @@ import {Logging} from './Logging'
import {Route} from '../http/routing/Route'
import {HTTPMethod} from '../http/lifecycle/Request'
import {ViewEngineFactory} from '../views/ViewEngineFactory'
import {ViewEngine} from '../views/ViewEngine'
import {lib} from '../lib'
/**
* Application unit that loads the various route files from `app/http/routes` and pre-compiles the route handlers.
@ -18,6 +20,9 @@ export class Routing extends Unit {
public async up(): Promise<void> {
this.app().registerFactory(new ViewEngineFactory())
const engine = <ViewEngine> this.make(ViewEngine)
this.logging.verbose('Registering @extollo view engine namespace.')
engine.registerNamespace('extollo', lib().concat('resources', 'views'))
for await ( const entry of this.path.walk() ) {
if ( !entry.endsWith('.routes.js') ) {

View File

@ -20,11 +20,8 @@ export class PugViewEngine extends ViewEngine {
return compiled(locals)
}
if ( !templateName.endsWith('.pug') ) {
templateName += '.pug'
}
const filePath = this.path.concat(...templateName.split(':'))
compiled = pug.compileFile(filePath.toLocal, this.getOptions())
const filePath = this.resolveName(templateName)
compiled = pug.compileFile(filePath.toLocal, this.getOptions(templateName))
this.compileCache[templateName] = compiled
return compiled(locals)
@ -34,9 +31,9 @@ export class PugViewEngine extends ViewEngine {
* Get the object of options passed to Pug's compile methods.
* @protected
*/
protected getOptions(): pug.Options {
protected getOptions(templateName?: string): pug.Options {
return {
basedir: this.path.toLocal,
basedir: templateName ? this.resolveBasePath(templateName).toLocal : this.path.toLocal,
debug: this.debug,
compileDebug: this.debug,
globals: [],

View File

@ -1,7 +1,7 @@
import {AppClass} from '../lifecycle/AppClass'
import {Config} from '../service/Config'
import {Container} from '../di'
import {UniversalPath} from '../util'
import {ErrorWithContext, UniversalPath} from '../util'
/**
* Abstract base class for rendering views via different view engines.
@ -11,6 +11,8 @@ export abstract class ViewEngine extends AppClass {
protected readonly debug: boolean
protected readonly namespaces: {[key: string]: UniversalPath} = {}
constructor() {
super()
this.config = Container.getContainer().make(Config)
@ -38,4 +40,53 @@ export abstract class ViewEngine extends AppClass {
* @param locals
*/
public abstract renderByName(templateName: string, locals: {[key: string]: any}): string | Promise<string>
public registerNamespace(namespace: string, basePath: UniversalPath): this {
if ( namespace.startsWith('@') ) {
namespace = namespace.substr(1)
}
this.namespaces[namespace] = basePath
return this
}
public resolveName(templateName: string): UniversalPath {
let path = this.path
if ( templateName.startsWith('@') ) {
const [namespace, ...parts] = templateName.split(':')
path = this.namespaces[namespace.substr(1)]
if ( !path ) {
throw new ErrorWithContext('Invalid template namespace: ' + namespace, {
namespace,
templateName,
})
}
templateName = parts.join(':')
}
if ( !templateName.endsWith('.pug') ) {
templateName += '.pug'
}
return path.concat(...templateName.split(':'))
}
public resolveBasePath(templateName: string): UniversalPath {
let path = this.path
if ( templateName.startsWith('@') ) {
const [namespace] = templateName.split(':')
path = this.namespaces[namespace.substr(1)]
if ( !path ) {
throw new ErrorWithContext('Invalid template namespace: ' + namespace, {
namespace,
templateName,
})
}
}
return path
}
}