@lincyaw/web 1.0.0-rc.0 → 1.0.0-rc.2
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 +33 -4
- package/package.json +20 -19
- package/src/app/base.css +117 -0
- package/src/app/block-playback.ts +81 -0
- package/src/app/block-report.ts +104 -0
- package/src/app/document-modules.ts +30 -0
- package/src/app/env.d.ts +12 -0
- package/src/app/main.ts +198 -0
- package/src/app/page-app.ts +166 -0
- package/src/app/router.ts +109 -0
- package/src/app/server.ts +32 -0
- package/src/app/site/home.tsx +117 -0
- package/src/app/site/layout.tsx +92 -0
- package/src/app/site/search.tsx +22 -0
- package/src/app/site.css +1450 -0
- package/src/client/document.css +172 -0
- package/src/client/host.ts +22 -94
- package/src/client/preview-frame.ts +152 -0
- package/src/client/runtime.ts +159 -0
- package/src/domain/app-config.ts +91 -0
- package/src/domain/config.ts +17 -9
- package/src/domain/dev-updates.ts +12 -0
- package/src/domain/index.ts +17 -7
- package/src/domain/landing.ts +83 -0
- package/src/domain/links.ts +23 -18
- package/src/domain/messages.ts +74 -63
- package/src/domain/pages.ts +23 -41
- package/src/domain/themes.ts +59 -0
- package/src/domain/workspace-home.ts +12 -0
- package/src/node/client-modules.ts +92 -0
- package/src/node/document-bundle.ts +227 -0
- package/src/node/landing-template.ts +34 -0
- package/src/node/landing.ts +82 -0
- package/src/node/offline.ts +34 -11
- package/src/node/publish.ts +83 -15
- package/src/node/renderer.ts +78 -117
- package/src/node/site-assets.ts +17 -0
- package/src/node/site-build.ts +304 -0
- package/src/node/site-files.ts +87 -0
- package/src/node/site-model.ts +337 -0
- package/src/node/site-page.ts +82 -0
- package/src/node/site-server.ts +822 -0
- package/src/node/slides.ts +135 -0
- package/src/node/viewer-app.ts +504 -0
- package/src/node/viewer-build.ts +38 -0
- package/custom-nav-controls.vue +0 -17
- package/src/client/env.d.ts +0 -1
- package/src/client/home.ts +0 -182
- package/src/client/index.ts +0 -4
- package/src/client/page.ts +0 -38
- package/src/client/preview.ts +0 -71
- package/src/client/style.css +0 -427
- package/src/client/theme.ts +0 -322
- package/src/node/addons.ts +0 -76
- package/src/node/site.ts +0 -349
- package/src/node/sites.ts +0 -531
- package/src/node/vite.ts +0 -166
- package/src/node/vitepress.ts +0 -72
package/src/client/theme.ts
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { createServerClient, createSharedStates, currentLocale, installHost, OPERATIONS, pluginsOf, PmIcon, PmPresenterPanels, PmToolbar, presmiumService, provideServer, registerPresmiumPlugin, reportPluginFailure, startHost, stopPresmium } from '@lincyaw/host'
|
|
2
|
-
import type { ServerClient, SyncTransports } from '@lincyaw/host'
|
|
3
|
-
import { errorMessage } from '@lincyaw/kernel'
|
|
4
|
-
import type { IRDocument } from '@lincyaw/kernel'
|
|
5
|
-
import { createViewer, followOperations, getView, installViewer, installViewerPlugin, OverlayHost, PmComponentFallback, registerComponent, useViewer, withSlideGlobals } from '@lincyaw/viewer'
|
|
6
|
-
import type { LiveUpdate, Position, Viewer } from '@lincyaw/viewer'
|
|
7
|
-
import { applyUpdate } from '@lincyaw/viewer/domain'
|
|
8
|
-
import type { Theme } from 'vitepress'
|
|
9
|
-
import { inBrowser, useData, withBase } from 'vitepress'
|
|
10
|
-
import DefaultTheme from 'vitepress/theme-without-fonts'
|
|
11
|
-
import type { Component } from 'vue'
|
|
12
|
-
import { computed, defineAsyncComponent, defineComponent, h, nextTick, onMounted, ref, shallowRef, watch } from 'vue'
|
|
13
|
-
import { IR_ENDPOINT, offlineEnabled, requested, samePage, SERVICE_WORKER, sectionLinks, sidebar, slideUrl, SOURCE_ENDPOINT, toPages, translationPages, viewSearch, WEB_MESSAGES } from '../domain/index.ts'
|
|
14
|
-
import type { FormOptions } from '../domain/index.ts'
|
|
15
|
-
import { createViewerHost, createViewerNavigation } from './host.ts'
|
|
16
|
-
import { PAGES, PresmiumPage, ViewMenu } from './page.ts'
|
|
17
|
-
import type { WebPreviewPage } from './preview.ts'
|
|
18
|
-
import './style.css'
|
|
19
|
-
|
|
20
|
-
export interface ViewerModule {
|
|
21
|
-
name: string
|
|
22
|
-
load: () => Promise<unknown>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ThemeInput {
|
|
26
|
-
doc: IRDocument
|
|
27
|
-
revision?: string
|
|
28
|
-
components: Record<string, () => Promise<unknown>>
|
|
29
|
-
plugins: (() => Promise<unknown>)[]
|
|
30
|
-
viewers?: ViewerModule[]
|
|
31
|
-
translations?: Record<string, () => Promise<{ default: IRDocument }>>
|
|
32
|
-
preview?: (page: WebPreviewPage) => void
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface ThemeOptions {
|
|
36
|
-
form?: FormOptions
|
|
37
|
-
server?: boolean
|
|
38
|
-
document?: string
|
|
39
|
-
home?: string
|
|
40
|
-
ir?: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface UpdateEvent {
|
|
44
|
-
update: LiveUpdate
|
|
45
|
-
base: string
|
|
46
|
-
revision: string
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function startKernel(loaders: ThemeInput['plugins'], doc: IRDocument, viewer: Viewer, transports: SyncTransports, server?: ServerClient): Promise<void> {
|
|
50
|
-
const config = doc.config.presmium
|
|
51
|
-
const modules = await Promise.all(loaders.map(load => load().catch((error) => {
|
|
52
|
-
console.error('[presmium] a plugin module failed to load', error)
|
|
53
|
-
return {}
|
|
54
|
-
})))
|
|
55
|
-
modules.flatMap(module => pluginsOf(module, config)).forEach(registerPresmiumPlugin)
|
|
56
|
-
await startHost({ mode: 'document', config, navigation: createViewerNavigation(viewer), transports, server, environments: doc.config.execute?.environments })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function clientOnly(name: string, load: () => Promise<unknown>): Component {
|
|
60
|
-
const loaded = defineAsyncComponent(() => load().then(module => withSlideGlobals((module as { default: Component }).default)))
|
|
61
|
-
return defineComponent({
|
|
62
|
-
name,
|
|
63
|
-
inheritAttrs: false,
|
|
64
|
-
setup(_, { attrs, slots }) {
|
|
65
|
-
const mounted = ref(false)
|
|
66
|
-
onMounted(() => {
|
|
67
|
-
mounted.value = true
|
|
68
|
-
})
|
|
69
|
-
return () => (mounted.value
|
|
70
|
-
? h(loaded, attrs, slots)
|
|
71
|
-
: h('div', { 'class': 'pm-client-only', 'data-component': name }, [h(PmComponentFallback)]))
|
|
72
|
-
},
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function loadViewerPlugins(modules: ViewerModule[]): Promise<void> {
|
|
77
|
-
await Promise.all(modules.map(async ({ name, load }) => {
|
|
78
|
-
const id = `${name} (viewer)`
|
|
79
|
-
try {
|
|
80
|
-
const reason = installViewerPlugin(((await load()) as { default?: unknown }).default, id)
|
|
81
|
-
if (reason) reportPluginFailure(id, reason)
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error(`[presmium] ${id} failed to load`, error)
|
|
84
|
-
reportPluginFailure(id, errorMessage(error))
|
|
85
|
-
}
|
|
86
|
-
}))
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function serviceWorker(offline: boolean): void {
|
|
90
|
-
if (!('serviceWorker' in navigator)) return
|
|
91
|
-
if (!offline) {
|
|
92
|
-
void navigator.serviceWorker.getRegistrations().then(registrations => registrations
|
|
93
|
-
.filter(registration => (registration.active ?? registration.waiting ?? registration.installing)?.scriptURL.endsWith(`/${SERVICE_WORKER}`))
|
|
94
|
-
.forEach(registration => void registration.unregister()))
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
const register = () => navigator.serviceWorker.register(withBase(`/${SERVICE_WORKER}`)).catch((error) => {
|
|
98
|
-
console.warn('[presmium] offline support unavailable', error)
|
|
99
|
-
})
|
|
100
|
-
if (document.readyState === 'complete') void register()
|
|
101
|
-
else window.addEventListener('load', register, { once: true })
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function slidesIcon() {
|
|
105
|
-
return h('svg', { 'viewBox': '0 0 24 24', 'width': '1em', 'height': '1em', 'aria-hidden': 'true', 'fill': 'none', 'stroke': 'currentColor', 'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }, [
|
|
106
|
-
h('rect', { x: 2, y: 3, width: 20, height: 14, rx: 2 }),
|
|
107
|
-
h('path', { d: 'M8 21h8M12 17v4' }),
|
|
108
|
-
])
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function localizeHiddenLabels(): void {
|
|
112
|
-
const t = WEB_MESSAGES[currentLocale()]
|
|
113
|
-
for (const [id, text] of [['sidebar-aria-label', t.sidebarNav], ['doc-footer-aria-label', t.pager]] as const) {
|
|
114
|
-
const label = document.getElementById(id)
|
|
115
|
-
if (label && label.textContent !== text) label.textContent = text
|
|
116
|
-
}
|
|
117
|
-
for (const [selector, text] of [['.VPNavBarHamburger', t.mobileNav], ['.VPNavBarExtra > button', t.extraNav]] as const) {
|
|
118
|
-
for (const button of document.querySelectorAll(selector)) {
|
|
119
|
-
if (button.getAttribute('aria-label') !== text) button.setAttribute('aria-label', text)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const slidesBase = ref<string>()
|
|
125
|
-
let preview: ThemeInput['preview']
|
|
126
|
-
let viewersReady: Promise<void> = Promise.resolve()
|
|
127
|
-
|
|
128
|
-
const Layout = defineComponent({
|
|
129
|
-
name: 'PresmiumLayout',
|
|
130
|
-
setup() {
|
|
131
|
-
const viewer = useViewer()
|
|
132
|
-
const { theme, page, isDark } = useData()
|
|
133
|
-
onMounted(async () => {
|
|
134
|
-
const agent = preview
|
|
135
|
-
preview = undefined
|
|
136
|
-
agent?.({ viewer, dark: isDark })
|
|
137
|
-
localizeHiddenLabels()
|
|
138
|
-
watch(() => page.value.relativePath, () => void nextTick(localizeHiddenLabels), { flush: 'post' })
|
|
139
|
-
const form: FormOptions = theme.value.presmium?.form ?? {}
|
|
140
|
-
if (form.slides) slidesBase.value = form.slidesUrl ?? new URL('../slides', new URL(withBase('/'), location.href)).href
|
|
141
|
-
const ask = requested(location.search, viewer.doc.value)
|
|
142
|
-
if (ask.section || ask.view) {
|
|
143
|
-
history.replaceState(history.state, '', location.pathname + viewSearch(location.search) + location.hash)
|
|
144
|
-
if (ask.section) viewer.show({ section: ask.section, step: ask.step ?? 0 })
|
|
145
|
-
if (ask.view) {
|
|
146
|
-
await viewersReady
|
|
147
|
-
if (getView(ask.view)) viewer.setView(ask.view)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
watch([viewer.view, viewer.position], ([view, position]) => {
|
|
151
|
-
const search = viewSearch(location.search, getView(view)?.kind === 'overlay' ? { view, ...position } : undefined)
|
|
152
|
-
if (search !== location.search) history.replaceState(history.state, '', location.pathname + search + location.hash)
|
|
153
|
-
}, { immediate: true })
|
|
154
|
-
})
|
|
155
|
-
const home = () => {
|
|
156
|
-
const href = theme.value.presmium?.home
|
|
157
|
-
return href ? h('a', { class: 'pm-site-link', href }, [h(PmIcon, { name: 'arrow-left', size: 14, strokeWidth: 2 }), WEB_MESSAGES[currentLocale()].documents]) : null
|
|
158
|
-
}
|
|
159
|
-
const footer = () => {
|
|
160
|
-
const { message, copyright } = (theme.value.footer ?? {}) as { message?: string, copyright?: string }
|
|
161
|
-
if (!message && !copyright) return null
|
|
162
|
-
return h('footer', { class: 'pm-site-footer' }, [
|
|
163
|
-
message ? h('p', { class: 'pm-site-footer__message', innerHTML: message }) : null,
|
|
164
|
-
copyright ? h('p', { class: 'pm-site-footer__copyright', innerHTML: copyright }) : null,
|
|
165
|
-
])
|
|
166
|
-
}
|
|
167
|
-
return () => h(DefaultTheme.Layout, null, {
|
|
168
|
-
'nav-bar-content-after': () => [home(), h(ViewMenu, { class: 'pm-view-menu--bar' }), h(PmToolbar, { class: 'pm-toolbar--bar' })],
|
|
169
|
-
'nav-screen-content-after': () => [home(), h(ViewMenu, { class: 'pm-view-menu--screen' }), h(PmToolbar, { class: 'pm-toolbar--screen' })],
|
|
170
|
-
'doc-after': footer,
|
|
171
|
-
'layout-bottom': () => [
|
|
172
|
-
h('div', { class: 'vp-doc pm-overlay-views' }, [h(OverlayHost)]),
|
|
173
|
-
viewer.view.value === 'presenter' ? h(PmPresenterPanels, { to: '.pm-presenter__side', docked: true }) : null,
|
|
174
|
-
],
|
|
175
|
-
})
|
|
176
|
-
},
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
function followDevServer(original: () => IRDocument, revision: string | undefined, endpoint: string, follow: (doc: IRDocument) => void): void {
|
|
180
|
-
const hot = import.meta.hot
|
|
181
|
-
if (!hot) return
|
|
182
|
-
let current = revision
|
|
183
|
-
let updates = 0
|
|
184
|
-
let asked = 0
|
|
185
|
-
let answered = 0
|
|
186
|
-
const fetchCurrent = async () => {
|
|
187
|
-
const request = ++asked
|
|
188
|
-
const since = updates
|
|
189
|
-
try {
|
|
190
|
-
const response = await fetch(endpoint, { cache: 'no-store' })
|
|
191
|
-
if (!response.ok) return
|
|
192
|
-
const { revision: latest, doc } = await response.json() as { revision: string, doc: IRDocument }
|
|
193
|
-
if (updates !== since || request < answered || latest === current) return
|
|
194
|
-
answered = request
|
|
195
|
-
current = latest
|
|
196
|
-
follow(doc)
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.warn('[presmium] could not get the current document from the dev server', error)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
hot.on('presmium:update', ({ update, base, revision: next }: UpdateEvent) => {
|
|
202
|
-
if (base !== current) {
|
|
203
|
-
void fetchCurrent()
|
|
204
|
-
return
|
|
205
|
-
}
|
|
206
|
-
current = next
|
|
207
|
-
updates++
|
|
208
|
-
follow(applyUpdate(original(), update))
|
|
209
|
-
})
|
|
210
|
-
hot.on('vite:ws:connect', () => void fetchCurrent())
|
|
211
|
-
void fetchCurrent()
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function localeOf(path: string, base: string, langs: string[]): string {
|
|
215
|
-
const rest = path.startsWith(base) ? path.slice(base.length) : path.replace(/^\//, '')
|
|
216
|
-
const first = rest.split('/')[0]!
|
|
217
|
-
return langs.includes(first) ? first : 'root'
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function createPresmiumTheme(input: ThemeInput): Theme {
|
|
221
|
-
return {
|
|
222
|
-
extends: DefaultTheme,
|
|
223
|
-
Layout,
|
|
224
|
-
enhanceApp({ app, router, siteData }) {
|
|
225
|
-
const presmium: ThemeOptions = siteData.value.themeConfig.presmium ?? {}
|
|
226
|
-
const form: FormOptions = presmium.form ?? {}
|
|
227
|
-
const translations = input.translations ?? {}
|
|
228
|
-
const langs = Object.keys(translations)
|
|
229
|
-
const original = shallowRef(input.doc)
|
|
230
|
-
const locale = ref('root')
|
|
231
|
-
const loaded = new Map<string, Promise<IRDocument>>()
|
|
232
|
-
const documentOf = (lang: string): Promise<IRDocument> => {
|
|
233
|
-
if (lang === 'root' || !translations[lang]) return Promise.resolve(original.value)
|
|
234
|
-
let doc = loaded.get(lang)
|
|
235
|
-
if (!doc) loaded.set(lang, doc = translations[lang]().then(module => module.default))
|
|
236
|
-
return doc
|
|
237
|
-
}
|
|
238
|
-
const pages = computed(() => (locale.value === 'root' ? toPages(current.doc.value) : translationPages(current.doc.value, toPages(original.value))))
|
|
239
|
-
const prefix = computed(() => (locale.value === 'root' ? '' : `/${locale.value}`))
|
|
240
|
-
const links = computed(() => sectionLinks(pages.value, prefix.value))
|
|
241
|
-
const pageOf = (position: Position) => withBase((links.value[position.section] ?? `${prefix.value}/`).split('#')[0]!)
|
|
242
|
-
const current = createViewer(input.doc, {
|
|
243
|
-
renderer: 'web',
|
|
244
|
-
sectionHref: id => withBase(links.value[id] ?? `${prefix.value}/#${id}`),
|
|
245
|
-
sectionHeading(section, step) {
|
|
246
|
-
if (!slidesBase.value) return null
|
|
247
|
-
const t = WEB_MESSAGES[currentLocale()]
|
|
248
|
-
return h('a', {
|
|
249
|
-
'class': 'pm-open-slides',
|
|
250
|
-
'href': slideUrl(slidesBase.value, form, section.index, step),
|
|
251
|
-
'title': t.openInSlides,
|
|
252
|
-
'aria-label': t.openInSlides,
|
|
253
|
-
'data-label': t.slides,
|
|
254
|
-
}, [slidesIcon()])
|
|
255
|
-
},
|
|
256
|
-
diagnostics: import.meta.env.DEV,
|
|
257
|
-
openSource: import.meta.env.DEV && presmium.document === undefined
|
|
258
|
-
? source => void fetch(`${SOURCE_ENDPOINT}?file=${encodeURIComponent(source.file)}&line=${source.line}`, { method: 'POST', headers: { 'Presmium-Request': 'source' } })
|
|
259
|
-
: undefined,
|
|
260
|
-
navigate: position => void router.go(pageOf(position)),
|
|
261
|
-
openPresenter(position) {
|
|
262
|
-
const url = new URL(location.href)
|
|
263
|
-
url.hash = ''
|
|
264
|
-
url.search = viewSearch(url.search, { view: 'presenter', ...position })
|
|
265
|
-
window.open(url.href, 'presmium-presenter', 'popup,width=1100,height=720')
|
|
266
|
-
},
|
|
267
|
-
})
|
|
268
|
-
router.onBeforePageLoad = async (to) => {
|
|
269
|
-
if (locale.value === 'root') original.value = current.doc.value
|
|
270
|
-
const lang = localeOf(new URL(to, 'http://site').pathname, siteData.value.base, langs)
|
|
271
|
-
const doc = await documentOf(lang)
|
|
272
|
-
locale.value = lang
|
|
273
|
-
if (current.doc.value !== doc) current.replace(doc)
|
|
274
|
-
}
|
|
275
|
-
const ownPage = (path: string) => {
|
|
276
|
-
const { base } = siteData.value
|
|
277
|
-
if (!path.startsWith(base)) return false
|
|
278
|
-
const lang = localeOf(path, base, langs)
|
|
279
|
-
let rest = path.slice(base.length + (lang === 'root' ? 0 : lang.length + 1))
|
|
280
|
-
try {
|
|
281
|
-
rest = decodeURI(rest)
|
|
282
|
-
} catch {
|
|
283
|
-
}
|
|
284
|
-
const name = rest === '' || rest === 'index.html' ? 'index' : rest.replace(/\.html$/, '')
|
|
285
|
-
return toPages(original.value).some(page => page.path === name) || (lang !== 'root' && pages.value.some(page => page.path === name))
|
|
286
|
-
}
|
|
287
|
-
router.onBeforeRouteChange = (to) => {
|
|
288
|
-
if (!inBrowser) return
|
|
289
|
-
const url = new URL(to, location.href)
|
|
290
|
-
if (url.origin !== location.origin || samePage(url.pathname, location.pathname) || ownPage(url.pathname)) return
|
|
291
|
-
location.assign(url.href)
|
|
292
|
-
return false
|
|
293
|
-
}
|
|
294
|
-
const shared = createSharedStates(input.doc.meta.title)
|
|
295
|
-
installHost(app, createViewerHost(current, { lang: siteData.value.lang, shared }))
|
|
296
|
-
installViewer(app, current)
|
|
297
|
-
app.provide(PAGES, { pages, prefix })
|
|
298
|
-
app.component('PresmiumPage', PresmiumPage)
|
|
299
|
-
for (const [name, load] of Object.entries(input.components)) registerComponent(name, clientOnly(name, load))
|
|
300
|
-
if (!inBrowser) return
|
|
301
|
-
preview = input.preview
|
|
302
|
-
viewersReady = loadViewerPlugins(input.viewers ?? [])
|
|
303
|
-
const server = presmium.server ? createServerClient({ document: presmium.document }) : undefined
|
|
304
|
-
provideServer(server)
|
|
305
|
-
void startKernel(input.plugins, input.doc, current, shared.transports, server)
|
|
306
|
-
watch(() => presmiumService(OPERATIONS), (operations, _, onCleanup) => {
|
|
307
|
-
if (operations) onCleanup(followOperations(current, operations))
|
|
308
|
-
}, { immediate: true })
|
|
309
|
-
if (import.meta.env.PROD) serviceWorker(offlineEnabled(input.doc.config.presmium))
|
|
310
|
-
followDevServer(() => original.value, input.revision, presmium.ir ?? IR_ENDPOINT, (doc) => {
|
|
311
|
-
original.value = doc
|
|
312
|
-
if (locale.value === 'root') current.replace(doc)
|
|
313
|
-
const site = siteData.value
|
|
314
|
-
const nav = sidebar(doc, toPages(doc))
|
|
315
|
-
if (site.title === doc.meta.title && JSON.stringify(site.themeConfig.sidebar) === JSON.stringify(nav)) return
|
|
316
|
-
siteData.value = { ...site, title: doc.meta.title, themeConfig: { ...site.themeConfig, sidebar: nav } }
|
|
317
|
-
})
|
|
318
|
-
},
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
import.meta.hot?.dispose(() => stopPresmium())
|
package/src/node/addons.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
-
import { join, resolve } from 'node:path'
|
|
3
|
-
import { pathToFileURL } from 'node:url'
|
|
4
|
-
import { isRecord } from '@lincyaw/kernel'
|
|
5
|
-
import type { PresmiumNodePlugin, RenderContext } from '@lincyaw/kernel'
|
|
6
|
-
|
|
7
|
-
const CLIENT_PLUGIN = 'src/client/plugin.ts'
|
|
8
|
-
const VIEWER_PLUGIN = 'src/client/viewer.ts'
|
|
9
|
-
const VITE_CONFIG = 'vite.config.ts'
|
|
10
|
-
const COMPONENT = /^[^.]+\.(vue|[jt]sx?)$/
|
|
11
|
-
|
|
12
|
-
export interface VitePluginsEntry {
|
|
13
|
-
file: string
|
|
14
|
-
config: Record<string, unknown>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ClientModules {
|
|
18
|
-
components: Map<string, string>
|
|
19
|
-
plugins: string[]
|
|
20
|
-
viewers: { name: string, file: string }[]
|
|
21
|
-
vite: string[]
|
|
22
|
-
nodeVite: VitePluginsEntry[]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function collect(dir: string, name: string, modules: ClientModules, plugin: boolean): void {
|
|
26
|
-
const components = join(dir, 'components')
|
|
27
|
-
if (existsSync(components)) {
|
|
28
|
-
for (const file of readdirSync(components).filter(f => COMPONENT.test(f)).sort()) {
|
|
29
|
-
modules.components.set(file.replace(/\.[^.]+$/, ''), join(components, file))
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (existsSync(join(dir, CLIENT_PLUGIN))) modules.plugins.push(join(dir, CLIENT_PLUGIN))
|
|
33
|
-
if (existsSync(join(dir, VIEWER_PLUGIN))) modules.viewers.push({ name, file: join(dir, VIEWER_PLUGIN) })
|
|
34
|
-
if (plugin && existsSync(join(dir, VITE_CONFIG))) modules.vite.push(join(dir, VITE_CONFIG))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function manifest(dir: string): Record<string, unknown> {
|
|
38
|
-
try {
|
|
39
|
-
const pkg: unknown = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'))
|
|
40
|
-
return isRecord(pkg) ? pkg : {}
|
|
41
|
-
} catch {
|
|
42
|
-
return {}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function nodeEntry(dir: string): string | undefined {
|
|
47
|
-
const { exports } = manifest(dir)
|
|
48
|
-
const entry = isRecord(exports) ? exports['./presmium'] : undefined
|
|
49
|
-
return typeof entry === 'string' ? resolve(dir, entry) : undefined
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function nodeVitePlugins(ctx: RenderContext, presmium: Record<string, unknown>, always = false): Promise<VitePluginsEntry[]> {
|
|
53
|
-
if (ctx.vitePlugins.length === 0 && !always) return []
|
|
54
|
-
const entries: VitePluginsEntry[] = []
|
|
55
|
-
for (const name of ctx.plugins) {
|
|
56
|
-
const dir = ctx.pluginDirs[name]
|
|
57
|
-
const file = dir && nodeEntry(dir)
|
|
58
|
-
if (!file) continue
|
|
59
|
-
const plugin = (await import(pathToFileURL(file).href).catch(() => ({}))).default as PresmiumNodePlugin | undefined
|
|
60
|
-
if (typeof plugin?.vitePlugins !== 'function') continue
|
|
61
|
-
const config = presmium[plugin.id]
|
|
62
|
-
entries.push({ file, config: isRecord(config) ? config : {} })
|
|
63
|
-
}
|
|
64
|
-
return entries
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function clientModules(ctx: RenderContext, nodeVite: VitePluginsEntry[]): ClientModules {
|
|
68
|
-
const modules: ClientModules = { components: new Map(), plugins: [], viewers: [], vite: [], nodeVite }
|
|
69
|
-
for (const name of ctx.plugins) {
|
|
70
|
-
const dir = ctx.pluginDirs[name]
|
|
71
|
-
const { name: pkg } = dir ? manifest(dir) : {}
|
|
72
|
-
if (dir) collect(dir, typeof pkg === 'string' ? pkg : name, modules, true)
|
|
73
|
-
}
|
|
74
|
-
collect(ctx.deckDir, 'deck', modules, false)
|
|
75
|
-
return modules
|
|
76
|
-
}
|