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/README.md +5 -0
- package/package.json +1 -1
- package/src/assets.js +1 -1
- package/src/build-system.js +2 -1
- package/src/config.js +122 -4
- package/src/dev-server.js +49 -27
- package/src/main.js +3 -1
- package/src/mdx.js +31 -32
- package/src/pages.js +85 -76
- package/src/preview-server.js +1 -1
- package/src/rehype-plugins/link-resolve.js +0 -1
- package/src/state.js +2 -0
- package/src/utils.js +9 -0
- package/src/virtual-module/inject.js +3 -4
- package/src/virtual-module/{pagefind.js → pagefind-loader.js} +23 -2
- package/src/vite-plugins.js +50 -6
- package/themes/default/components/ThemeSearchBox.client.jsx +9 -2
- package/themes/default/page.jsx +54 -26
- package/themes/default/sources/style.css +5 -1
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
|
|
40
|
-
const label = page
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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(`${
|
|
80
|
-
if (
|
|
100
|
+
if (normalizedRoute === base || (basePrefix && normalizedRoute.startsWith(`${basePrefix}/`))) {
|
|
101
|
+
if (basePrefix.length > bestLength) {
|
|
81
102
|
best = lang
|
|
82
|
-
bestLength =
|
|
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
|
|
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(`${
|
|
174
|
-
const stripped = routePath.slice(
|
|
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(`${
|
|
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
|
|
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}
|
|
385
|
-
|
|
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
|
|
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 =
|
|
556
|
+
const page = pagesByRoute.get(candidate)
|
|
539
557
|
if (page?.isIndex && page?.isRoot) {
|
|
540
|
-
return
|
|
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
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
749
|
+
routeHref: entry.routeHref,
|
|
741
750
|
title: entry.title || entry.name || entry.routePath,
|
|
742
751
|
filePath: entry.filePath || null
|
|
743
752
|
}
|
package/src/preview-server.js
CHANGED
|
@@ -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
|
@@ -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 '
|
|
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
|
-
|
|
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)
|
package/src/vite-plugins.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
132
|
+
loader: LOADER_SCRIPT,
|
|
118
133
|
'inject.js': INJECT_SCRIPT,
|
|
119
|
-
'pagefind
|
|
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
|
-
|
|
143
|
-
|
|
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' +
|
|
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
|
-
|
|
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
|
|
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 || {}) }
|