methanol 0.0.21 → 0.0.22

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/client/sw.js CHANGED
@@ -18,39 +18,16 @@
18
18
  * under the License.
19
19
  */
20
20
 
21
- import { clientsClaim } from 'workbox-core'
22
- import { registerRoute } from 'workbox-routing'
23
21
  import { cached, cachedStr } from '../utils.js'
22
+ import { normalizeBasePrefix } from '../base.js'
24
23
 
25
- const __WB_MANIFEST = self.__WB_MANIFEST
26
- const BATCH_SIZE = 5
24
+ const MANIFEST_HASH = '__METHANOL_MANIFEST_HASH__'
25
+ const DEFAULT_BATCH_SIZE = 5
27
26
  const REVISION_HEADER = 'X-Methanol-Revision'
28
27
 
29
28
  self.skipWaiting()
30
- clientsClaim()
31
29
 
32
- const resolveBasePrefix = cached(() => {
33
- let base = import.meta.env?.BASE_URL || '/'
34
- if (!base || base === '/' || base === './') return ''
35
- if (base.startsWith('http://') || base.startsWith('https://')) {
36
- try {
37
- base = new URL(base).pathname
38
- } catch {
39
- return ''
40
- }
41
- }
42
- if (!base.startsWith('/')) return ''
43
- if (base.endsWith('/')) base = base.slice(0, -1)
44
- return base
45
- })
46
-
47
- const resolveCurrentBasePrefix = cached(() => {
48
- const prefix = resolveBasePrefix()
49
- if (!prefix) {
50
- return self.location.origin
51
- }
52
- return new URL(prefix, self.location.origin).href
53
- })
30
+ const resolveBasePrefix = cached(() => normalizeBasePrefix(import.meta.env?.BASE_URL || '/'))
54
31
 
