@xyd-js/plugin-docs 0.1.0-xyd.2 → 0.1.0-xyd.3
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/CHANGELOG.md +41 -0
- package/TODO.md +1 -0
- package/package.json +4 -14
- package/src/const.ts +7 -0
- package/src/declarations.d.ts +23 -0
- package/src/index.ts +305 -0
- package/src/pages/context.tsx +9 -0
- package/src/pages/layout.tsx +263 -0
- package/src/pages/metatags.ts +96 -0
- package/src/pages/page.tsx +363 -0
- package/src/presets/docs/index.ts +232 -0
- package/src/presets/docs/settings.ts +75 -0
- package/src/presets/graphql/index.ts +69 -0
- package/src/presets/openapi/index.ts +66 -0
- package/src/presets/sources/index.ts +74 -0
- package/src/presets/uniform/index.ts +751 -0
- package/src/types.ts +38 -0
- package/src/utils.ts +19 -0
- package/tsconfig.json +51 -0
- package/tsup.config.ts +74 -0
- package/dist/index.d.ts +0 -42
- package/dist/index.js +0 -4307
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { MetaTags } from "@xyd-js/core";
|
|
2
|
+
|
|
3
|
+
export const SUPPORTED_META_TAGS: MetaTags = {
|
|
4
|
+
"robots": "name",
|
|
5
|
+
"charset": "name",
|
|
6
|
+
"viewport": "name",
|
|
7
|
+
"description": "name",
|
|
8
|
+
"keywords": "name",
|
|
9
|
+
"author": "name",
|
|
10
|
+
"googlebot": "name",
|
|
11
|
+
"google": "name",
|
|
12
|
+
"google-site-verification": "name",
|
|
13
|
+
"generator": "name",
|
|
14
|
+
"theme-color": "name",
|
|
15
|
+
"color-scheme": "name",
|
|
16
|
+
"format-detection": "name",
|
|
17
|
+
"referrer": "name",
|
|
18
|
+
"refresh": "name",
|
|
19
|
+
"rating": "name",
|
|
20
|
+
"revisit-after": "name",
|
|
21
|
+
"language": "name",
|
|
22
|
+
"copyright": "name",
|
|
23
|
+
"reply-to": "name",
|
|
24
|
+
"distribution": "name",
|
|
25
|
+
"coverage": "name",
|
|
26
|
+
"category": "name",
|
|
27
|
+
"target": "name",
|
|
28
|
+
"HandheldFriendly": "name",
|
|
29
|
+
"MobileOptimized": "name",
|
|
30
|
+
"apple-mobile-web-app-capable": "name",
|
|
31
|
+
"apple-mobile-web-app-status-bar-style": "name",
|
|
32
|
+
"apple-mobile-web-app-title": "name",
|
|
33
|
+
"application-name": "name",
|
|
34
|
+
"msapplication-TileColor": "name",
|
|
35
|
+
"msapplication-TileImage": "name",
|
|
36
|
+
"msapplication-config": "name",
|
|
37
|
+
|
|
38
|
+
"og:title": "property",
|
|
39
|
+
"og:type": "property",
|
|
40
|
+
"og:url": "property",
|
|
41
|
+
"og:image": "property",
|
|
42
|
+
"og:description": "property",
|
|
43
|
+
"og:site_name": "property",
|
|
44
|
+
"og:locale": "property",
|
|
45
|
+
"og:video": "property",
|
|
46
|
+
"og:audio": "property",
|
|
47
|
+
|
|
48
|
+
"twitter:card": "property",
|
|
49
|
+
"twitter:site": "property",
|
|
50
|
+
"twitter:creator": "property",
|
|
51
|
+
"twitter:title": "property",
|
|
52
|
+
"twitter:description": "property",
|
|
53
|
+
"twitter:image": "property",
|
|
54
|
+
"twitter:image:alt": "property",
|
|
55
|
+
"twitter:player": "property",
|
|
56
|
+
"twitter:player:width": "property",
|
|
57
|
+
"twitter:player:height": "property",
|
|
58
|
+
"twitter:app:name:iphone": "property",
|
|
59
|
+
"twitter:app:id:iphone": "property",
|
|
60
|
+
"twitter:app:url:iphone": "property",
|
|
61
|
+
|
|
62
|
+
"article:published_time": "property",
|
|
63
|
+
"article:modified_time": "property",
|
|
64
|
+
"article:expiration_time": "property",
|
|
65
|
+
"article:author": "property",
|
|
66
|
+
"article:section": "property",
|
|
67
|
+
"article:tag": "property",
|
|
68
|
+
|
|
69
|
+
"book:author": "property",
|
|
70
|
+
"book:isbn": "property",
|
|
71
|
+
"book:release_date": "property",
|
|
72
|
+
"book:tag": "property",
|
|
73
|
+
|
|
74
|
+
"profile:first_name": "property",
|
|
75
|
+
"profile:last_name": "property",
|
|
76
|
+
"profile:username": "property",
|
|
77
|
+
"profile:gender": "property",
|
|
78
|
+
|
|
79
|
+
"music:duration": "property",
|
|
80
|
+
"music:album": "property",
|
|
81
|
+
"music:album:disc": "property",
|
|
82
|
+
"music:album:track": "property",
|
|
83
|
+
"music:musician": "property",
|
|
84
|
+
"music:song": "property",
|
|
85
|
+
"music:song:disc": "property",
|
|
86
|
+
"music:song:track": "property",
|
|
87
|
+
|
|
88
|
+
"video:actor": "property",
|
|
89
|
+
"video:actor:role": "property",
|
|
90
|
+
"video:director": "property",
|
|
91
|
+
"video:writer": "property",
|
|
92
|
+
"video:duration": "property",
|
|
93
|
+
"video:release_date": "property",
|
|
94
|
+
"video:tag": "property",
|
|
95
|
+
"video:series": "property"
|
|
96
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useMemo, useContext, ReactElement, SVGProps } from "react";
|
|
5
|
+
import { redirect, ScrollRestoration, useLocation } from "react-router";
|
|
6
|
+
|
|
7
|
+
import { MetadataMap, Metadata, Settings } from "@xyd-js/core"
|
|
8
|
+
import { ContentFS } from "@xyd-js/content"
|
|
9
|
+
import { markdownPlugins } from "@xyd-js/content/md"
|
|
10
|
+
import { mapSettingsToProps } from "@xyd-js/framework/hydration";
|
|
11
|
+
import { FrameworkPage, type FwSidebarGroupProps } from "@xyd-js/framework/react";
|
|
12
|
+
import type { IBreadcrumb, INavLinks } from "@xyd-js/ui";
|
|
13
|
+
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
import virtualSettings from "virtual:xyd-settings";
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
const { settings } = virtualSettings as Settings
|
|
18
|
+
import { PageContext } from "./context";
|
|
19
|
+
import { SUPPORTED_META_TAGS } from "./metatags";
|
|
20
|
+
|
|
21
|
+
function getPathname(url: string) {
|
|
22
|
+
const parsedUrl = new URL(url);
|
|
23
|
+
return parsedUrl.pathname.replace(/^\//, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface loaderData {
|
|
27
|
+
sidebarGroups: FwSidebarGroupProps[]
|
|
28
|
+
breadcrumbs: IBreadcrumb[],
|
|
29
|
+
toc: MetadataMap,
|
|
30
|
+
slug: string
|
|
31
|
+
code: string
|
|
32
|
+
metadata: Metadata | null
|
|
33
|
+
rawPage: string // TODO: in the future routing like /docs/quickstart.md but some issues with react-router like *.md in `route` config
|
|
34
|
+
navlinks?: INavLinks,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class timedebugLoader {
|
|
38
|
+
static get total() {
|
|
39
|
+
console.time('loader:total')
|
|
40
|
+
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static get totalEnd() {
|
|
45
|
+
console.timeEnd('loader:total')
|
|
46
|
+
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static get compile() {
|
|
51
|
+
console.time('loader:compile')
|
|
52
|
+
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static get compileEnd() {
|
|
57
|
+
console.timeEnd('loader:compile')
|
|
58
|
+
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static get mapSettingsToProps() {
|
|
63
|
+
console.time('loader:mapSettingsToProps')
|
|
64
|
+
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static get mapSettingsToPropsEnd() {
|
|
69
|
+
console.timeEnd('loader:mapSettingsToProps')
|
|
70
|
+
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function loader({ request }: { request: any }) {
|
|
76
|
+
if (!globalThis.__xydPagePathMapping) {
|
|
77
|
+
throw new Error("PagePathMapping not found")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const timedebug = timedebugLoader
|
|
81
|
+
|
|
82
|
+
timedebug.total
|
|
83
|
+
|
|
84
|
+
const slug = getPathname(request.url || "index") || "index"
|
|
85
|
+
if (path.extname(slug)) {
|
|
86
|
+
console.log(`(loader): currently not supporting file extension in slug: ${slug}`);
|
|
87
|
+
timedebug.totalEnd
|
|
88
|
+
return {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
timedebug.mapSettingsToProps
|
|
92
|
+
|
|
93
|
+
const {
|
|
94
|
+
groups: sidebarGroups,
|
|
95
|
+
breadcrumbs,
|
|
96
|
+
navlinks,
|
|
97
|
+
hiddenPages,
|
|
98
|
+
metadata
|
|
99
|
+
} = await mapSettingsToProps( // TOOD: we use mapSettingsToProps twice (in layout) - refactor
|
|
100
|
+
settings || globalThis.__xydSettings,
|
|
101
|
+
globalThis.__xydPagePathMapping,
|
|
102
|
+
slug,
|
|
103
|
+
)
|
|
104
|
+
timedebug.mapSettingsToPropsEnd
|
|
105
|
+
|
|
106
|
+
function redirectFallback() {
|
|
107
|
+
if (!sidebarGroups) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
const firstItem = findFirstUrl(sidebarGroups?.[0]?.items);
|
|
111
|
+
|
|
112
|
+
if (firstItem) {
|
|
113
|
+
return redirect(firstItem)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (hiddenPages?.[slug]) {
|
|
118
|
+
const resp = redirectFallback()
|
|
119
|
+
if (resp) {
|
|
120
|
+
timedebug.totalEnd
|
|
121
|
+
return resp
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let code = ""
|
|
126
|
+
let rawPage = ""
|
|
127
|
+
|
|
128
|
+
const mdPlugins = markdownPlugins({
|
|
129
|
+
maxDepth: metadata?.maxTocDepth || settings?.theme?.maxTocDepth || 2,
|
|
130
|
+
}, settings)
|
|
131
|
+
|
|
132
|
+
const contentFs = new ContentFS(settings, mdPlugins.remarkPlugins, mdPlugins.rehypePlugins)
|
|
133
|
+
|
|
134
|
+
const pagePath = globalThis.__xydPagePathMapping[slug]
|
|
135
|
+
if (pagePath) {
|
|
136
|
+
timedebug.compile
|
|
137
|
+
code = await contentFs.compile(pagePath)
|
|
138
|
+
rawPage = await contentFs.readRaw(pagePath)
|
|
139
|
+
timedebug.compileEnd
|
|
140
|
+
} else {
|
|
141
|
+
const resp = redirectFallback()
|
|
142
|
+
if (resp) {
|
|
143
|
+
timedebug.totalEnd
|
|
144
|
+
return resp
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
timedebug.totalEnd
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
sidebarGroups,
|
|
152
|
+
breadcrumbs,
|
|
153
|
+
navlinks,
|
|
154
|
+
slug,
|
|
155
|
+
code,
|
|
156
|
+
metadata,
|
|
157
|
+
rawPage,
|
|
158
|
+
} as loaderData
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface MetaTag {
|
|
162
|
+
title?: string
|
|
163
|
+
|
|
164
|
+
name?: string
|
|
165
|
+
|
|
166
|
+
property?: string
|
|
167
|
+
|
|
168
|
+
content?: string
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// TODO: better SEO (use https://github.com/unjs/unhead?)
|
|
172
|
+
export function meta(props: any) {
|
|
173
|
+
const {
|
|
174
|
+
title = "",
|
|
175
|
+
description = "",
|
|
176
|
+
} = props?.data?.metadata || {}
|
|
177
|
+
|
|
178
|
+
const metaTags: MetaTag[] = [
|
|
179
|
+
{ title: title },
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
if (description) {
|
|
183
|
+
metaTags.push({
|
|
184
|
+
name: "description",
|
|
185
|
+
content: description,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const metaTagsMap: {[key: string]: MetaTag} = {}
|
|
190
|
+
|
|
191
|
+
for (const key in settings?.seo?.metatags) {
|
|
192
|
+
const metaType = SUPPORTED_META_TAGS[key]
|
|
193
|
+
if (!metaType) {
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (description && key === "description") {
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
metaTagsMap[key] = {
|
|
202
|
+
[metaType]: key,
|
|
203
|
+
content: settings?.seo?.metatags[key],
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// TOOD: filter?
|
|
208
|
+
for (const key in props?.data?.metadata) {
|
|
209
|
+
const metaType = SUPPORTED_META_TAGS[key]
|
|
210
|
+
if (!metaType) {
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (description && key === "description") {
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
metaTagsMap[key] = {
|
|
219
|
+
[metaType]: key,
|
|
220
|
+
content: props?.data?.metadata[key],
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (props?.data?.metadata?.noindex) {
|
|
225
|
+
metaTagsMap["robots"] = {
|
|
226
|
+
name: "robots",
|
|
227
|
+
content: "noindex",
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const key in metaTagsMap) {
|
|
232
|
+
metaTags.push(metaTagsMap[key])
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return metaTags
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function findFirstUrl(items: any = []): string {
|
|
239
|
+
const queue = [...items];
|
|
240
|
+
|
|
241
|
+
while (queue.length > 0) {
|
|
242
|
+
const item = queue.shift();
|
|
243
|
+
|
|
244
|
+
if (item.href) {
|
|
245
|
+
return item.href;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (item.items) {
|
|
249
|
+
queue.push(...item.items);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const createElementWithKeys = (type: any, props: any) => {
|
|
257
|
+
// Process children to add keys to all elements
|
|
258
|
+
const processChildren = (childrenArray: any[]): any[] => {
|
|
259
|
+
return childrenArray.map((child, index) => {
|
|
260
|
+
// If the child is a React element and doesn't have a key, add one
|
|
261
|
+
if (React.isValidElement(child) && !child.key) {
|
|
262
|
+
return React.cloneElement(child, { key: `mdx-${index}` });
|
|
263
|
+
}
|
|
264
|
+
// If the child is an array, process it recursively
|
|
265
|
+
if (Array.isArray(child)) {
|
|
266
|
+
return processChildren(child);
|
|
267
|
+
}
|
|
268
|
+
return child;
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Handle both cases: children as separate args or as props.children
|
|
273
|
+
let processedChildren;
|
|
274
|
+
|
|
275
|
+
if (props && props.children) {
|
|
276
|
+
if (Array.isArray(props.children)) {
|
|
277
|
+
processedChildren = processChildren(props.children);
|
|
278
|
+
} else if (React.isValidElement(props.children) && !props.children.key) {
|
|
279
|
+
// Single child without key
|
|
280
|
+
processedChildren = React.cloneElement(props.children, { key: 'mdx-child' });
|
|
281
|
+
} else {
|
|
282
|
+
// Single child with key or non-React element
|
|
283
|
+
processedChildren = props.children;
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
processedChildren = [];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Create the element with processed children
|
|
290
|
+
return React.createElement(type, {
|
|
291
|
+
...props,
|
|
292
|
+
children: processedChildren
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// TODO: move to content?
|
|
297
|
+
function mdxExport(code: string) {
|
|
298
|
+
// Create a wrapper around React.createElement that adds keys to elements in lists
|
|
299
|
+
const scope = {
|
|
300
|
+
Fragment: React.Fragment,
|
|
301
|
+
jsxs: createElementWithKeys,
|
|
302
|
+
jsx: createElementWithKeys,
|
|
303
|
+
jsxDEV: createElementWithKeys,
|
|
304
|
+
}
|
|
305
|
+
const fn = new Function(...Object.keys(scope), code)
|
|
306
|
+
|
|
307
|
+
return fn(scope)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// // TODO: move to content?
|
|
311
|
+
function mdxContent(code: string) {
|
|
312
|
+
const content = mdxExport(code) // TODO: fix any
|
|
313
|
+
if (!mdxExport) {
|
|
314
|
+
return {}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
component: content?.default,
|
|
319
|
+
toc: content?.toc,
|
|
320
|
+
metadata: content?.frontmatter,
|
|
321
|
+
themeSettings: content?.themeSettings || {},
|
|
322
|
+
page: content?.page || false,
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function MemoMDXComponent(codeComponent: any) {
|
|
327
|
+
return useMemo(
|
|
328
|
+
() => codeComponent ? codeComponent : null,
|
|
329
|
+
[codeComponent]
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export default function DocsPage({ loaderData }: { loaderData: loaderData }) {
|
|
334
|
+
const location = useLocation()
|
|
335
|
+
|
|
336
|
+
const { theme } = useContext(PageContext)
|
|
337
|
+
if (!theme) {
|
|
338
|
+
throw new Error("BaseTheme not found")
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const content = mdxContent(loaderData.code)
|
|
342
|
+
const Content = MemoMDXComponent(content.component)
|
|
343
|
+
|
|
344
|
+
const themeContentComponents = theme.reactContentComponents()
|
|
345
|
+
const { Page } = theme
|
|
346
|
+
|
|
347
|
+
return <FrameworkPage
|
|
348
|
+
key={location.pathname}
|
|
349
|
+
metadata={content.metadata}
|
|
350
|
+
breadcrumbs={loaderData.breadcrumbs}
|
|
351
|
+
rawPage={loaderData.rawPage}
|
|
352
|
+
toc={content.toc || []}
|
|
353
|
+
navlinks={loaderData.navlinks}
|
|
354
|
+
ContentComponent={Content}
|
|
355
|
+
>
|
|
356
|
+
<Page>
|
|
357
|
+
<Content components={themeContentComponents} />
|
|
358
|
+
<ScrollRestoration />
|
|
359
|
+
</Page>
|
|
360
|
+
</FrameworkPage>
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { createServer, Plugin as VitePlugin } from "vite";
|
|
6
|
+
import { route } from "@react-router/dev/routes";
|
|
7
|
+
|
|
8
|
+
import { Settings } from "@xyd-js/core";
|
|
9
|
+
|
|
10
|
+
import { Preset, PresetData } from "../../types";
|
|
11
|
+
import { readSettings } from "./settings";
|
|
12
|
+
import { DEFAULT_THEME, THEME_CONFIG_FOLDER } from "../../const";
|
|
13
|
+
import { getDocsPluginBasePath, getHostPath } from "../../utils";
|
|
14
|
+
|
|
15
|
+
interface docsPluginOptions {
|
|
16
|
+
urlPrefix?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// TODO: find better solution - maybe something what rr7 use?
|
|
20
|
+
async function loadModule(filePath: string) {
|
|
21
|
+
const server = await createServer({
|
|
22
|
+
optimizeDeps: {
|
|
23
|
+
include: ["react/jsx-runtime"],
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const module = await server.ssrLoadModule(filePath);
|
|
29
|
+
return module.default;
|
|
30
|
+
} finally {
|
|
31
|
+
await server.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function preinstall() {
|
|
36
|
+
return async function docsPluginInner(_, data: PresetData) {
|
|
37
|
+
// TODO: configurable root?
|
|
38
|
+
const root = process.cwd()
|
|
39
|
+
|
|
40
|
+
const settings = await readSettings()
|
|
41
|
+
if (settings && !settings.theme) {
|
|
42
|
+
settings.theme = {
|
|
43
|
+
name: DEFAULT_THEME
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let themeRoutesExists = false
|
|
48
|
+
try {
|
|
49
|
+
await fs.access(path.join(root, THEME_CONFIG_FOLDER, "./routes.ts"))
|
|
50
|
+
themeRoutesExists = true
|
|
51
|
+
} catch (_) {
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (themeRoutesExists) {
|
|
55
|
+
const routeMod = await loadModule(path.join(root, THEME_CONFIG_FOLDER, "./routes.ts"))
|
|
56
|
+
|
|
57
|
+
const routes = routeMod((routePath, routeFile, routeOptions) => {
|
|
58
|
+
return route(routePath, path.join(root, THEME_CONFIG_FOLDER, routeFile), routeOptions)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
data.routes.push(...routes)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
settings
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// TODO: maybe later as a separate plugin?
|
|
71
|
+
function vitePluginSettings() {
|
|
72
|
+
return async function ({ preinstall }): Promise<VitePlugin> {
|
|
73
|
+
return {
|
|
74
|
+
name: 'virtual:xyd-settings',
|
|
75
|
+
resolveId(id) {
|
|
76
|
+
if (id === 'virtual:xyd-settings') {
|
|
77
|
+
return id + '.jsx'; // Return the module with .jsx extension
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
},
|
|
81
|
+
async load(id) { // TODO: better cuz we probably dont neeed `get settings()`
|
|
82
|
+
if (id === 'virtual:xyd-settings.jsx') {
|
|
83
|
+
return `
|
|
84
|
+
export default {
|
|
85
|
+
get settings() {
|
|
86
|
+
return globalThis.__xydSettings || ${typeof preinstall.settings === "string" ? preinstall.settings : JSON.stringify(preinstall.settings)}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
`
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
export function vitePluginThemeCSS() {
|
|
99
|
+
return async function ({
|
|
100
|
+
preinstall
|
|
101
|
+
}: {
|
|
102
|
+
preinstall: {
|
|
103
|
+
settings: Settings
|
|
104
|
+
}
|
|
105
|
+
}): Promise<VitePlugin> {
|
|
106
|
+
return {
|
|
107
|
+
name: 'virtual:xyd-theme/index.css',
|
|
108
|
+
|
|
109
|
+
resolveId(source) {
|
|
110
|
+
if (source === 'virtual:xyd-theme/index.css') {
|
|
111
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
112
|
+
const __dirname = path.dirname(__filename);
|
|
113
|
+
|
|
114
|
+
const themeName = preinstall.settings.theme?.name || DEFAULT_THEME
|
|
115
|
+
let themePath = ""
|
|
116
|
+
|
|
117
|
+
if (process.env.XYD_CLI) {
|
|
118
|
+
themePath = path.join(getHostPath(), `node_modules/@xyd-js/theme-${themeName}/dist`)
|
|
119
|
+
} else {
|
|
120
|
+
themePath = path.join(path.resolve(__dirname, "../../"), `xyd-theme-${themeName}/dist`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return path.join(themePath, "index.css")
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function vitePluginThemeOverrideCSS() {
|
|
133
|
+
return async function ({ preinstall }: { preinstall: { settings: Settings } }): Promise<VitePlugin> {
|
|
134
|
+
return {
|
|
135
|
+
name: 'virtual:xyd-theme-override-css',
|
|
136
|
+
|
|
137
|
+
async resolveId(id) {
|
|
138
|
+
if (id === 'virtual:xyd-theme-override/index.css') {
|
|
139
|
+
const root = process.cwd();
|
|
140
|
+
const filePath = path.join(root, THEME_CONFIG_FOLDER, "./index.css");
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await fs.access(filePath);
|
|
144
|
+
return filePath;
|
|
145
|
+
} catch {
|
|
146
|
+
// File does not exist, omit it
|
|
147
|
+
return 'virtual:xyd-theme-override/empty.css';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async load(id) {
|
|
154
|
+
if (id === 'virtual:xyd-theme-override/empty.css') {
|
|
155
|
+
// Return an empty module
|
|
156
|
+
return '';
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function vitePluginTheme() {
|
|
165
|
+
return async function ({
|
|
166
|
+
preinstall
|
|
167
|
+
}: {
|
|
168
|
+
preinstall: {
|
|
169
|
+
settings: Settings
|
|
170
|
+
}
|
|
171
|
+
}): Promise<VitePlugin> {
|
|
172
|
+
return {
|
|
173
|
+
name: 'virtual:xyd-theme',
|
|
174
|
+
resolveId(id) {
|
|
175
|
+
if (id === 'virtual:xyd-theme') {
|
|
176
|
+
return id;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
},
|
|
180
|
+
async load(id) {
|
|
181
|
+
if (id === 'virtual:xyd-theme') {
|
|
182
|
+
// return ''
|
|
183
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
184
|
+
const __dirname = path.dirname(__filename);
|
|
185
|
+
|
|
186
|
+
const themeName = preinstall.settings.theme?.name || DEFAULT_THEME
|
|
187
|
+
let themePath = ""
|
|
188
|
+
|
|
189
|
+
if (process.env.XYD_CLI) {
|
|
190
|
+
themePath = `@xyd-js/theme-${themeName}`
|
|
191
|
+
} else {
|
|
192
|
+
themePath = path.join(path.resolve(__dirname, "../../"), `xyd-theme-${themeName}/src`)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Return a module that imports the theme from the local workspace
|
|
196
|
+
return `
|
|
197
|
+
import Theme from '${themePath}';
|
|
198
|
+
|
|
199
|
+
export default Theme;
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function preset(settings: Settings, options: docsPluginOptions) {
|
|
209
|
+
const basePath = getDocsPluginBasePath()
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
preinstall: [
|
|
213
|
+
preinstall,
|
|
214
|
+
],
|
|
215
|
+
routes: [
|
|
216
|
+
route("", path.join(basePath, "src/pages/docs.tsx")),
|
|
217
|
+
// TODO: custom routes
|
|
218
|
+
route(options.urlPrefix ? `${options.urlPrefix}/*` : "*", path.join(basePath, "src/pages/docs.tsx"), {
|
|
219
|
+
id: "xyd-plugin-docs/docs",
|
|
220
|
+
}),
|
|
221
|
+
],
|
|
222
|
+
vitePlugins: [
|
|
223
|
+
vitePluginSettings,
|
|
224
|
+
vitePluginTheme,
|
|
225
|
+
vitePluginThemeCSS,
|
|
226
|
+
vitePluginThemeOverrideCSS,
|
|
227
|
+
],
|
|
228
|
+
basePath
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export const docsPreset = preset as Preset<unknown>
|