methanol 0.0.7 → 0.0.8

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.
package/src/pages.js CHANGED
@@ -23,6 +23,7 @@ import { readdir, readFile, stat } from 'fs/promises'
23
23
  import { existsSync } from 'fs'
24
24
  import { resolve, join, relative } from 'path'
25
25
  import { state, cli } from './state.js'
26
+ import { withBase } from './config.js'
26
27
  import { compilePageMdx } from './mdx.js'
27
28
  import { createStageLogger } from './stage-logger.js'
28
29
 
@@ -36,12 +37,11 @@ const pageDerivedCache = new Map()
36
37
  const collectLanguagesFromPages = (pages = []) => {
37
38
  const languages = new Map()
38
39
  for (const page of pages) {
39
- if (!page?.isIndex) continue
40
- const label = page?.frontmatter?.lang
40
+ if (!page.isIndex) continue
41
+ const label = page.frontmatter?.lang
41
42
  if (label == null || label === '') continue
42
43
  const routePath = page.routePath || '/'
43
- const href = page.routeHref || routePath || '/'
44
- const frontmatterCode = page?.frontmatter?.langCode
44
+ const frontmatterCode = page.frontmatter?.langCode
45
45
  const code =
46
46
  typeof frontmatterCode === 'string' && frontmatterCode.trim()
47
47
  ? frontmatterCode.trim()
@@ -50,19 +50,39 @@ const collectLanguagesFromPages = (pages = []) => {
50
50
  : routePath.replace(/^\/+/, '')
51
51
  languages.set(routePath, {
52
52
  routePath,
53
- href,
53
+ routeHref: withBase(routePath),
54
54
  label: String(label),
55
55
  code: code || null
56
56
  })
57
57
  }
58
- return Array.from(languages.values()).sort((a, b) => a.href.localeCompare(b.href))
58
+ return Array.from(languages.values()).sort((a, b) => a.routePath.localeCompare(b.routePath))
59
59
  }
60
60
 
61
61
  const normalizeRoutePath = (value) => {
62
+ if (!value) return '/'
63
+ let normalized = value
64
+ if (!normalized.startsWith('/')) {
65
+ normalized = `/${normalized}`
66
+ }
67
+ normalized = normalized.replace(/\/{2,}/g, '/')
68
+ if (normalized === '/') return '/'
69
+ if (normalized.endsWith('/')) {
70
+ return normalized.replace(/\/+$/, '/')
71
+ }
72
+ return normalized
73
+ }
74
+
75
+ const stripTrailingSlash = (value) => {
62
76
  if (!value || value === '/') return '/'
63
77
  return value.replace(/\/+$/, '')
64
78
  }
65
79
 
80
+ const toRoutePrefix = (value) => {
81
+ const normalized = normalizeRoutePath(value)
82
+ const stripped = stripTrailingSlash(normalized)
83
+ return stripped === '/' ? '' : stripped
84
+ }
85
+
66
86
  const resolveLanguageForRoute = (languages = [], routePath = '/') => {
67
87
  if (!languages.length) return null
68
88
  const normalizedRoute = normalizeRoutePath(routePath)
@@ -70,16 +90,17 @@ const resolveLanguageForRoute = (languages = [], routePath = '/') => {
70
90
  let bestLength = -1
71
91
  let rootLanguage = null
72
92
  for (const lang of languages) {
73
- const base = normalizeRoutePath(lang?.routePath || lang?.href)
93
+ const base = normalizeRoutePath(lang.routePath)
94
+ const basePrefix = toRoutePrefix(base)
74
95
  if (!base) continue
75
96
  if (base === '/') {
76
97
  rootLanguage = lang
77
98
  continue
78
99
  }
79
- if (normalizedRoute === base || normalizedRoute.startsWith(`${base}/`)) {
80
- if (base.length > bestLength) {
100
+ if (normalizedRoute === base || (basePrefix && normalizedRoute.startsWith(`${basePrefix}/`))) {
101
+ if (basePrefix.length > bestLength) {
81
102
  best = lang
82
- bestLength = base.length
103
+ bestLength = basePrefix.length
83
104
  }
84
105
  }
85
106
  }
@@ -104,7 +125,7 @@ export const routePathFromFile = (filePath, pagesDir = state.PAGES_DIR) => {
104
125
  return '/'
105
126
  }
106
127
  if (normalized.endsWith('/index')) {
107
- return `/${normalized.slice(0, -'/index'.length)}`
128
+ return `/${normalized.slice(0, -'/index'.length)}/`
108
129
  }
109
130
  return `/${normalized}`
110
131
  }
@@ -162,7 +183,8 @@ const stripRootPrefix = (value, rootDir) => {
162
183
 
163
184
  const buildPagesTree = (pages, options = {}) => {
164
185
  const rootPath = normalizeRoutePath(options.rootPath || '/')
165
- const rootDir = rootPath === '/' ? '' : rootPath.replace(/^\/+/, '')
186
+ const rootPrefix = toRoutePrefix(rootPath)
187
+ const rootDir = rootPrefix ? rootPrefix.replace(/^\/+/, '') : ''
166
188
  const includeHiddenRoot = Boolean(options.includeHiddenRoot)
167
189
  const currentRoutePath = normalizeRoutePath(options.currentRoutePath || '/')
168
190
  const rootSegments = rootDir ? rootDir.split('/') : []
@@ -170,8 +192,8 @@ const buildPagesTree = (pages, options = {}) => {
170
192
  if (!routePath) return '/'
171
193
  if (!rootDir) return routePath
172
194
  if (routePath === rootPath) return '/'
173
- if (routePath.startsWith(`${rootPath}/`)) {
174
- const stripped = routePath.slice(rootPath.length)
195
+ if (rootPrefix && routePath.startsWith(`${rootPrefix}/`)) {
196
+ const stripped = routePath.slice(rootPrefix.length)
175
197
  return stripped.startsWith('/') ? stripped : `/${stripped}`
176
198
  }
177
199
  return routePath
@@ -179,7 +201,7 @@ const buildPagesTree = (pages, options = {}) => {
179
201
  const currentRouteWithinRoot = resolveRouteWithinRoot(currentRoutePath)
180
202
  const isUnderRoot = (page) => {
181
203
  if (!rootDir) return true
182
- return page.routePath === rootPath || page.routePath.startsWith(`${rootPath}/`)
204
+ return page.routePath === rootPath || (rootPrefix && page.routePath.startsWith(`${rootPrefix}/`))
183
205
  }
184
206
  const treePages = pages
185
207
  .filter((page) => !page.isInternal)
@@ -249,10 +271,10 @@ const buildPagesTree = (pages, options = {}) => {
249
271
  children: [],
250
272
  depth,
251
273
  routePath: null,
274
+ routeHref: null,
252
275
  title: null,
253
276
  weight: null,
254
277
  date: null,
255
- routeHref: null,
256
278
  isRoot: false
257
279
  }
258
280
  dirs.set(path, dir)
@@ -306,7 +328,7 @@ const buildPagesTree = (pages, options = {}) => {
306
328
  if (page.isIndex && page.dir) {
307
329
  const dir = getDirNode(page.dir, page.dir.split('/').pop(), page.depth)
308
330
  dir.routePath = page.routePath
309
- dir.routeHref = page.routeHref || page.routePath
331
+ dir.routeHref = page.routeHref
310
332
  dir.title = page.title
311
333
  dir.weight = page.weight ?? null
312
334
  dir.date = page.date ?? null
@@ -381,9 +403,8 @@ const walkPages = async function* (dir, basePath = '') {
381
403
  const name = entry.replace(/\.(mdx|md)$/, '')
382
404
  const relativePath = join(basePath, name).replace(/\\/g, '/')
383
405
  const isIndex = name === 'index'
384
- const routePath = isIndex ? (basePath ? `/${basePath}` : '/') : `/${relativePath}`
385
- const routeHref = isIndex && basePath ? `/${basePath}/` : routePath
386
- yield { routePath, routeHref, filePath: fullPath, isIndex }
406
+ const routePath = isIndex ? (basePath ? `/${basePath}/` : '/') : `/${relativePath}`
407
+ yield { routePath, filePath: fullPath, isIndex }
387
408
  }
388
409
 
389
410
  for (const { entry, fullPath } of dirs) {
@@ -400,7 +421,6 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
400
421
  const dir = name.split('/').slice(0, -1).join('/')
401
422
  const dirName = dir ? dir.split('/').pop() : ''
402
423
  const isIndex = baseName === 'index'
403
- const routeHref = isIndex && dir ? `/${dir}/` : routePath
404
424
  const segments = routePath.split('/').filter(Boolean)
405
425
  const stats = await stat(filePath)
406
426
  const cached = pageMetadataCache.get(filePath)
@@ -423,7 +443,7 @@ export const buildPageEntry = async ({ filePath, pagesDir, source }) => {
423
443
  : isNotFoundPage || Boolean(metadata.frontmatter?.isRoot)
424
444
  return {
425
445
  routePath,
426
- routeHref,
446
+ routeHref: withBase(routePath),
427
447
  filePath,
428
448
  source,
429
449
  relativePath: relPath,
@@ -466,7 +486,6 @@ const collectPagesFromDir = async (pagesDir, source) => {
466
486
  source
467
487
  })
468
488
  if (entry) {
469
- entry.routeHref = page.routeHref || entry.routeHref
470
489
  entry.isIndex = page.isIndex || entry.isIndex
471
490
  pages.push(entry)
472
491
  }
@@ -529,15 +548,22 @@ const buildIndexFallback = (pages, siteName) => {
529
548
  return lines.join('\n')
530
549
  }
531
550
 
532
- const resolveRootPath = (routePath, pagesByRoute, pagesByRouteIndex = null) => {
551
+ const resolveRootPath = (routePath, pagesByRoute) => {
533
552
  const normalized = normalizeRoutePath(routePath || '/')
534
553
  const segments = normalized.split('/').filter(Boolean)
535
- const lookup = pagesByRouteIndex || pagesByRoute
536
554
  for (let i = segments.length; i >= 1; i--) {
537
555
  const candidate = `/${segments.slice(0, i).join('/')}`
538
- const page = lookup.get(candidate)
556
+ const page = pagesByRoute.get(candidate)
539
557
  if (page?.isIndex && page?.isRoot) {
540
- return candidate
558
+ return page.routePath
559
+ }
560
+ const isExact = normalized === candidate
561
+ if (!isExact) {
562
+ const indexCandidate = candidate === '/' ? '/' : `${candidate}/`
563
+ const indexPage = pagesByRoute.get(indexCandidate)
564
+ if (indexPage?.isIndex && indexPage?.isRoot) {
565
+ return indexPage.routePath
566
+ }
541
567
  }
542
568
  }
543
569
  return '/'
@@ -547,7 +573,7 @@ const buildNavSequence = (nodes, pagesByRoute) => {
547
573
  const result = []
548
574
  const seen = new Set()
549
575
  const addEntry = (entry) => {
550
- if (!entry?.routePath) return
576
+ if (!entry.routePath) return
551
577
  const key = entry.filePath || entry.routePath
552
578
  if (seen.has(key)) return
553
579
  seen.add(key)
@@ -587,54 +613,42 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
587
613
  const hasIndex = pages.some((page) => page.routePath === '/')
588
614
  if (!hasIndex) {
589
615
  const content = buildIndexFallback(pages, state.SITE_NAME)
590
- pages = [
591
- {
592
- routePath: '/',
593
- routeHref: '/',
594
- filePath: resolve(state.PAGES_DIR, 'index.md'),
595
- relativePath: 'index.md',
596
- name: 'index',
597
- dir: '',
598
- segments: [],
599
- depth: 0,
600
- isIndex: true,
601
- isInternal: false,
602
- title: state.SITE_NAME || 'Methanol Site',
603
- weight: null,
604
- date: null,
605
- isRoot: false,
606
- hidden: false,
607
- content,
608
- frontmatter: {},
609
- matter: null,
610
- stats: { size: content.length, createdAt: null, updatedAt: null },
611
- createdAt: null,
612
- updatedAt: null
613
- },
614
- ...pages
615
- ]
616
+ pages.unshift({
617
+ routePath: '/',
618
+ routeHref: withBase('/'),
619
+ filePath: resolve(state.PAGES_DIR, 'index.md'),
620
+ relativePath: 'index.md',
621
+ name: 'index',
622
+ dir: '',
623
+ segments: [],
624
+ depth: 0,
625
+ isIndex: true,
626
+ isInternal: false,
627
+ title: state.SITE_NAME || 'Methanol Site',
628
+ weight: null,
629
+ date: null,
630
+ isRoot: false,
631
+ hidden: false,
632
+ content,
633
+ frontmatter: {},
634
+ matter: null,
635
+ stats: { size: content.length, createdAt: null, updatedAt: null },
636
+ createdAt: null,
637
+ updatedAt: null
638
+ })
616
639
  if (excludedRoutes?.has('/')) {
617
640
  excludedRoutes.delete('/')
618
641
  }
619
642
  }
620
643
 
621
644
  const pagesByRoute = new Map()
622
- const pagesByRouteIndex = new Map()
623
645
  for (const page of pages) {
624
- if (page.isIndex) {
625
- pagesByRouteIndex.set(page.routePath, page)
626
- if (!pagesByRoute.has(page.routePath)) {
627
- pagesByRoute.set(page.routePath, page)
628
- }
629
- continue
630
- }
631
- const existing = pagesByRoute.get(page.routePath)
632
- if (!existing || existing.isIndex) {
646
+ if (!pagesByRoute.has(page.routePath)) {
633
647
  pagesByRoute.set(page.routePath, page)
634
648
  }
635
649
  }
636
650
  const getPageByRoute = (routePath, options = {}) => {
637
- const { filePath, preferIndex } = options || {}
651
+ const { filePath } = options || {}
638
652
  if (filePath) {
639
653
  for (const page of pages) {
640
654
  if (page.routePath === routePath && page.filePath === filePath) {
@@ -642,18 +656,12 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
642
656
  }
643
657
  }
644
658
  }
645
- if (preferIndex === true) {
646
- return pagesByRouteIndex.get(routePath) || pagesByRoute.get(routePath) || null
647
- }
648
- if (preferIndex === false) {
649
- return pagesByRoute.get(routePath) || pagesByRouteIndex.get(routePath) || null
650
- }
651
- return pagesByRoute.get(routePath) || pagesByRouteIndex.get(routePath) || null
659
+ return pagesByRoute.get(routePath) || null
652
660
  }
653
661
  const pagesTreeCache = new Map()
654
662
  const navSequenceCache = new Map()
655
663
  const getPagesTree = (routePath) => {
656
- const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
664
+ const rootPath = resolveRootPath(routePath, pagesByRoute)
657
665
  const normalizedRoute = normalizeRoutePath(routePath || '/')
658
666
  const cacheKey = `${rootPath}::${normalizedRoute}`
659
667
  if (pagesTreeCache.has(cacheKey)) {
@@ -668,7 +676,7 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
668
676
  return tree
669
677
  }
670
678
  const getNavSequence = (routePath) => {
671
- const rootPath = resolveRootPath(routePath, pagesByRoute, pagesByRouteIndex)
679
+ const rootPath = resolveRootPath(routePath, pagesByRoute)
672
680
  const normalizedRoute = normalizeRoutePath(routePath || '/')
673
681
  const cacheKey = `${rootPath}::${normalizedRoute}`
674
682
  if (navSequenceCache.has(cacheKey)) {
@@ -683,8 +691,10 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
683
691
  const notFound = pagesByRoute.get('/404') || null
684
692
  const languages = collectLanguagesFromPages(pages)
685
693
  const userSite = state.USER_SITE || {}
694
+ const siteBase = state.VITE_BASE ?? userSite.base ?? null
686
695
  const site = {
687
696
  ...userSite,
697
+ base: siteBase,
688
698
  name: state.SITE_NAME,
689
699
  root: state.ROOT_DIR,
690
700
  pagesDir: state.PAGES_DIR,
@@ -703,7 +713,6 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
703
713
  const pagesContext = {
704
714
  pages,
705
715
  pagesByRoute,
706
- pagesByRouteIndex,
707
716
  getPageByRoute,
708
717
  pagesTree,
709
718
  getPagesTree,
@@ -737,7 +746,7 @@ export const buildPagesContext = async ({ compileAll = true } = {}) => {
737
746
  if (!entry) return null
738
747
  return {
739
748
  routePath: entry.routePath,
740
- routeHref: entry.routeHref || entry.routePath,
749
+ routeHref: entry.routeHref,
741
750
  title: entry.title || entry.name || entry.routePath,
742
751
  filePath: entry.filePath || null
743
752
  }
@@ -28,8 +28,8 @@ import { methanolPreviewRoutingPlugin } from './vite-plugins.js'
28
28
  export const runVitePreview = async () => {
29
29
  const baseConfig = {
30
30
  configFile: false,
31
+ appType: 'mpa',
31
32
  root: state.PAGES_DIR,
32
- base: '/',
33
33
  build: {
34
34
  outDir: state.DIST_DIR
35
35
  }
@@ -27,7 +27,6 @@ import { state } from '../state.js'
27
27
  const extensionRegex = /\.(mdx?|html)$/i
28
28
 
29
29
  const isRelativeHref = (href) => {
30
- if (typeof href !== 'string') return false
31
30
  if (!href) return false
32
31
  if (href.startsWith('#') || href.startsWith('?') || href.startsWith('/')) return false
33
32
  if (href.startsWith('//')) return false
package/src/state.js CHANGED
@@ -148,6 +148,8 @@ export const state = {
148
148
  PROJECT_ROOT,
149
149
  ROOT_DIR: PROJECT_ROOT,
150
150
  SITE_NAME: 'Methanol Site',
151
+ SITE_BASE: null,
152
+ VITE_BASE: null,
151
153
  PAGES_DIR: resolve(PROJECT_ROOT, 'pages'),
152
154
  COMPONENTS_DIR: resolve(PROJECT_ROOT, 'components'),
153
155
  STATIC_DIR: resolve(PROJECT_ROOT, 'public'),
package/src/utils.js ADDED
@@ -0,0 +1,9 @@
1
+ export const cached = (fn) => {
2
+ let cache = null
3
+ return () => (cache ?? (cache = fn()))
4
+ }
5
+
6
+ export const cachedStr = (fn) => {
7
+ const cache = Object.create(null)
8
+ return (key) => (cache[key] ?? (cache[key] = fn(key)))
9
+ }
@@ -21,10 +21,9 @@
21
21
  import { createDOMRenderer } from 'refui/dom'
22
22
  import { defaults } from 'refui/browser'
23
23
  import { lazy } from 'refui'
24
- import { init } from '/.methanol_virtual_module/loader.js'
25
-
26
- const reg = import('/.methanol_virtual_module/registry.js')
24
+ import { init } from 'methanol:loader'
25
+ import { registry } from 'methanol:registry'
27
26
 
28
27
  const R = createDOMRenderer(defaults)
29
28
 
30
- reg.then((m) => init(m.registry, R))
29
+ init(registry, R)
@@ -22,6 +22,27 @@ let pagefindInit = null
22
22
  let pagefindUiInit = null
23
23
  let pagefindUiReady = false
24
24
 
25
+ const resolveBasePrefix = () => {
26
+ let base = import.meta.env?.BASE_URL || '/'
27
+ if (!base || base === '/' || base === './') return ''
28
+ if (base.startsWith('http://') || base.startsWith('https://')) {
29
+ try {
30
+ base = new URL(base).pathname
31
+ } catch {
32
+ return ''
33
+ }
34
+ }
35
+ if (!base.startsWith('/')) return ''
36
+ if (base.endsWith('/')) base = base.slice(0, -1)
37
+ return base
38
+ }
39
+
40
+ const withBase = (path) => {
41
+ const prefix = resolveBasePrefix()
42
+ if (!prefix || path.startsWith(`${prefix}/`)) return path
43
+ return `${prefix}${path}`
44
+ }
45
+
25
46
  const dynamicImport = (path) => {
26
47
  try {
27
48
  const importer = new Function('p', 'return import(p)')
@@ -38,7 +59,7 @@ export const loadPagefind = async () => {
38
59
  resolve(null)
39
60
  return
40
61
  }
41
- dynamicImport('/pagefind/pagefind.js')
62
+ dynamicImport(withBase('/pagefind/pagefind.js'))
42
63
  .then((mod) => {
43
64
  if (!mod) return resolve(null)
44
65
  if (mod.search) return resolve(mod)
@@ -89,7 +110,7 @@ export const loadPagefindUI = async (options = {}) => {
89
110
  return
90
111
  }
91
112
  const script = document.createElement('script')
92
- script.src = '/pagefind/pagefind-ui.js'
113
+ script.src = withBase('/pagefind/pagefind-ui.js')
93
114
  script.async = true
94
115
  script.onload = () => done(initPagefindUI(options))
95
116
  script.onerror = () => done(false)
@@ -24,8 +24,9 @@ import { existsSync } from 'node:fs'
24
24
  import { resolve } from 'node:path'
25
25
  import { normalizePath } from 'vite'
26
26
  import { state } from './state.js'
27
+ import { resolveBasePrefix } from './config.js'
27
28
  import { genRegistryScript } from './components.js'
28
- import { INJECT_SCRIPT, LOADER_SCRIPT, PAGEFIND_SCRIPT } from './assets.js'
29
+ import { INJECT_SCRIPT, LOADER_SCRIPT, PAGEFIND_LOADER_SCRIPT } from './assets.js'
29
30
  import { projectRequire } from './node-loader.js'
30
31
 
31
32
  const require = createRequire(import.meta.url)
@@ -60,6 +61,9 @@ export const methanolPreviewRoutingPlugin = (distDir, notFoundPath) => ({
60
61
  cachedHtml = await readFile(notFoundPath, 'utf-8')
61
62
  return cachedHtml
62
63
  }
64
+
65
+ const basePrefix = resolveBasePrefix()
66
+
63
67
  const handler = async (req, res, next) => {
64
68
  if (!req.url || req.method !== 'GET') {
65
69
  return next()
@@ -69,6 +73,13 @@ export const methanolPreviewRoutingPlugin = (distDir, notFoundPath) => ({
69
73
  try {
70
74
  pathname = new URL(req.url, 'http://methanol').pathname
71
75
  pathname = decodeURIComponent(pathname)
76
+ if (basePrefix) {
77
+ if (pathname.startsWith(basePrefix)) {
78
+ pathname = pathname.slice(basePrefix.length)
79
+ } else {
80
+ return next()
81
+ }
82
+ }
72
83
  } catch {}
73
84
  const hasTrailingSlash = pathname.endsWith('/') && pathname !== '/'
74
85
  if (pathname.includes('.') && !pathname.endsWith('.html')) {
@@ -109,20 +120,43 @@ export const methanolPreviewRoutingPlugin = (distDir, notFoundPath) => ({
109
120
 
110
121
  const virtualModulePrefix = '/.methanol_virtual_module/'
111
122
  const resolvedVirtualModulePrefix = '\0' + virtualModulePrefix
123
+ const virtualModuleScheme = 'methanol:'
112
124
 
113
125
  const virtualModuleMap = {
126
+ get registry() {
127
+ return `export const registry = ${genRegistryScript()}`
128
+ },
114
129
  get 'registry.js'() {
115
130
  return `export const registry = ${genRegistryScript()}`
116
131
  },
117
- 'loader.js': LOADER_SCRIPT,
132
+ loader: LOADER_SCRIPT,
118
133
  'inject.js': INJECT_SCRIPT,
119
- 'pagefind.js': PAGEFIND_SCRIPT
134
+ 'pagefind-loader': PAGEFIND_LOADER_SCRIPT
120
135
  }
121
136
 
122
137
  const getModuleIdSegment = (id, start) => {
123
138
  return new URL(id.slice(start), 'http://methanol').pathname.slice(1)
124
139
  }
125
140
 
141
+ const getSchemeModuleKey = (id) => {
142
+ if (!id.startsWith(virtualModuleScheme)) return null
143
+ return id.slice(virtualModuleScheme.length)
144
+ }
145
+
146
+ const resolveVirtualModuleId = (id) => {
147
+ if (id.startsWith(virtualModulePrefix)) {
148
+ return id
149
+ }
150
+ const basePrefix = resolveBasePrefix()
151
+ if (basePrefix) {
152
+ const prefixed = `${basePrefix}${virtualModulePrefix}`
153
+ if (id.startsWith(prefixed)) {
154
+ return id.slice(basePrefix.length)
155
+ }
156
+ }
157
+ return null
158
+ }
159
+
126
160
  export const methanolResolverPlugin = () => {
127
161
  return {
128
162
  name: 'methanol-resolver',
@@ -139,10 +173,16 @@ export const methanolResolverPlugin = () => {
139
173
  return require.resolve(id)
140
174
  }
141
175
 
142
- if (id.startsWith(virtualModulePrefix)) {
143
- const _moduleId = getModuleIdSegment(id, virtualModulePrefix.length)
176
+ const schemeKey = getSchemeModuleKey(id)
177
+ if (schemeKey && Object.prototype.hasOwnProperty.call(virtualModuleMap, schemeKey)) {
178
+ return '\0' + id
179
+ }
180
+
181
+ const virtualId = resolveVirtualModuleId(id)
182
+ if (virtualId) {
183
+ const _moduleId = getModuleIdSegment(virtualId, virtualModulePrefix.length)
144
184
  if (Object.prototype.hasOwnProperty.call(virtualModuleMap, _moduleId)) {
145
- return '\0' + id
185
+ return '\0' + virtualId
146
186
  }
147
187
  }
148
188
 
@@ -164,6 +204,10 @@ export const methanolResolverPlugin = () => {
164
204
  }
165
205
  },
166
206
  load(id) {
207
+ if (id.startsWith('\0' + virtualModuleScheme)) {
208
+ const key = id.slice(1 + virtualModuleScheme.length)
209
+ return virtualModuleMap[key]
210
+ }
167
211
  if (id.startsWith(resolvedVirtualModulePrefix)) {
168
212
  const _moduleId = getModuleIdSegment(id, resolvedVirtualModulePrefix.length)
169
213
  return virtualModuleMap[_moduleId]
@@ -20,7 +20,13 @@
20
20
 
21
21
  import { signal, $, t, If, For, onCondition } from 'refui'
22
22
  import { createPortal } from 'refui/extras'
23
- import { loadPagefind } from '/.methanol_virtual_module/pagefind.js'
23
+
24
+ let pagefindModule = null
25
+ const loadPagefindModule = async () => {
26
+ if (pagefindModule) return pagefindModule
27
+ pagefindModule = import('methanol:pagefind-loader')
28
+ return pagefindModule
29
+ }
24
30
 
25
31
  let keybindReady = false
26
32
  let cachedPagefind = null
@@ -35,7 +41,8 @@ const resolveShortcutLabel = () => {
35
41
 
36
42
  const ensurePagefind = async (options) => {
37
43
  if (cachedPagefind) return cachedPagefind
38
- const pagefind = await loadPagefind()
44
+ const module = await loadPagefindModule()
45
+ const pagefind = await module?.loadPagefind?.()
39
46
  if (!pagefind) return null
40
47
  if (pagefind.options) {
41
48
  const nextOptions = { excerptLength: 30, ...(options || {}) }