55
32
  const withBase = cachedStr((path) => {
56
33
  const prefix = resolveBasePrefix()
@@ -58,73 +35,93 @@ const withBase = cachedStr((path) => {
58
35
  return `${prefix}${path}`
59
36
  })
60
37
 
38
+ const MANIFEST_URL = withBase('/precache-manifest.json')
39
+
61
40
  const NOT_FOUND_URL = new URL(withBase('/404.html'), self.location.origin).href.toLowerCase()
62
41
  const OFFLINE_FALLBACK_URL = new URL(withBase('/offline.html'), self.location.origin).href.toLowerCase()
63
42
 
64
43
  const PAGES_CACHE = withBase(':methanol-pages-swr')
65
44
  const ASSETS_CACHE = withBase(':methanol-assets-swr')
66
45
 
67
- const stripBase = cachedStr((url) => {
68
- const base = resolveCurrentBasePrefix()
69
- if (!base) return url
70
- if (url === base) return '/'
71
- if (url.startsWith(`${base}/`)) return url.slice(base.length)
72
- return url
73
- })
74
46
 
75
- function isRootOrAssets(url) {
76
- const basePath = stripBase(url)
77
- if (basePath.startsWith('/assets/')) return true
78
- const trimmed = basePath.startsWith('/') ? basePath.slice(1) : basePath
79
- return trimmed !== '' && !trimmed.includes('/')
47
+ let manifestCache = null
48
+ let manifestPromise = null
49
+ let manifestIndexCache = null
50
+ let manifestIndexPromise = null
51
+
52
+ const resolveManifestUrl = () => {
53
+ if (!MANIFEST_HASH || MANIFEST_HASH === '__METHANOL_MANIFEST_HASH__') return MANIFEST_URL
54
+ return `${MANIFEST_URL}?v=${MANIFEST_HASH}`
55
+ }
56
+
57
+ async function loadManifest(force = false) {
58
+ if (manifestCache && !force) return manifestCache
59
+ if (manifestPromise && !force) return manifestPromise
60
+ manifestPromise = (async () => {
61
+ if (!force) {
62
+ const cached = await idbGet(KEY_MANIFEST_DATA).catch(() => null)
63
+ if (cached && cached.entries) {
64
+ if (!cached.hash || cached.hash === MANIFEST_HASH) {
65
+ manifestCache = cached
66
+ manifestIndexCache = null
67
+ manifestIndexPromise = null
68
+ return cached
69
+ }
70
+ }
71
+ }
72
+ try {
73
+ const url = new URL(resolveManifestUrl(), self.location.origin).toString()
74
+ const res = await fetch(url, { cache: 'no-store' })
75
+ if (!res || !res.ok) {
76
+ throw new Error('manifest fetch failed')
77
+ }
78
+ const data = await res.json()
79
+ const entries = Array.isArray(data?.entries) ? data.entries : []
80
+ const normalized = entries
81
+ .filter((entry) => entry && entry.url)
82
+ .map((entry) => ({
83
+ url: new URL(entry.url, self.location.href).toString(),
84
+ revision: entry.revision ?? null
85
+ }))
86
+ const installCount = Number.isFinite(data?.installCount)
87
+ ? Math.max(0, Math.min(normalized.length, data.installCount))
88
+ : normalized.length
89
+ const batchSize = Number.isFinite(data?.batchSize) ? data.batchSize : DEFAULT_BATCH_SIZE
90
+ const result = { entries: normalized, installCount, batchSize, hash: data?.hash || '' }
91
+ manifestCache = result
92
+ manifestIndexCache = null
93
+ manifestIndexPromise = null
94
+ await idbSet(KEY_MANIFEST_DATA, result).catch(() => {})
95
+ return result
96
+ } catch (error) {
97
+ if (manifestCache) return manifestCache
98
+ throw error
99
+ } finally {
100
+ manifestPromise = null
101
+ }
102
+ })()
103
+ return manifestPromise
80
104
  }
81
105
 
82
- const getManifestEntries = cached(() => {
83
- const entries = []
84
- for (const entry of __WB_MANIFEST) {
85
- if (!entry?.url) continue
86
- entries.push({
87
- url: new URL(entry.url, self.location.href).toString(),
88
- revision: entry.revision
89
- })
90
- }
91
- return entries
92
- })
93
-
94
- const getManifestIndex = cached(() => {
95
- const map = new Map()
96
- for (const entry of getManifestEntries()) {
97
- map.set(manifestKey(entry.url), entry.revision ?? null)
98
- }
99
- return map
100
- })
101
-
102
- function collectManifestUrls() {
103
- return getManifestEntries().map((entry) => entry.url)
106
+ async function getManifestEntries() {
107
+ const data = await loadManifest()
108
+ return data.entries
104
109
  }
105
110
 
106
- function prioritizeManifestUrls(urls) {
107
- const prioritized = []
108
- const other = []
109
-
110
- for (const url of urls) {
111
- const lower = url.toLowerCase()
112
- if (lower.endsWith('.css') && isRootOrAssets(url)) {
113
- prioritized.push(url)
114
- continue
115
- }
116
- if ((lower.endsWith('.js') || lower.endsWith('.mjs')) && isRootOrAssets(url)) {
117
- prioritized.push(url)
118
- continue
111
+ async function getManifestIndex() {
112
+ if (manifestIndexCache) return manifestIndexCache
113
+ if (manifestIndexPromise) return manifestIndexPromise
114
+ manifestIndexPromise = (async () => {
115
+ const map = new Map()
116
+ const entries = await getManifestEntries()
117
+ for (const entry of entries) {
118
+ map.set(manifestKey(entry.url), entry.revision ?? null)
119
119
  }
120
- if (lower.endsWith('.html') && (lower === NOT_FOUND_URL || lower === OFFLINE_FALLBACK_URL)) {
121
- prioritized.unshift(url)
122
- continue
123
- }
124
- other.push(url)
125
- }
126
-
127
- return [prioritized, other]
120
+ manifestIndexCache = map
121
+ manifestIndexPromise = null
122
+ return map
123
+ })()
124
+ return manifestIndexPromise
128
125
  }
129
126
 
130
127
  // Precache prioritized entries during install
@@ -132,41 +129,60 @@ self.addEventListener('install', (event) => {
132
129
  event.waitUntil(
133
130
  (async () => {
134
131
  try {
135
- await idbSet(KEY_FORCE, 1)
136
- await idbSet(KEY_INDEX, 0)
137
- } catch {}
138
- const pageCache = await openCache(PAGES_CACHE)
139
- const assetCache = await openCache(ASSETS_CACHE)
140
- const manifestEntries = getManifestEntries()
141
- const manifestMap = buildManifestMap(manifestEntries)
142
- const manifestUrls = manifestEntries.map((entry) => entry.url)
143
- const [prioritized] = prioritizeManifestUrls(manifestUrls)
144
- const { failedIndex } = await runConcurrentQueue(prioritized, {
145
- concurrency: BATCH_SIZE,
146
- handler: async (url) => {
147
- const isHtml = url.endsWith('.html')
148
- const cacheName = isHtml ? PAGES_CACHE : ASSETS_CACHE
149
- const cached = await matchCache(cacheName, url)
150
- const key = manifestKey(url)
151
- const currentRevision = manifestMap.get(key) ?? null
152
- const shouldFetch = shouldFetchWithRevision({
153
- cached,
154
- currentRevision
155
- })
156
- if (!shouldFetch) return true
157
- const cache = isHtml ? pageCache : assetCache
158
- return fetchAndCache(cache, url, currentRevision)
132
+ const prevHash = await idbGet(KEY_MANIFEST)
133
+ const swChanged = prevHash !== MANIFEST_HASH
134
+ if (swChanged) {
135
+ await idbSet(KEY_FORCE, 1)
136
+ await idbSet(KEY_INDEX, 0)
137
+ await idbSet(KEY_MANIFEST, MANIFEST_HASH)
138
+ await idbSet(KEY_MANIFEST_DATA, null)
159
139
  }
160
- })
161
- if (failedIndex !== null) {
162
- throw new Error('install cache failed')
140
+ const manifest = await loadManifest(swChanged)
141
+ await installManifest(manifest)
142
+ } catch (error) {
143
+ throw error
163
144
  }
164
145
  })()
165
146
  )
166
147
  })
167
148
 
149
+ async function installManifest(manifest) {
150
+ const pageCache = await openCache(PAGES_CACHE)
151
+ const assetCache = await openCache(ASSETS_CACHE)
152
+ const manifestEntries = manifest.entries
153
+ const manifestMap = buildManifestMap(manifestEntries)
154
+ const installCount = manifest.installCount ?? manifestEntries.length
155
+ const installUrls = manifestEntries.slice(0, installCount).map((entry) => entry.url)
156
+ const batchSize = Math.max(1, manifest.batchSize || DEFAULT_BATCH_SIZE)
157
+ const { failedIndex } = await runConcurrentQueue(installUrls, {
158
+ concurrency: batchSize,
159
+ handler: async (url) => {
160
+ const isHtml = url.endsWith('.html')
161
+ const cacheName = isHtml ? PAGES_CACHE : ASSETS_CACHE
162
+ const cached = await matchCache(cacheName, url)
163
+ const key = manifestKey(url)
164
+ const currentRevision = manifestMap.get(key) ?? null
165
+ const shouldFetch = shouldFetchWithRevision({
166
+ cached,
167
+ currentRevision
168
+ })
169
+ if (!shouldFetch) return true
170
+ const cache = isHtml ? pageCache : assetCache
171
+ return fetchAndCache(cache, url, currentRevision)
172
+ }
173
+ })
174
+ if (failedIndex !== null) {
175
+ throw new Error('install cache failed')
176
+ }
177
+ }
178
+
168
179
  self.addEventListener('activate', (event) => {
169
- warmManifestResumable()
180
+ event.waitUntil(
181
+ (async () => {
182
+ await self.clients.claim()
183
+ await warmManifestResumable()
184
+ })()
185
+ )
170
186
  })
171
187
 
172
188
  function stripSearch(urlString) {
@@ -466,88 +482,114 @@ async function serveOffline() {
466
482
  })
467
483
  }
468
484
 
469
- // NAVIGATIONS: cache-first for manifest pages, network fallback for others
470
- registerRoute(
471
- ({ request, url }) => (isHtmlNavigation(request) || isPrefetch(request)) && url.origin === self.location.origin,
472
- async ({ event, request }) => {
473
- const normalizedKey = normalizeNavigationURL(new URL(request.url)).toString()
474
- const key = manifestKey(normalizedKey)
475
- const manifestRevision = getManifestIndex().get(key) ?? null
476
- const inManifest = getManifestIndex().has(key)
477
-
478
- if (inManifest) {
479
- const cached = await matchCache(PAGES_CACHE, normalizedKey)
480
- const shouldRevalidate = shouldRevalidateCached(cached, manifestRevision)
481
- if (cached && !shouldRevalidate) return cached
482
- if (cached && shouldRevalidate) {
483
- const fresh = await fetchWithCleanUrlFallback(event, request, {
484
- usePreload: isHtmlNavigation(request),
485
- allowNotOk: true
486
- })
487
- if (fresh && fresh.status === 200) {
488
- await putCache(PAGES_CACHE, normalizedKey, fresh.clone(), manifestRevision)
489
- return fresh
490
- }
491
- if (fresh && fresh.status === 404) {
492
- return serveNotFound()
493
- }
494
- if (fresh) return fresh
495
- return cached
496
- }
497
- }
485
+ const handleNavigationRequest = async (event, request, index) => {
486
+ const normalizedKey = normalizeNavigationURL(new URL(request.url)).toString()
487
+ const key = manifestKey(normalizedKey)
488
+ const manifestRevision = index.get(key) ?? null
489
+ const inManifest = index.has(key)
498
490
 
499
- const fresh = await fetchWithCleanUrlFallback(event, request, {
500
- usePreload: isHtmlNavigation(request),
501
- allowNotOk: true
502
- })
503
- if (fresh && fresh.status === 200) {
504
- if (inManifest) {
491
+ if (inManifest) {
492
+ const cached = await matchCache(PAGES_CACHE, normalizedKey)
493
+ const shouldRevalidate = shouldRevalidateCached(cached, manifestRevision)
494
+ if (cached && !shouldRevalidate) return cached
495
+ if (cached && shouldRevalidate) {
496
+ const fresh = await fetchWithCleanUrlFallback(event, request, {
497
+ usePreload: isHtmlNavigation(request),
498
+ allowNotOk: true
499
+ })
500
+ if (fresh && fresh.status === 200) {
505
501
  await putCache(PAGES_CACHE, normalizedKey, fresh.clone(), manifestRevision)
502
+ return fresh
503
+ }
504
+ if (fresh && fresh.status === 404) {
505
+ return serveNotFound()
506
506
  }
507
- return fresh
507
+ if (fresh) return fresh
508
+ return cached
508
509
  }
509
- if (fresh && fresh.status === 404) {
510
- return serveNotFound()
510
+ }
511
+
512
+ const fresh = await fetchWithCleanUrlFallback(event, request, {
513
+ usePreload: isHtmlNavigation(request),
514
+ allowNotOk: true
515
+ })
516
+ if (fresh && fresh.status === 200) {
517
+ if (inManifest) {
518
+ await putCache(PAGES_CACHE, normalizedKey, fresh.clone(), manifestRevision)
511
519
  }
520
+ return fresh
521
+ }
522
+ if (fresh && fresh.status === 404) {
523
+ return serveNotFound()
524
+ }
512
525
 
513
- if (fresh) return fresh
526
+ if (fresh) return fresh
514
527
 
515
- return serveOffline()
516
- }
517
- )
518
-
519
- registerRoute(
520
- ({ request, url }) =>
521
- url.origin === self.location.origin &&
522
- request.method === 'GET' &&
523
- request.destination !== 'document' &&
524
- !isHtmlNavigation(request) &&
525
- getManifestIndex().has(manifestKey(request.url)),
526
- async ({ request }) => {
527
- const key = manifestKey(request.url)
528
- const manifestRevision = getManifestIndex().get(key) ?? null
529
- const cached = await matchCache(ASSETS_CACHE, key)
530
- const shouldRevalidate = shouldRevalidateCached(cached, manifestRevision)
531
- if (cached && !shouldRevalidate) return cached
528
+ return serveOffline()
529
+ }
532
530
 
533
- try {
534
- const res = await fetch(request)
535
- if (res && res.status === 200) {
536
- await putCache(ASSETS_CACHE, key, res.clone(), manifestRevision)
537
- }
538
- return res
539
- } catch {
540
- if (cached) return cached
541
- return new Response(null, { status: 503 })
531
+ const handleAssetRequest = async (request, index) => {
532
+ const key = manifestKey(request.url)
533
+ const manifestRevision = index.get(key) ?? null
534
+ const cached = await matchCache(ASSETS_CACHE, key)
535
+ const shouldRevalidate = shouldRevalidateCached(cached, manifestRevision)
536
+ if (cached && !shouldRevalidate) return cached
537
+
538
+ try {
539
+ const res = await fetch(request)
540
+ if (res && res.status === 200) {
541
+ await putCache(ASSETS_CACHE, key, res.clone(), manifestRevision)
542
542
  }
543
+ return res
544
+ } catch {
545
+ if (cached) return cached
546
+ return new Response(null, { status: 503 })
543
547
  }
544
- )
548
+ }
549
+
550
+ self.addEventListener('fetch', (event) => {
551
+ const request = event.request
552
+ if (!request || request.method !== 'GET') return
553
+ let url = null
554
+ try {
555
+ url = new URL(request.url)
556
+ } catch {
557
+ return
558
+ }
559
+ if (url.origin !== self.location.origin) return
560
+
561
+ event.respondWith(
562
+ (async () => {
563
+ try {
564
+ const manifestKeyUrl = stripSearch(new URL(MANIFEST_URL, self.location.origin).toString()).toString()
565
+ if (stripSearch(request.url).toString() === manifestKeyUrl) {
566
+ return fetch(request)
567
+ }
568
+ if (isHtmlNavigation(request) || isPrefetch(request)) {
569
+ const index = await getManifestIndex().catch(() => new Map())
570
+ return await handleNavigationRequest(event, request, index)
571
+ }
572
+ const index = await getManifestIndex().catch(() => new Map())
573
+ if (
574
+ request.destination !== 'document' &&
575
+ !isHtmlNavigation(request) &&
576
+ index.has(manifestKey(request.url))
577
+ ) {
578
+ return await handleAssetRequest(request, index)
579
+ }
580
+ } catch {}
581
+ return fetch(request)
582
+ })()
583
+ )
584
+ })
545
585
 
546
586
  const DB_NAME = withBase(':methanol-pwa-warm-db')
547
587
  const DB_STORE = 'kv'
548
588
  const KEY_INDEX = 'warmIndex'
549
589
  const KEY_LEASE = 'warmLease'
550
590
  const KEY_FORCE = 'warmForce'
591
+ const KEY_MANIFEST = 'warmManifestHash'
592
+ const KEY_MANIFEST_DATA = 'warmManifestData'
551
593
 
552
594
  function idbOpen() {
553
595
  return new Promise((resolve, reject) => {
@@ -639,7 +681,12 @@ async function releaseLease(lease) {
639
681
  }
640
682
 
641
683
  async function warmManifestResumable({ force = false } = {}) {
642
- if (!__WB_MANIFEST.length) return
684
+ let manifest = null
685
+ try {
686
+ manifest = await loadManifest()
687
+ } catch {
688
+ return
689
+ }
643
690
 
644
691
  const forceFlag = await idbGet(KEY_FORCE)
645
692
  if (forceFlag) force = true
@@ -656,9 +703,9 @@ async function warmManifestResumable({ force = false } = {}) {
656
703
 
657
704
  let completed = false
658
705
  try {
659
- const manifestEntries = getManifestEntries()
706
+ const manifestEntries = manifest.entries
660
707
  const manifestMap = buildManifestMap(manifestEntries)
661
- const [, urls] = prioritizeManifestUrls(manifestEntries.map((entry) => entry.url))
708
+ const urls = manifestEntries.slice(manifest.installCount).map((entry) => entry.url)
662
709
  if (!urls.length) return
663
710
  if (index >= urls.length) {
664
711
  completed = true
@@ -667,7 +714,7 @@ async function warmManifestResumable({ force = false } = {}) {
667
714
 
668
715
  const startIndex = index
669
716
  const { failedIndex } = await runConcurrentQueue(urls.slice(startIndex), {
670
- concurrency: BATCH_SIZE,
717
+ concurrency: Math.max(1, manifest.batchSize || DEFAULT_BATCH_SIZE),
671
718
  handler: async (abs) => {
672
719
  const leaseOk = await renewLease(lease, leaseMs)
673
720
  if (!leaseOk) return false
@@ -18,8 +18,30 @@
18
18
  * under the License.
19
19
  */
20
20
 
21
+ const resolveBasePrefix = () => {
22
+ let base = import.meta.env?.BASE_URL || '/'
23
+ if (!base || base === '/' || base === './') return ''
24
+ if (base.startsWith('http://') || base.startsWith('https://')) {
25
+ try {
26
+ base = new URL(base).pathname
27
+ } catch {
28
+ return ''
29
+ }
30
+ }
31
+ if (!base.startsWith('/')) return ''
32
+ if (base.endsWith('/')) base = base.slice(0, -1)
33
+ return base
34
+ }
35
+
21
36
  if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
22
- navigator.serviceWorker.ready.then((reg) => {
23
- reg.active?.postMessage({ type: 'METHANOL_WARM_MANIFEST' });
24
- });
37
+ const base = resolveBasePrefix()
38
+ const scope = base ? `${base}/` : '/'
39
+ const swUrl = `${scope}sw.js`
40
+ navigator.serviceWorker
41
+ .register(swUrl)
42
+ .then(() => navigator.serviceWorker.ready)
43
+ .then((reg) => {
44
+ reg.active?.postMessage({ type: 'METHANOL_WARM_MANIFEST' })
45
+ })
46
+ .catch(() => {})
25
47
  }
package/src/config.js CHANGED
@@ -31,6 +31,8 @@ import { HTMLRenderer } from './renderer.js'
31
31
  import { reframeEnv } from './components.js'
32
32
  import { env as createEnv } from './reframe.js'
33
33
  import { cached, cachedStr } from './utils.js'
34
+ import { normalizeBasePrefix } from './base.js'
35
+ import { resolvePwaOptions } from './pwa.js'
34
36
  import defaultTheme from '../themes/default/index.js'
35
37
 
36
38
  const CONFIG_FILENAMES = [
@@ -431,12 +433,7 @@ export const applyConfig = async (config, mode) => {
431
433
  state.MERGED_ASSETS_DIR = null
432
434
  } else {
433
435
  // We need to merge
434
- const nodeModulesPath = resolve(root, 'node_modules')
435
- if (existsSync(nodeModulesPath)) {
436
- state.MERGED_ASSETS_DIR = resolve(nodeModulesPath, '.methanol/assets')
437
- } else {
438
- state.MERGED_ASSETS_DIR = resolve(state.PAGES_DIR || resolve(root, 'pages'), '.methanol/assets')
439
- }
436
+ state.MERGED_ASSETS_DIR = resolve(state.PAGES_DIR || resolve(root, 'pages'), '.methanol/assets')
440
437
  state.STATIC_DIR = state.MERGED_ASSETS_DIR
441
438
  }
442
439
  } else {
@@ -471,29 +468,29 @@ export const applyConfig = async (config, mode) => {
471
468
  state.RSS_OPTIONS = { ...(state.RSS_OPTIONS || {}), atom: cli.CLI_ATOM }
472
469
  }
473
470
 
474
- if (hasOwn(config, 'pwa')) {
475
- if (config.pwa === true) {
476
- state.PWA_ENABLED = true
477
- state.PWA_OPTIONS = null
478
- } else if (typeof config.pwa === 'object' && config.pwa !== null) {
479
- state.PWA_ENABLED = true
480
- state.PWA_OPTIONS = config.pwa
481
- } else {
482
- state.PWA_ENABLED = false
483
- state.PWA_OPTIONS = null
484
- }
485
- }
471
+ const resolvedPwa = resolvePwaOptions(config.pwa)
472
+ state.PWA_ENABLED = resolvedPwa.enabled
473
+ state.PWA_OPTIONS = resolvedPwa.options
486
474
  if (cli.CLI_PWA !== undefined) {
487
475
  state.PWA_ENABLED = cli.CLI_PWA
476
+ if (cli.CLI_PWA && !state.PWA_OPTIONS) {
477
+ state.PWA_OPTIONS = resolvePwaOptions(true).options
478
+ }
488
479
  }
489
480
  state.USER_PRE_BUILD_HOOKS = normalizeHooks(config.preBuild)
490
481
  state.USER_POST_BUILD_HOOKS = normalizeHooks(config.postBuild)
491
482
  state.USER_PRE_BUNDLE_HOOKS = normalizeHooks(config.preBundle)
492
483
  state.USER_POST_BUNDLE_HOOKS = normalizeHooks(config.postBundle)
484
+ state.USER_PRE_WRITE_HOOKS = normalizeHooks(config.preWrite)
485
+ state.USER_POST_WRITE_HOOKS = normalizeHooks(config.postWrite)
486
+ state.USER_FINALIZE_HOOKS = normalizeHooks(config.finalize)
493
487
  state.THEME_PRE_BUILD_HOOKS = normalizeHooks(state.USER_THEME?.preBuild)
494
488
  state.THEME_POST_BUILD_HOOKS = normalizeHooks(state.USER_THEME?.postBuild)
495
489
  state.THEME_PRE_BUNDLE_HOOKS = normalizeHooks(state.USER_THEME?.preBundle)
496
490
  state.THEME_POST_BUNDLE_HOOKS = normalizeHooks(state.USER_THEME?.postBundle)
491
+ state.THEME_PRE_WRITE_HOOKS = normalizeHooks(state.USER_THEME?.preWrite)
492
+ state.THEME_POST_WRITE_HOOKS = normalizeHooks(state.USER_THEME?.postWrite)
493
+ state.THEME_FINALIZE_HOOKS = normalizeHooks(state.USER_THEME?.finalize)
497
494
  if (hasOwn(config, 'gfm')) {
498
495
  state.GFM_ENABLED = config.gfm !== false
499
496
  }
@@ -619,23 +616,7 @@ export const resolveUserViteConfig = async (command) => {
619
616
  return state.RESOLVED_VITE_CONFIG
620
617
  }
621
618
 
622
- export const resolveBasePrefix = cached(() => {
623
- const value = state.VITE_BASE || state.SITE_BASE || '/'
624
- if (!value || value === '/' || value === './') return ''
625
- if (typeof value !== 'string') return ''
626
- let base = value.trim()
627
- if (!base || base === '/' || base === './') return ''
628
- if (base.startsWith('http://') || base.startsWith('https://')) {
629
- try {
630
- base = new URL(base).pathname
631
- } catch {
632
- return ''
633
- }
634
- }
635
- if (!base.startsWith('/')) return ''
636
- if (base.endsWith('/')) base = base.slice(0, -1)
637
- return base
638
- })
619
+ export const resolveBasePrefix = cached(() => normalizeBasePrefix(state.VITE_BASE || state.SITE_BASE || '/'))
639
620
 
640
621
  export const withBase = cachedStr((value) => {
641
622
  if (!value || typeof value !== 'string') return value
package/src/dev-server.js CHANGED
@@ -214,7 +214,7 @@ export const runViteDev = async () => {
214
214
 
215
215
  const prebuildHtmlCache = async (token) => {
216
216
  if (!pagesContext || token !== pagesContextToken) return
217
- const pages = pagesContext.pages || []
217
+ const pages = pagesContext.pagesAll || pagesContext.pages || []
218
218
  if (!pages.length) return
219
219
  const { workers, assignments } = createBuildWorkers(pages.length, { command: 'serve' })
220
220
  const excludedRoutes = Array.from(pagesContext.excludedRoutes || [])
@@ -691,7 +691,9 @@ export const runViteDev = async () => {
691
691
  source: resolved.source
692
692
  })
693
693
  if (!nextEntry) return false
694
- const prevEntry = pagesContext.pages?.find?.((page) => page.path === path) || null
694
+ const prevEntry = pagesContext.pagesAll?.find?.((page) => page.path === path)
695
+ || pagesContext.pages?.find?.((page) => page.path === path)
696
+ || null
695
697
  if (!prevEntry) return false
696
698
  if (prevEntry.exclude !== nextEntry.exclude) return false
697
699
  if (prevEntry.isIndex !== nextEntry.isIndex || prevEntry.dir !== nextEntry.dir) return false
package/src/entry.js ADDED
@@ -0,0 +1,22 @@
1
+ /* Copyright Yukino Song, SudoMaker Ltd.
2
+ *
3
+ * Licensed to the Apache Software Foundation (ASF) under one
4
+ * or more contributor license agreements. See the NOTICE file
5
+ * distributed with this work for additional information
6
+ * regarding copyright ownership. The ASF licenses this file
7
+ * to you under the Apache License, Version 2.0 (the
8
+ * "License"); you may not use this file except in compliance
9
+ * with the License. You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing,
14
+ * software distributed under the License is distributed on an
15
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ * KIND, either express or implied. See the License for the
17
+ * specific language governing permissions and limitations
18
+ * under the License.
19
+ */
20
+
21
+ import './register-loader.js'
22
+ import('./main.js')