waibu-mpa
Version:
MPA support for Waibu Framework
154 lines (147 loc) • 6.5 kB
JavaScript
import path from 'path'
export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefix, subRoute }) {
const { defaultsDeep, parseObject } = this.app.lib.aneka
const { importModule, readJson } = this.app.bajo
const { isFunction, isPlainObject, pick, last, camelCase, omit, trimEnd } = this.app.lib._
const { titleize } = this.app.lib.aneka
const { getPluginPrefix } = this.app.waibu
const mergeRouteHooks = await importModule('waibu:/lib/webapp-scope/merge-route-hooks.js')
const mods = []
const me = this
for (const f of files) {
const ext = path.extname(f)
const urls = f.slice(0, f.length - ext.length).replace(`${dir}/extend/${pathPrefix}`, '').replaceAll('@', ':').split('/')
if (last(urls) === 'index') urls.pop()
const url = urls.join('/')
let mod
if (ext === '.js') mod = await importModule(f)
else if (ext === '.json') mod = await readJson(f)
else if (['.html', '.md'].includes(ext)) mod = [{ view: f }]
if (!mod) continue
if (isFunction(mod)) mod = [{ handler: mod }]
else if (isPlainObject(mod)) mod = [mod]
for (let m of mod) {
const mhandler = m.handler // parseObject has problem with function
const murl = m.url // same as above
m = parseObject(omit(m, ['handler', 'url'], { parseValue: true }))
m.url = murl
m.handler = mhandler
m.url = m.url ?? url
if (isFunction(m.url)) m.url = await m.url.call(this)
if (m.redirect) {
m.handler = async function (req, reply) {
return reply.redirectTo(m.redirect)
}
}
if (!m.handler) {
m.handler = async function (req, reply) {
const params = {}
let tpl = m.view ?? `${ns}.template:${m.url}.html`
if (m.tbd) {
params.page = { title: titleize(last(m.url.split('/'))) }
tpl = 'waibuMpa.template:/tbd.html'
}
return await reply.view(tpl, params)
}
}
if (urlPrefix) m.url = `/${urlPrefix}/${m.url}`
m.url = trimEnd(m.url, '/')
m.method = m.method ?? 'GET'
await mergeRouteHooks.call(me, m)
m.config = m.config ?? {}
m.config.prefix = getPluginPrefix(ns)
m.config.pathSrc = m.url
m.config.webApp = parent ?? ns
m.config.xSite = m.xSite
m.config.ns = ns
m.config.subNs = ''
m.config.noCacheReq = m.noCacheReq
m.config.title = m.title ?? camelCase(last(m.url.split('/')))
m.config.subRoute = subRoute
if (m.cache === true) m.cache = omit(me.config.page.cache, ['urls'])
m.config.cache = defaultsDeep(m.cache ?? {}, { ttlDur: 0 })
delete m.title
m = defaultsDeep(pick(cfg, ['exposeHeadRoute', 'bodyLimit']), m)
mods.push(m)
}
}
return mods
}
async function addRoutes ({ appPrefix, prefix, mods, appCtx, cfg }) {
const { importModule } = this.app.bajo
const isRouteDisabled = await importModule('waibu:/lib/webapp-scope/is-route-disabled.js')
const reroutedPath = await importModule('waibu:/lib/webapp-scope/rerouted-path.js')
for (const mod of mods) {
const fullPath = `/${appPrefix}${mod.url}`
if (await isRouteDisabled.call(this, fullPath, mod.method, cfg.disabled)) {
this.log.warn('routeDisabled%s%s', `${prefix}${fullPath}`, mod.method)
continue
}
const rpath = await reroutedPath.call(this, fullPath, cfg.rerouted)
if (rpath) {
this.log.warn('rerouted%s%s', `${prefix}${fullPath}`, `${prefix}${rpath}`)
mod.url = rpath
mod.pathReroutedTo = rpath
this.webAppCtx.route(mod)
} else appCtx.route(mod)
}
}
async function buildRoutes (prefix) {
const { eachPlugins, runHook } = this.app.bajo
const { getPluginPrefix } = this.app.waibu
const { fastGlob } = this.app.lib
const { groupBy } = this.app.lib._
const cfg = this.config
const pathPrefix = 'waibuMpa/route'
const me = this
const appCtxs = {}
let subRoutes = []
const names = this.app.getAllNs()
await runHook(`${this.ns}:beforeBuildRoutes`, this.webAppCtx)
await eachPlugins(async function ({ dir }) {
const { ns } = this
let appPrefix = getPluginPrefix(ns)
if (ns === me.ns || (ns === me.app.mainNs && cfg.mountMainAsRoot)) appPrefix = ''
const pattern = `${dir}/extend/${pathPrefix}/**/*.{js,json,html,md}`
const files = await fastGlob(pattern)
// subRoutes
const spattern = `${dir}/extend/waibuMpa/extend/{${names.join(',')}}/route/**/*.{js,json,html,md}`
const sfiles = await fastGlob(spattern)
for (const file of sfiles) {
const [sns] = file.replace(`${dir}/extend/waibuMpa/extend/`, '').split('/')
subRoutes.push({ file, ns: sns, sns: ns, dir })
}
if (files.length === 0) return undefined
await me.webAppCtx.register(async (appCtx) => {
appCtxs[ns] = appCtx
await runHook(`${me.ns}.${this.ns}:beforeBuildRoutes`, appCtx, appPrefix)
const mods = await build.call(this, { appCtx, files, appPrefix, pathPrefix, dir, ns, cfg, parent: me.ns })
await addRoutes.call(me, { appPrefix, prefix, mods, appCtx, cfg })
await runHook(`${me.ns}.${this.ns}:afterBuildRoutes`, appCtx, appPrefix)
}, { prefix: appPrefix })
})
if (subRoutes.length > 0) {
subRoutes = groupBy(subRoutes, 'ns')
for (const k in subRoutes) {
const items = subRoutes[k]
if (items.length === 0) continue
const { sns, ns } = items[0]
const appCtx = appCtxs[ns]
if (!appCtx) throw this.error('cantHaveSubroutesWithoutContext%s', ns)
let appPrefix = getPluginPrefix(sns)
if (sns === me.ns || (sns === me.app.mainNs && cfg.mountMainAsRoot)) appPrefix = ''
await runHook(`${me.ns}.${k}:beforeBuildSubRoutes`, appCtx, appPrefix)
for (const item of items) {
const { file, sns, ns, dir } = item
const scope = this.app[ns]
const pathPrefix = `waibuMpa/extend/${ns}/route/`
const urlPrefix = getPluginPrefix(sns)
const mods = await build.call(scope, { appCtx, files: [file], pathPrefix, dir, ns, cfg, parent: me.ns, urlPrefix, subRoute: sns })
await addRoutes.call(me, { appPrefix, prefix, mods, appCtx, cfg })
}
await runHook(`${me.ns}.${k}:afterBuildSubRoutes`, appCtx, appPrefix)
}
}
await runHook(`${this.ns}:afterBuildRoutes`, this.webAppCtx)
}
export default buildRoutes