Skip to content

Commit 73c7452

Browse files
committed
global: Refactor data parsing bootstrap code
It only took a year, but finally figured out that vite provides a plugin/hook (configResolved()) that allows us to execute code AFTER the configuration has been fully parsed and BEFORE the page transformations start. This is the correct place to force full loading of the various data files, since they need to pre-scan all the files for, e.g., frontmatter data needed to do proper inter-page linking. This allows us to use core javascript async/await functionality, rather than the somewhat hackish previous method of using "import-sync" module.
1 parent bccb770 commit 73c7452

File tree

12 files changed

+109
-145
lines changed

12 files changed

+109
-145
lines changed

.vitepress/config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { pagefindPlugin } from 'vitepress-plugin-pagefind'
44
import { generateSidebar } from 'vitepress-sidebar'
55
import { dovecotMdExtend } from '../lib/markdown.js'
66
import { getExcludes } from '../lib/utility.js'
7+
import dovecotVitepressInit from '../lib/dovecot_vitepress_init.js'
78

89
const base = '/2.4'
910
const base_url = 'https://doc.dovecot.org'
@@ -62,7 +63,8 @@ export default defineConfig({
6263
chunkSizeWarningLimit: 1000,
6364
},
6465
plugins: [
65-
pagefindPlugin()
66+
pagefindPlugin(),
67+
dovecotVitepressInit()
6668
],
6769
},
6870

