waibu-mpa 2.15.2 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,8 +8,6 @@ class Wmpa {
8
8
  this.prefixMain = '<%= prefix.main %>'
9
9
  this.accessTokenUrl = '<%= accessTokenUrl %>'
10
10
  this.renderUrl = '<%= renderUrl %>'
11
- this.theme = '<%= _meta.theme.name %>'
12
- this.iconset = '<%= _meta.iconset.name %>'
13
11
  this.apiPrefix = '<%= api.prefix %>'
14
12
  this.apiExt = '<%= api.ext %>'
15
13
  this.apiHeaderKey = '<%= api.headerKey %>'
@@ -59,6 +57,7 @@ class Wmpa {
59
57
  }
60
58
 
61
59
  init = () => {
60
+ this.reqId = document.querySelector('meta[name="req-id"]').getAttribute('content')
62
61
  window.addEventListener('load', evt => {
63
62
  if (window.hljs) window.hljs.highlightAll()
64
63
  })
@@ -103,7 +102,7 @@ class Wmpa {
103
102
  fetchRender = async (body, qs = {}) => {
104
103
  if (_.isArray(body)) body = body.join('\n')
105
104
  let url = this.renderUrl + '?'
106
- _.forOwn(_.omit(qs, ['theme', 'iconset']), (v, k) => {
105
+ _.forOwn(qs, (v, k) => {
107
106
  url += '&' + k + '=' + v
108
107
  })
109
108
  const opts = {
@@ -111,8 +110,7 @@ class Wmpa {
111
110
  headers: {
112
111
  'Content-Type': 'text/plain',
113
112
  'X-Referer': window.location.href,
114
- 'X-Theme': this.theme,
115
- 'X-Iconset': this.iconset
113
+ 'X-Req-Id': this.reqId
116
114
  },
117
115
  body
118
116
  }
@@ -1,12 +1,27 @@
1
1
  const component = {
2
2
  method: 'POST',
3
+ noCacheReq: true,
3
4
  handler: async function (req, reply) {
5
+ const { getPluginDataDir } = this.app.bajo
6
+ const { fs } = this.app.lib
7
+ const { merge, get } = this.app.lib._
4
8
  req.referer = req.headers['x-referer']
9
+ const pageId = req.headers['x-req-id']
5
10
  const { ext = '.html' } = req.body
11
+ let params = req.query
6
12
  reply.header('Content-Type', `text/html; charset=${this.config.page.charset}`)
7
13
  reply.header('Content-Language', req.lang)
8
- const opts = { req, reply, partial: true, ext, theme: req.headers['x-theme'], iconset: req.headers['x-iconset'] }
9
- return this.renderString(req.body, req.query, opts)
14
+ if (pageId) {
15
+ try {
16
+ const file = `${getPluginDataDir(this.ns)}/cache/req/${pageId}/locals.json`
17
+ const locals = JSON.parse(fs.readFileSync(file, 'utf8'))
18
+ params = merge({}, locals, params)
19
+ } catch (err) {}
20
+ }
21
+ const theme = get(req, 'headers.x-theme', get(params, '_meta.theme.name'))
22
+ const iconset = get(req, 'headers.x-iconset', get(params, '_meta.iconset.name'))
23
+ const opts = { pageId, req, reply, partial: true, ext, theme, iconset }
24
+ return this.renderString(req.body, params, opts)
10
25
  }
11
26
  }
12
27
 
@@ -1,6 +1,7 @@
1
1
  const wmpa = {
2
2
  url: '/wmpa.js',
3
3
  method: 'GET',
4
+ noCacheReq: true,
4
5
  handler: async function (req, reply) {
5
6
  const { get, trim, cloneDeep } = this.app.lib._
6
7
  const { getPluginPrefix } = this.app.waibu
package/index.js CHANGED
@@ -43,6 +43,7 @@ async function factory (pkgName) {
43
43
  ttlDur: 0,
44
44
  urls: []
45
45
  },
46
+ refreshDur: '1d',
46
47
  insertWarning: false,
47
48
  usePluginTitle: false,
48
49
  scriptsAtEndOfBody: true
@@ -136,7 +137,11 @@ async function factory (pkgName) {
136
137
  },
137
138
  compress: false,
138
139
  rateLimit: false,
139
- disabled: []
140
+ disabled: [],
141
+ purgeCache: {
142
+ intvDur: '1m',
143
+ reqTtlDur: '1d'
144
+ }
140
145
  }
141
146
 
142
147
  this.configDev = {
@@ -166,6 +171,13 @@ async function factory (pkgName) {
166
171
  await widgetFactory.call(this)
167
172
  }
168
173
 
174
+ start = async () => {
175
+ this.purgeCache()
176
+ setInterval(() => {
177
+ this.purgeCache()
178
+ }, this.config.purgeCache.intvDur)
179
+ }
180
+
169
181
  buildUrl = ({ exclude = [], prefix = '?', base, url = '', params = {}, prettyUrl }) => {
170
182
  const { parseObject } = this.app.lib
171
183
  const { qs } = this.app.waibu
@@ -495,23 +507,17 @@ async function factory (pkgName) {
495
507
  return output
496
508
  }
497
509
 
498
- renderString = async (text, params = {}, opts = {}) => {
499
- const { importModule } = this.app.bajo
500
- const buildLocals = await importModule('waibu:/lib/build-locals.js')
501
- const locals = await buildLocals.call(this, { tpl: null, params, opts })
510
+ renderString = async (text, locals = {}, opts = {}) => {
502
511
  const ve = this.getViewEngine(opts.ext)
503
512
  return await ve.renderString(text, locals, opts)
504
513
  }
505
514
 
506
- render = async (tpl, params = {}, opts = {}) => {
507
- const { importModule } = this.app.bajo
508
- const buildLocals = await importModule('waibu:/lib/build-locals.js')
509
- const locals = await buildLocals.call(this, { tpl, params, opts })
515
+ render = async (tpl, locals = {}, opts = {}) => {
510
516
  const ext = path.extname(tpl)
511
517
  if (['.json', '.js', '.css'].includes(ext)) opts.partial = true
512
518
  opts.ext = ext
513
- const viewEngine = this.getViewEngine(ext)
514
- return await viewEngine.render(tpl, locals, opts)
519
+ const ve = this.getViewEngine(ext)
520
+ return await ve.render(tpl, locals, opts)
515
521
  }
516
522
 
517
523
  stripHtmlTags = (html, options = {}) => {
@@ -634,6 +640,18 @@ async function factory (pkgName) {
634
640
  if (!iconset) return iconset
635
641
  return nameOnly ? iconset.name : iconset
636
642
  }
643
+
644
+ purgeCache = async () => {
645
+ const { getPluginDataDir } = this.app.bajo
646
+ const { fastGlob, fs } = this.app.lib
647
+ const dirs = await fastGlob(`${getPluginDataDir(this.ns)}/cache/req/*`, { onlyDirectories: true })
648
+ for (const dir of dirs) {
649
+ try {
650
+ const { mtimeMs } = await fs.stat(dir)
651
+ if (Date.now() - mtimeMs > this.config.purgeCache.reqTtlDur) await fs.remove(dir)
652
+ } catch (err) {}
653
+ }
654
+ }
637
655
  }
638
656
 
639
657
  return WaibuMpa
@@ -11,7 +11,19 @@ async function meta (options) {
11
11
  const { sprintf } = this.app.lib
12
12
  const { $, theme, req, locals } = options ?? {}
13
13
  const { page = {} } = locals
14
- let items = []
14
+ let items = [{
15
+ tag: 'meta',
16
+ name: 'req-id',
17
+ content: req.id
18
+ }]
19
+ const refreshDur = get(req, 'routeOptions.config.refreshDur', get(this, 'config.page.refreshDur', 0))
20
+ if (refreshDur > 0) {
21
+ items.push({
22
+ tag: 'meta',
23
+ httpEquiv: 'Refresh',
24
+ content: Math.floor(refreshDur / 60) + ''
25
+ })
26
+ }
15
27
  // meta
16
28
  for (const attr of keys(omit(locals.page, [...omitted]))) {
17
29
  if (!page[attr]) continue
@@ -1,7 +1,7 @@
1
1
  import path from 'path'
2
2
 
3
3
  export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefix, subRoute }) {
4
- const { defaultsDeep } = this.app.lib.aneka
4
+ const { defaultsDeep, parseObject } = this.app.lib.aneka
5
5
  const { importModule, readJson } = this.app.bajo
6
6
  const { isFunction, isPlainObject, pick, last, camelCase, omit } = this.app.lib._
7
7
  const { titleize } = this.app.lib.aneka
@@ -20,6 +20,12 @@ export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefi
20
20
  if (isFunction(mod)) mod = [{ handler: mod }]
21
21
  else if (isPlainObject(mod)) mod = [mod]
22
22
  for (let m of mod) {
23
+ const mhandler = m.handler // parseObject has problem with function
24
+ const murl = m.url
25
+ m = parseObject(omit(m, ['handler', 'url'], { parseValue: true }))
26
+ m.handler = mhandler
27
+ m.url = murl
28
+
23
29
  m.url = m.url ?? url
24
30
  if (isFunction(m.url)) m.url = await m.url.call(this)
25
31
  if (m.redirect) {
@@ -48,6 +54,7 @@ export async function build ({ files, pathPrefix, dir, ns, cfg, parent, urlPrefi
48
54
  m.config.xSite = m.xSite
49
55
  m.config.ns = ns
50
56
  m.config.subNs = ''
57
+ m.config.noCacheReq = m.noCacheReq
51
58
  m.config.title = m.title ?? camelCase(last(m.url.split('/')))
52
59
  m.config.subRoute = subRoute
53
60
  if (m.cache === true) m.cache = omit(me.config.page.cache, ['urls'])
@@ -280,7 +280,7 @@ async function componentFactory () {
280
280
  * @returns {Promise<string>} The built sentence.
281
281
  */
282
282
  buildSentence = async (sentence, params = {}, extra = {}) => {
283
- const { get } = this.app.lib._
283
+ const { get, merge } = this.app.lib._
284
284
  if (Array.isArray(sentence)) sentence = sentence.join(' ')
285
285
  const { minify, renderString } = this.app.waibuMpa
286
286
  if (extra.wrapped) sentence = '<w>' + sentence + '</w>'
@@ -292,7 +292,7 @@ async function componentFactory () {
292
292
  theme: get(this, 'theme.name', 'default'),
293
293
  iconset: get(this, 'iconset.name', 'default')
294
294
  }
295
- let html = await renderString(sentence, params, opts)
295
+ let html = await renderString(sentence, merge({}, this.locals, params), opts)
296
296
  if (extra.wrapped) html = html.slice(3, html.length - 4)
297
297
  if (extra.minify) html = await minify(html)
298
298
  return html
@@ -24,6 +24,7 @@ async function viewEngineFactory () {
24
24
  }
25
25
 
26
26
  renderString = async (content, locals = {}, opts = {}) => {
27
+ locals.tpl = null
27
28
  this._applySetting(locals, opts)
28
29
  return await this.app.bajoTemplate.renderString(content, locals, opts)
29
30
  }
package/lib/decorate.js CHANGED
@@ -19,10 +19,12 @@ async function isCacheable (req, cachedUrls) {
19
19
  }
20
20
 
21
21
  async function decorate () {
22
+ const { importModule, getPluginDataDir } = this.app.bajo
23
+ const buildLocals = await importModule('waibu:/lib/build-locals.js')
22
24
  const { importPkg } = this.app.bajo
23
- const { isString, cloneDeep, isEmpty } = this.app.lib._
25
+ const { isString, cloneDeep, isEmpty, get } = this.app.lib._
24
26
  const { get: getCache, set: setCache } = this.app.bajoCache ?? {}
25
- const { outmatch } = this.app.lib
27
+ const { outmatch, fs } = this.app.lib
26
28
  const { routePath } = this.app.waibu
27
29
  const mime = await importPkg('waibu:mime')
28
30
  const cfg = this.config
@@ -46,6 +48,7 @@ async function decorate () {
46
48
  mimeType += `; charset=${cfg.page.charset}`
47
49
  this.header('Content-Type', mimeType)
48
50
  this.header('Content-Language', this.request.lang)
51
+ this.header('X-Req-Id', this.request.id)
49
52
  opts.req = this.request
50
53
  opts.reply = this
51
54
  for (const item of ['theme', 'iconset']) {
@@ -60,7 +63,13 @@ async function decorate () {
60
63
  return cached
61
64
  }
62
65
  }
63
- const result = await me.render(tpl, params, opts)
66
+ const locals = await buildLocals.call(me, { tpl, params, opts })
67
+ if (!get(this.request, 'routeOptions.config.noCacheReq')) {
68
+ const dir = `${getPluginDataDir(me.ns)}/cache/req/${opts.req.id}`
69
+ fs.ensureDirSync(dir)
70
+ fs.writeFileSync(`${dir}/locals.json`, JSON.stringify(locals, null, 2), 'utf8')
71
+ }
72
+ const result = await me.render(tpl, locals, opts)
64
73
  if (ttl > 0) await setCache({ key, value: result, ttl })
65
74
  if (this.request.session) {
66
75
  ext = path.extname(this.request.url)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-mpa",
3
- "version": "2.15.2",
3
+ "version": "2.16.0",
4
4
  "description": "MPA support for Waibu Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-05-25
4
+
5
+ - [2.16.0] Add theme & iconset auto detection mechanism
6
+ - [2.16.0] Add page expiration & reload through ```routeOptions.config.refreshDur``` or ```config.page.refreshDur```
7
+
3
8
  ## 2026-05-22
4
9
 
5
10
  - [2.15.2] Bug fix in ```error-handler.js```