waibu-mpa
Version:
MPA support for Waibu Framework
67 lines (63 loc) • 3.05 kB
JavaScript
import path from 'path'
import { printLink } from './inject-elements/css.js'
import { printScript } from './inject-elements/script.js'
async function apply ({ $, req, tag, attr, type }) {
const { includes } = this.app.lib.aneka
const { routePath } = this.app.waibu
const { hash, fetch } = this.app.bajoExtra
const { isEmpty, map, without } = this.app.lib._
const { get: getCache, set: setCache } = this.app.bajoCache ?? {}
const prefix = this.app.bajoCache.config.exportPrefix
const excluded = map(this.config.concatResource.excluded, item => routePath(item))
const baseUrl = `${req.protocol}://${req.hostname}${req.port ? `:${req.port}` : ''}` // TODO: auth, if any
const items = []
$(`${tag}[${attr}]`).each(function () {
items.push(this.attribs[attr])
})
if (items.length === 0) return
let keys = []
const notKeys = []
const key = `${prefix}-waibu-mpa-concat-resource-${await hash(items)}`
const cached = await getCache({ key })
if (!cached) {
const contents = []
for (const item of items) {
try {
if (excluded.includes(item)) throw this.error('excludedFromRscConcat%s', item)
let url = item
if (!item.startsWith('http')) {
const u = new URL(req.url, baseUrl)
if (item[0] === '/') url = u.origin + url
else url = u.origin + path.dirname(u.pathname) + '/' + url
}
const resp = await fetch(url, undefined, { rawResponse: true })
if (!resp.ok) throw this.error('respError%s', resp.status)
const text = await resp.text()
if (type === 'css' && includes(['url(".', 'url(.', 'url(\'.'], text)) throw this.error('cssContainsRelPath%s', item)
contents.push(`/* waibu resource: ${item} */`, text)
keys.push(item)
} catch (err) {
notKeys.push(item)
}
}
if (isEmpty(contents)) return
$(map(keys, k => `${tag}[${attr}="${k}"]`).join(',')).remove()
const value = { contents, notKeys, type, tag, attr }
await setCache({ key, value, ttl: this.config.concatResource.ttlDur })
} else {
keys = without(items, ...cached.notKeys)
$(map(keys, k => `${tag}[${attr}="${k}"]`).join(',')).remove()
}
const rsc = `bajoCache:/external/${key.slice(prefix.length + 1)}.${type}`
if (tag === 'link') $('head').prepend(printLink.call(this, rsc))
else if (tag === 'script') $('body').append(printScript.call(this, rsc))
}
async function concatResources (options) {
const { $, req } = options ?? {}
if (!(this.app.bajoExtra && this.app.bajoCache)) return
if (this.config.concatResource.ttlDur === 0) return
if (this.config.concatResource.css) await apply.call(this, { $, req, tag: 'link', attr: 'href', type: 'css' })
if (this.config.concatResource.links) await apply.call(this, { $, req, tag: 'link', attr: 'href', type: 'css' })
if (this.config.concatResource.scripts) await apply.call(this, { $, req, tag: 'script', attr: 'src', type: 'js' })
}
export default concatResources