lib/data/doveadm.data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ async function normalizeDoveadm(doveadm) {
154154
export default addWatchPaths({
155155
async load() {
156156
return await normalizeDoveadm(
157-
structuredClone(loadData('doveadm').doveadm)
157+
structuredClone((await loadData('doveadm')).doveadm)
158158
)
159159
}
160160
})

lib/data/event_categories.data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ async function normalizeEventCategories(categories) {
1414
export default addWatchPaths({
1515
async load() {
1616
return await normalizeEventCategories(
17-
structuredClone(loadData('event_categories').categories)
17+
structuredClone((await loadData('event_categories')).categories)
1818
)
1919
}
2020
})

lib/data/event_reasons.data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ async function normalizeEventReasons(reasons) {
1414
export default addWatchPaths({
1515
async load() {
1616
return await normalizeEventReasons(
17-
structuredClone(loadData('event_reasons').reasons)
17+
structuredClone((await loadData('event_reasons')).reasons)
1818
)
1919
}
2020
})

lib/data/lua.data.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ async function normalizeLuaFunctions(lua) {
3131
/* Merge information from Dovecot settings. */
3232
if (v2.dovecot_setting) {
3333
if (!set) {
34-
set = structuredClone(loadData('settings').settings)
34+
set = structuredClone((await loadData('settings')).settings)
3535
}
3636

3737
if (!v2.type) {
@@ -84,7 +84,7 @@ async function normalizeLuaVariables(lua) {
8484

8585
export default addWatchPaths({
8686
async load() {
87-
const data = loadData('lua')
87+
const data = await loadData('lua')
8888

8989
return {
9090
constants: await normalizeLuaConstants(data.lua_constants),

lib/data/settings.data.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ async function normalizeSettings(settings) {
131131
export default addWatchPaths({
132132
async load() {
133133
return await normalizeSettings(
134-
structuredClone(loadData('settings').settings)
134+
structuredClone((await loadData('settings')).settings)
135135
)
136136
}
137137
})

lib/dovecot_vitepress_init.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { dovecotMdInit } from './markdown.js'
2+
3+
export default function dovecotVitepressInit() {
4+
return {
5+
name: 'dovecot-vitepress-init',
6+
async configResolved(config) {
7+
console.log('\n✅ Config resolved!')
8+
9+
/* We need to synchronously initialize markdown,
10+
* since we need to pre-populate various internal
11+
* tables (e.g. links). */
12+
await dovecotMdInit()
13+
console.log('\n✅ Markdown initialized!')
14+
},
15+
}
16+
}

lib/events.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ async function normalizeEvents(events, global_inherits, inherits) {
122122
}
123123

124124
export async function loadEvents() {
125-
const data = loadData('events')
125+
const data = await loadData('events')
126126
return await normalizeEvents(
127127
structuredClone(data.events),
128128
structuredClone(data.global_inherits),

lib/markdown.js

Lines changed: 63 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,56 @@ import path from 'path'
55
import { createMarkdownRenderer } from 'vitepress'
66
import { dovecotSetting, frontmatterIter, loadData } from './utility.js'
77

8-
export function dovecotMdExtend(md) {
8+
let md_conf = false
9+
export async function dovecotMdInit() {
10+
if (md_conf) {
11+
return md_conf
12+
}
13+
14+
md_conf = {
15+
doveadm: (await loadData('doveadm')).doveadm,
16+
events: (await loadData('events')).events,
17+
links: await (async () => {
18+
const links = {}
19+
const rewrites = globalThis.VITEPRESS_CONFIG.rewrites.map
20+
21+
frontmatterIter(Object.keys(rewrites), function (f, data) {
22+
if (!data.dovecotlinks) {
23+
return
24+
}
25+
26+
for (const [k, v] of Object.entries(data.dovecotlinks)) {
27+
if (links[k]) {
28+
throw new Error("Duplicate Dovecot Link key: " + k)
29+
}
30+
31+
links[k] = {
32+
url: resolveURL(rewrites[f].substring(0, rewrites[f].lastIndexOf('.')) + '.html')
33+
}
34+
35+
if ((typeof v) == 'object') {
36+
links[k].text = v.text
37+
if (v.hash) {
38+
links[k].url += '#' + v.hash
39+
}
40+
} else {
41+
links[k].text = v
42+
}
43+
}
44+
})
45+
46+
return {
47+
...links, ...((await loadData('links_overrides')).links_overrides)
48+
}
49+
})(),
50+
settings: (await loadData('settings')).settings,
51+
updates: (await loadData('updates')).updates
52+
}
53+
54+
return md_conf
55+
}
56+
57+
export async function dovecotMdExtend(md) {
958
md.use(containerPlugin, 'todo', {
1059
render: function(tokens, idx) {
1160
if (tokens[idx].nesting === 1) {
@@ -16,7 +65,7 @@ export function dovecotMdExtend(md) {
1665
}
1766
})
1867
md.use(deflistPlugin)
19-
md.use(dovecot_markdown)
68+
md.use(dovecot_markdown, await dovecotMdInit())
2069

2170
return md
2271
}
@@ -26,7 +75,7 @@ export async function getVitepressMd() {
2675
if (vitepress_md === null) {
2776
const config = globalThis.VITEPRESS_CONFIG
2877

29-
vitepress_md = dovecotMdExtend(await createMarkdownRenderer(
78+
vitepress_md = await dovecotMdExtend(await createMarkdownRenderer(
3079
config.srcDir,
3180
config.markdown,
3281
config.site.base,
@@ -40,7 +89,7 @@ export async function getVitepressMd() {
4089
/* This is a dovecot markdown extension to support the "[[...]]" syntax.
4190
* Much of this is copied from existing markdown-it plugins. See, e.g.,
4291
* https://github.com/markdown-it/markdown-it-sub/blob/master/index.mjs */
43-
function dovecot_markdown(md) {
92+
function dovecot_markdown(md, opts) {
4493
function process_brackets(state, silent) {
4594
const max = state.posMax
4695
const start = state.pos
@@ -130,8 +179,6 @@ function dovecot_markdown(md) {
130179
let page = mode
131180
switch (mode) {
132181
case 'doveadm':
133-
initDoveadm()
134-
135182
if (!opts.doveadm[env.inner]) {
136183
if (!Object.values(opts.doveadm).find((x) => (x.man == 'doveadm-' + env.inner))) {
137184
handle_error('doveadm link missing: ' + env.inner)
@@ -141,8 +188,6 @@ function dovecot_markdown(md) {
141188
break
142189

143190
case 'event':
144-
initEvents()
145-
146191
if (!opts.events[env.inner]) {
147192
handle_error('event link missing: ' + env.inner)
148193
return '<code><a>'
@@ -152,8 +197,6 @@ function dovecot_markdown(md) {
152197

153198
case 'setting':
154199
case 'setting_text':
155-
initSettings()
156-
157200
/* Settings names can have brackets, so we need to unescape
158201
* input for purposes of searching settings keys. */
159202
const search_str = env.inner.replaceAll('&gt;', '>')
@@ -175,14 +218,12 @@ function dovecot_markdown(md) {
175218
let url = '#'
176219
env.inner = false
177220

178-
initDovecotLinks()
179-
180-
if (!opts.dovecotlinks[parts[1]]) {
221+
if (!opts.links[parts[1]]) {
181222
handle_error('Dovecot link missing: ' + parts[1])
182223
return '<a>'
183224
}
184225

185-
const d = opts.dovecotlinks[parts[1]]
226+
const d = opts.links[parts[1]]
186227
env.inner = parts[2] ? parts[2] : (d.text ? d.text : false)
187228

188229
return '<a href="' + d.url + '">'
@@ -287,8 +328,6 @@ function dovecot_markdown(md) {
287328
case 'changed':
288329
case 'deprecated':
289330
case 'removed':
290-
initUpdates()
291-
292331
if (!opts.updates[env.args]) {
293332
handle_error('Missing updates entry for: ' + env.args)
294333
return env.args
@@ -369,56 +408,6 @@ function dovecot_markdown(md) {
369408
console.error(msg)
370409
}
371410

372-
function initDoveadm() {
373-
if (!opts.doveadm) {
374-
opts.doveadm = loadData('doveadm').doveadm
375-
}
376-
}
377-
378-
function initDovecotLinks() {
379-
if (opts.dovecotlinks) {
380-
return
381-
}
382-
383-
const links = {}
384-
const rewrites = globalThis.VITEPRESS_CONFIG.rewrites.map
385-
386-
frontmatterIter(Object.keys(rewrites), function (f, data) {
387-
if (!data.dovecotlinks) {
388-
return
389-
}
390-
391-
for (const [k, v] of Object.entries(data.dovecotlinks)) {
392-
if (links[k]) {
393-
throw new Error("Duplicate Dovecot Link key: " + k)
394-
}
395-
396-
links[k] = {
397-
url: resolveURL(rewrites[f].substring(0, rewrites[f].lastIndexOf('.')) + '.html')
398-
}
399-
400-
if ((typeof v) == 'object') {
401-
links[k].text = v.text
402-
if (v.hash) {
403-
links[k].url += '#' + v.hash
404-
}
405-
} else {
406-
links[k].text = v
407-
}
408-
}
409-
})
410-
411-
opts.dovecotlinks = {
412-
...links, ...(loadData('links_overrides').links_overrides)
413-
}
414-
}
415-
416-
function initEvents() {
417-
if (!opts.events) {
418-
opts.events = loadData('events').events
419-
}
420-
}
421-
422411
function initManFiles() {
423412
if (!opts.man) {
424413
opts.man = dovecotSetting('man_paths').flatMap((x) => {
@@ -444,37 +433,17 @@ function dovecot_markdown(md) {
444433
}
445434
}
446435

447-
function initSettings() {
448-
if (!opts.settings) {
449-
opts.settings = loadData('settings').settings
450-
}
451-
}
452-
453-
function initUpdates() {
454-
if (!opts.updates) {
455-
opts.updates = loadData('updates').updates
456-
}
457-
}
458-
459-
function resolveURL(url) {
460-
if (!('url_rewrite' in opts)) {
461-
opts.url_rewrite = dovecotSetting('url_rewrite')
462-
}
463-
464-
const new_url =
465-
(opts.base.endsWith('/') ? opts.base.slice(0, -1) : opts.base) +
466-
'/' + url
467-
return (opts.url_rewrite) ? opts.url_rewrite(new_url) : new_url
468-
}
469-
470-
const opts = {
471-
base: globalThis.VITEPRESS_CONFIG.site.base
472-
}
473-
474436
md.inline.ruler.after('emphasis', 'dovecot_brackets', process_brackets)
475437
md.renderer.rules.dovecot_open = dovecot_open
476438
md.renderer.rules.dovecot_body = dovecot_body
477439
md.renderer.rules.dovecot_close = dovecot_close
440+
}
478441

479-
opts.resolveURL = resolveURL
442+
export function resolveURL(url) {
443+
const base = globalThis.VITEPRESS_CONFIG.site.base
444+
const url_rewrite = dovecotSetting('url_rewrite')
445+
const new_url =
446+
(base.endsWith('/') ? base.slice(0, -1) : base) +
447+
'/' + url
448+
return (url_rewrite) ? url_rewrite(new_url) : new_url
480449
}

lib/utility.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import fg from 'fast-glob'
44
import fs from 'fs'
55
import matter from 'gray-matter'
6-
import importSync from 'import-sync'
76
import { dirname } from 'path'
87
import { fileURLToPath } from 'url'
98

@@ -27,16 +26,21 @@ export function normalizeArrayData(data, keys) {
2726
return data
2827
}
2928

30-
export function loadData(id) {
31-
const path = globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.data_paths?.[id]
32-
?? ('../data/' + id + '.js')
29+
const dataOb = {}
30+
export async function loadData(id) {
31+
if (!dataOb[id]) {
32+
const path = globalThis.VITEPRESS_CONFIG.userConfig.themeConfig.dovecot?.data_paths?.[id]
33+
?? ('../data/' + id + '.js')
3334

34-
try {
35-
return importSync(__dirname + '/' + path)
36-
} catch (e) {
37-
throw new Error('Unable to import module (' + __dirname + '/' +
38-
path + '):' + e)
35+
try {
36+
dataOb[id] = await import(__dirname + '/' + path)
37+
} catch (e) {
38+
throw new Error('Unable to import module (' + __dirname + '/' +
39+
path + '):' + e)
40+
}
3941
}
42+
43+
return dataOb[id]
4044
}
4145

4246
function _dovecotSetting(name, setting) {

0 commit comments

Comments
 (0)