polen 0.9.0-next.5 → 0.9.0-next.7
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 +42 -0
- package/build/api/builder/builder.d.ts +1 -0
- package/build/api/builder/builder.d.ts.map +1 -1
- package/build/api/builder/builder.js +1 -0
- package/build/api/builder/builder.js.map +1 -1
- package/build/api/config/configurator.d.ts +21 -5
- package/build/api/config/configurator.d.ts.map +1 -1
- package/build/api/config/configurator.js +12 -0
- package/build/api/config/configurator.js.map +1 -1
- package/build/api/config-resolver/resolve.d.ts +1 -1
- package/build/api/config-resolver/resolve.js +1 -1
- package/build/api/config-resolver/vite.d.ts.map +1 -1
- package/build/api/config-resolver/vite.js +1 -0
- package/build/api/config-resolver/vite.js.map +1 -1
- package/build/api/schema/read.d.ts +1 -1
- package/build/api/singletons/markdown/markdown.d.ts.map +1 -1
- package/build/api/singletons/markdown/markdown.js +33 -11
- package/build/api/singletons/markdown/markdown.js.map +1 -1
- package/build/api/utils/asset-url/asset-url.d.ts +20 -0
- package/build/api/utils/asset-url/asset-url.d.ts.map +1 -0
- package/build/api/utils/asset-url/asset-url.js +35 -0
- package/build/api/utils/asset-url/asset-url.js.map +1 -0
- package/build/api/utils/asset-url/index.d.ts +2 -0
- package/build/api/utils/asset-url/index.d.ts.map +1 -0
- package/build/api/utils/asset-url/index.js +2 -0
- package/build/api/utils/asset-url/index.js.map +1 -0
- package/build/api/vite/plugins/core.d.ts.map +1 -1
- package/build/api/vite/plugins/core.js +10 -4
- package/build/api/vite/plugins/core.js.map +1 -1
- package/build/api/vite/plugins/pages.d.ts.map +1 -1
- package/build/api/vite/plugins/pages.js +27 -2
- package/build/api/vite/plugins/pages.js.map +1 -1
- package/build/cli/commands/build.js +2 -0
- package/build/cli/commands/build.js.map +1 -1
- package/build/cli/commands/dev.js +2 -0
- package/build/cli/commands/dev.js.map +1 -1
- package/build/lib/shiki/index.d.ts +2 -0
- package/build/lib/shiki/index.d.ts.map +1 -0
- package/build/lib/shiki/index.js +2 -0
- package/build/lib/shiki/index.js.map +1 -0
- package/build/lib/shiki/shiki.d.ts +26 -0
- package/build/lib/shiki/shiki.d.ts.map +1 -0
- package/build/lib/shiki/shiki.js +105 -0
- package/build/lib/shiki/shiki.js.map +1 -0
- package/build/lib/vite-virtual/identifier.d.ts +2 -2
- package/build/project-data.d.ts +1 -0
- package/build/project-data.d.ts.map +1 -1
- package/build/template/components/CodeBlock.d.ts +17 -0
- package/build/template/components/CodeBlock.d.ts.map +1 -0
- package/build/template/components/CodeBlock.jsx +42 -0
- package/build/template/components/CodeBlock.jsx.map +1 -0
- package/build/template/components/Link.d.ts.map +1 -1
- package/build/template/components/Link.jsx +2 -1
- package/build/template/components/Link.jsx.map +1 -1
- package/build/template/entry.client.jsx +3 -0
- package/build/template/entry.client.jsx.map +1 -1
- package/build/template/routes/root.d.ts.map +1 -1
- package/build/template/routes/root.jsx +37 -2
- package/build/template/routes/root.jsx.map +1 -1
- package/build/template/server/manifest.d.ts +1 -1
- package/build/template/server/manifest.d.ts.map +1 -1
- package/build/template/server/manifest.js +6 -5
- package/build/template/server/manifest.js.map +1 -1
- package/build/template/server/render-page.d.ts.map +1 -1
- package/build/template/server/render-page.jsx +2 -1
- package/build/template/server/render-page.jsx.map +1 -1
- package/build/template/server/ssg/generate.d.ts.map +1 -1
- package/build/template/server/ssg/generate.js +50 -7
- package/build/template/server/ssg/generate.js.map +1 -1
- package/build/template/server/view.d.ts.map +1 -1
- package/build/template/server/view.js +4 -1
- package/build/template/server/view.js.map +1 -1
- package/package.json +10 -1
- package/src/api/builder/builder.ts +2 -0
- package/src/api/config/configurator.ts +34 -5
- package/src/api/config-resolver/resolve.ts +1 -1
- package/src/api/config-resolver/vite.ts +1 -0
- package/src/api/schema/read.ts +1 -1
- package/src/api/singletons/markdown/markdown.test.ts +89 -0
- package/src/api/singletons/markdown/markdown.ts +35 -13
- package/src/api/utils/asset-url/asset-url.test.ts +47 -0
- package/src/api/utils/asset-url/asset-url.ts +38 -0
- package/src/api/utils/asset-url/index.ts +1 -0
- package/src/api/vite/plugins/core.ts +10 -4
- package/src/api/vite/plugins/pages.ts +27 -2
- package/src/cli/commands/build.ts +5 -0
- package/src/cli/commands/dev.ts +5 -0
- package/src/lib/shiki/index.ts +1 -0
- package/src/lib/shiki/shiki.test.ts +107 -0
- package/src/lib/shiki/shiki.ts +161 -0
- package/src/lib/vite-virtual/identifier.ts +2 -2
- package/src/project-data.ts +1 -0
- package/src/template/components/CodeBlock.tsx +73 -0
- package/src/template/components/Link.tsx +4 -3
- package/src/template/entry.client.tsx +3 -0
- package/src/template/routes/root.tsx +37 -2
- package/src/template/server/manifest.ts +6 -3
- package/src/template/server/render-page.tsx +2 -1
- package/src/template/server/ssg/generate.ts +70 -7
- package/src/template/server/view.ts +4 -1
- package/src/template/styles/code-block.css +186 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
import { expect, test } from 'vitest'
|
2
|
+
import { getHighlighter, highlightCode } from './shiki.js'
|
3
|
+
|
4
|
+
test(`getHighlighter returns singleton instance`, async () => {
|
5
|
+
const highlighter1 = await getHighlighter()
|
6
|
+
const highlighter2 = await getHighlighter()
|
7
|
+
|
8
|
+
expect(highlighter1).toBe(highlighter2)
|
9
|
+
})
|
10
|
+
|
11
|
+
test(`highlightCode generates HTML with syntax highlighting`, async () => {
|
12
|
+
const code = `const hello = "world"`
|
13
|
+
const result = await highlightCode({
|
14
|
+
code,
|
15
|
+
lang: `javascript`,
|
16
|
+
theme: `light`,
|
17
|
+
})
|
18
|
+
|
19
|
+
expect(result).toContain(`<pre`)
|
20
|
+
expect(result).toContain(`shiki`)
|
21
|
+
expect(result).toContain(`hello`)
|
22
|
+
expect(result).toContain(`world`)
|
23
|
+
})
|
24
|
+
|
25
|
+
test(`highlightCode supports TypeScript`, async () => {
|
26
|
+
const code = `interface User { name: string }`
|
27
|
+
const result = await highlightCode({
|
28
|
+
code,
|
29
|
+
lang: `typescript`,
|
30
|
+
theme: `light`,
|
31
|
+
})
|
32
|
+
|
33
|
+
expect(result).toContain(`interface`)
|
34
|
+
expect(result).toContain(`User`)
|
35
|
+
expect(result).toContain(`string`)
|
36
|
+
})
|
37
|
+
|
38
|
+
test(`highlightCode supports GraphQL`, async () => {
|
39
|
+
const code = `type Query { user: User }`
|
40
|
+
const result = await highlightCode({
|
41
|
+
code,
|
42
|
+
lang: `graphql`,
|
43
|
+
theme: `light`,
|
44
|
+
})
|
45
|
+
|
46
|
+
expect(result).toContain(`type`)
|
47
|
+
expect(result).toContain(`Query`)
|
48
|
+
expect(result).toContain(`User`)
|
49
|
+
})
|
50
|
+
|
51
|
+
test(`highlightCode handles unknown language gracefully`, async () => {
|
52
|
+
const code = `some random text`
|
53
|
+
// Use 'text' as fallback for unknown languages
|
54
|
+
const result = await highlightCode({
|
55
|
+
code,
|
56
|
+
lang: `text`,
|
57
|
+
theme: `light`,
|
58
|
+
})
|
59
|
+
|
60
|
+
// Should still return highlighted HTML
|
61
|
+
expect(result).toContain(`<pre`)
|
62
|
+
expect(result).toContain(`some random text`)
|
63
|
+
})
|
64
|
+
|
65
|
+
test(`highlightCode applies line numbers when requested`, async () => {
|
66
|
+
const code = `line 1\nline 2\nline 3`
|
67
|
+
const result = await highlightCode({
|
68
|
+
code,
|
69
|
+
lang: `text`,
|
70
|
+
theme: `light`,
|
71
|
+
showLineNumbers: true,
|
72
|
+
})
|
73
|
+
|
74
|
+
expect(result).toContain(`data-line-numbers="true"`)
|
75
|
+
})
|
76
|
+
|
77
|
+
test(`highlightCode applies line highlighting`, async () => {
|
78
|
+
const code = `line 1\nline 2\nline 3`
|
79
|
+
const result = await highlightCode({
|
80
|
+
code,
|
81
|
+
lang: `text`,
|
82
|
+
theme: `light`,
|
83
|
+
highlightLines: [2],
|
84
|
+
})
|
85
|
+
|
86
|
+
expect(result).toContain(`data-highlighted="true"`)
|
87
|
+
})
|
88
|
+
|
89
|
+
test(`highlightCode supports both light and dark themes`, async () => {
|
90
|
+
const code = `const x = 1`
|
91
|
+
|
92
|
+
const lightResult = await highlightCode({
|
93
|
+
code,
|
94
|
+
lang: `javascript`,
|
95
|
+
theme: `light`,
|
96
|
+
})
|
97
|
+
|
98
|
+
const darkResult = await highlightCode({
|
99
|
+
code,
|
100
|
+
lang: `javascript`,
|
101
|
+
theme: `dark`,
|
102
|
+
})
|
103
|
+
|
104
|
+
// Both should contain theme CSS variables
|
105
|
+
expect(lightResult).toContain(`--shiki-light`)
|
106
|
+
expect(darkResult).toContain(`--shiki-dark`)
|
107
|
+
})
|
@@ -0,0 +1,161 @@
|
|
1
|
+
import {
|
2
|
+
transformerNotationDiff,
|
3
|
+
transformerNotationFocus,
|
4
|
+
transformerNotationHighlight,
|
5
|
+
transformerRenderWhitespace,
|
6
|
+
} from '@shikijs/transformers'
|
7
|
+
import { type BundledLanguage, type BundledTheme, createHighlighter, type Highlighter } from 'shiki'
|
8
|
+
|
9
|
+
export interface ShikiOptions {
|
10
|
+
themes?: {
|
11
|
+
light: BundledTheme
|
12
|
+
dark: BundledTheme
|
13
|
+
}
|
14
|
+
langs?: BundledLanguage[]
|
15
|
+
defaultTheme?: 'light' | 'dark'
|
16
|
+
}
|
17
|
+
|
18
|
+
const DEFAULT_THEMES = {
|
19
|
+
light: `github-light` as BundledTheme,
|
20
|
+
dark: `tokyo-night` as BundledTheme,
|
21
|
+
}
|
22
|
+
|
23
|
+
const DEFAULT_LANGS: BundledLanguage[] = [
|
24
|
+
`typescript`,
|
25
|
+
`javascript`,
|
26
|
+
`jsx`,
|
27
|
+
`tsx`,
|
28
|
+
`graphql`,
|
29
|
+
`json`,
|
30
|
+
`yaml`,
|
31
|
+
`markdown`,
|
32
|
+
`bash`,
|
33
|
+
`shell`,
|
34
|
+
`css`,
|
35
|
+
`html`,
|
36
|
+
`sql`,
|
37
|
+
`python`,
|
38
|
+
`rust`,
|
39
|
+
`go`,
|
40
|
+
`java`,
|
41
|
+
`csharp`,
|
42
|
+
`php`,
|
43
|
+
`ruby`,
|
44
|
+
`swift`,
|
45
|
+
`kotlin`,
|
46
|
+
`scala`,
|
47
|
+
`r`,
|
48
|
+
`matlab`,
|
49
|
+
`latex`,
|
50
|
+
`dockerfile`,
|
51
|
+
`makefile`,
|
52
|
+
`nginx`,
|
53
|
+
`apache`,
|
54
|
+
`xml`,
|
55
|
+
`toml`,
|
56
|
+
`ini`,
|
57
|
+
`diff`,
|
58
|
+
]
|
59
|
+
|
60
|
+
// Singleton highlighter instance
|
61
|
+
let highlighterInstance: Highlighter | null = null
|
62
|
+
let highlighterPromise: Promise<Highlighter> | null = null
|
63
|
+
|
64
|
+
export async function getHighlighter(options: ShikiOptions = {}): Promise<Highlighter> {
|
65
|
+
if (highlighterInstance) {
|
66
|
+
return highlighterInstance
|
67
|
+
}
|
68
|
+
|
69
|
+
if (!highlighterPromise) {
|
70
|
+
const themes = options.themes || DEFAULT_THEMES
|
71
|
+
const langs = options.langs || DEFAULT_LANGS
|
72
|
+
|
73
|
+
highlighterPromise = createHighlighter({
|
74
|
+
themes: [themes.light, themes.dark],
|
75
|
+
langs,
|
76
|
+
}).then(highlighter => {
|
77
|
+
highlighterInstance = highlighter
|
78
|
+
return highlighter
|
79
|
+
})
|
80
|
+
}
|
81
|
+
|
82
|
+
return highlighterPromise
|
83
|
+
}
|
84
|
+
|
85
|
+
export interface CodeHighlightOptions {
|
86
|
+
code: string
|
87
|
+
lang?: string
|
88
|
+
theme?: 'light' | 'dark'
|
89
|
+
showLineNumbers?: boolean
|
90
|
+
highlightLines?: number[]
|
91
|
+
diffLines?: { add: number[]; remove: number[] }
|
92
|
+
focusLines?: number[]
|
93
|
+
showInvisibles?: boolean
|
94
|
+
}
|
95
|
+
|
96
|
+
export async function highlightCode({
|
97
|
+
code,
|
98
|
+
lang = `text`,
|
99
|
+
theme = `light`,
|
100
|
+
showLineNumbers = false,
|
101
|
+
highlightLines = [],
|
102
|
+
diffLines,
|
103
|
+
focusLines = [],
|
104
|
+
showInvisibles = false,
|
105
|
+
}: CodeHighlightOptions): Promise<string> {
|
106
|
+
const highlighter = await getHighlighter()
|
107
|
+
|
108
|
+
const themes = {
|
109
|
+
light: DEFAULT_THEMES.light,
|
110
|
+
dark: DEFAULT_THEMES.dark,
|
111
|
+
}
|
112
|
+
|
113
|
+
const transformers = []
|
114
|
+
|
115
|
+
// Add line numbers transformer if needed
|
116
|
+
if (showLineNumbers) {
|
117
|
+
// Custom line numbers will be handled in CSS
|
118
|
+
transformers.push({
|
119
|
+
name: `line-numbers`,
|
120
|
+
pre(node: any) {
|
121
|
+
node.properties[`data-line-numbers`] = `true`
|
122
|
+
},
|
123
|
+
})
|
124
|
+
}
|
125
|
+
|
126
|
+
// Add highlight transformer
|
127
|
+
if (highlightLines.length > 0) {
|
128
|
+
transformers.push({
|
129
|
+
name: `highlight-lines`,
|
130
|
+
line(node: any, line: number) {
|
131
|
+
if (highlightLines.includes(line)) {
|
132
|
+
node.properties[`data-highlighted`] = `true`
|
133
|
+
}
|
134
|
+
},
|
135
|
+
})
|
136
|
+
}
|
137
|
+
|
138
|
+
// Add standard transformers
|
139
|
+
transformers.push(
|
140
|
+
transformerNotationHighlight(),
|
141
|
+
transformerNotationDiff(),
|
142
|
+
transformerNotationFocus(),
|
143
|
+
)
|
144
|
+
|
145
|
+
if (showInvisibles) {
|
146
|
+
transformers.push(transformerRenderWhitespace())
|
147
|
+
}
|
148
|
+
|
149
|
+
// Generate HTML with CSS variables for theme switching
|
150
|
+
const html = highlighter.codeToHtml(code, {
|
151
|
+
lang,
|
152
|
+
themes,
|
153
|
+
defaultColor: false,
|
154
|
+
transformers,
|
155
|
+
})
|
156
|
+
|
157
|
+
return html
|
158
|
+
}
|
159
|
+
|
160
|
+
// Re-export types
|
161
|
+
export type { BundledLanguage, BundledTheme, Highlighter } from 'shiki'
|
@@ -2,7 +2,7 @@ import { createId, markNoPlugins, normalizeId } from './id.ts'
|
|
2
2
|
|
3
3
|
export interface Options {
|
4
4
|
/**
|
5
|
-
* @
|
5
|
+
* @default '/'
|
6
6
|
*/
|
7
7
|
separator?: string
|
8
8
|
/**
|
@@ -10,7 +10,7 @@ export interface Options {
|
|
10
10
|
|
11
11
|
* @see https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention
|
12
12
|
|
13
|
-
* @
|
13
|
+
* @default false
|
14
14
|
*/
|
15
15
|
allowPluginProcessing?: boolean
|
16
16
|
}
|
package/src/project-data.ts
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
import { highlightCode } from '#lib/shiki/index'
|
2
|
+
import React, { useEffect, useState } from 'react'
|
3
|
+
|
4
|
+
interface CodeBlockProps {
|
5
|
+
children: string
|
6
|
+
language?: string
|
7
|
+
className?: string
|
8
|
+
showLineNumbers?: boolean
|
9
|
+
highlightLines?: number[]
|
10
|
+
diffLines?: { add: number[]; remove: number[] }
|
11
|
+
focusLines?: number[]
|
12
|
+
showInvisibles?: boolean
|
13
|
+
}
|
14
|
+
|
15
|
+
export const CodeBlock: React.FC<CodeBlockProps> = ({
|
16
|
+
children,
|
17
|
+
language = `text`,
|
18
|
+
className = ``,
|
19
|
+
showLineNumbers = false,
|
20
|
+
highlightLines = [],
|
21
|
+
diffLines,
|
22
|
+
focusLines = [],
|
23
|
+
showInvisibles = false,
|
24
|
+
}) => {
|
25
|
+
const [html, setHtml] = useState<string>(``)
|
26
|
+
const [isLoading, setIsLoading] = useState(true)
|
27
|
+
|
28
|
+
// TODO: Implement proper theme detection
|
29
|
+
// For now, we'll rely on CSS to handle theme switching
|
30
|
+
const theme = `light` // Default to light theme
|
31
|
+
|
32
|
+
useEffect(() => {
|
33
|
+
const renderCode = async () => {
|
34
|
+
try {
|
35
|
+
const output = await highlightCode({
|
36
|
+
code: children,
|
37
|
+
lang: language,
|
38
|
+
theme,
|
39
|
+
showLineNumbers,
|
40
|
+
highlightLines,
|
41
|
+
diffLines,
|
42
|
+
focusLines,
|
43
|
+
showInvisibles,
|
44
|
+
})
|
45
|
+
|
46
|
+
setHtml(output)
|
47
|
+
} catch (error) {
|
48
|
+
console.error(`Failed to highlight code:`, error)
|
49
|
+
// Fallback to plain text
|
50
|
+
setHtml(`<pre><code>${children}</code></pre>`)
|
51
|
+
} finally {
|
52
|
+
setIsLoading(false)
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
renderCode()
|
57
|
+
}, [children, language, showLineNumbers, highlightLines, diffLines, focusLines, showInvisibles])
|
58
|
+
|
59
|
+
if (isLoading) {
|
60
|
+
return (
|
61
|
+
<pre className={className}>
|
62
|
+
<code>{children}</code>
|
63
|
+
</pre>
|
64
|
+
)
|
65
|
+
}
|
66
|
+
|
67
|
+
return (
|
68
|
+
<div
|
69
|
+
className={`code-block ${className}`}
|
70
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
71
|
+
/>
|
72
|
+
)
|
73
|
+
}
|
@@ -52,11 +52,12 @@ export const getPathActiveReport = (
|
|
52
52
|
// Normalize both paths for comparison
|
53
53
|
const normalizedPath = pathExp.startsWith('/') ? pathExp.slice(1) : pathExp
|
54
54
|
const normalizedCurrentPath = currentPathExp.startsWith('/') ? currentPathExp.slice(1) : currentPathExp
|
55
|
-
|
55
|
+
|
56
56
|
const isDirect = normalizedCurrentPath === normalizedPath
|
57
|
-
const isdescendant = normalizedCurrentPath.startsWith(normalizedPath + '/')
|
57
|
+
const isdescendant = normalizedCurrentPath.startsWith(normalizedPath + '/')
|
58
|
+
&& normalizedCurrentPath !== normalizedPath
|
58
59
|
const is = isDirect || isdescendant
|
59
|
-
|
60
|
+
|
60
61
|
return {
|
61
62
|
is,
|
62
63
|
isDirect,
|
@@ -2,9 +2,11 @@
|
|
2
2
|
// But then, we won't get it from the client manifest. But we could get it from the server manifest. Should we do that?
|
3
3
|
// But then, that wouldn't work for SPA. Does that matter? Just put a conditional here e.g. if (import.meta.env.PROD) ...?
|
4
4
|
import '@radix-ui/themes/styles.css'
|
5
|
+
// import './styles/code-block.css' // TODO: Handle CSS in build process
|
5
6
|
import { ReactDomClient } from '#dep/react-dom-client/index'
|
6
7
|
import { StrictMode } from 'react'
|
7
8
|
import { createBrowserRouter, RouterProvider } from 'react-router'
|
9
|
+
import PROJECT_DATA from 'virtual:polen/project/data.jsonsuper'
|
8
10
|
import { routes } from './routes.jsx'
|
9
11
|
|
10
12
|
// SPA
|
@@ -19,6 +21,7 @@ import { routes } from './routes.jsx'
|
|
19
21
|
|
20
22
|
const router = createBrowserRouter(routes, {
|
21
23
|
hydrationData: window.__staticRouterHydrationData,
|
24
|
+
basename: PROJECT_DATA.basePath === `/` ? undefined : PROJECT_DATA.basePath.slice(0, -1), // Remove trailing slash for React Router
|
22
25
|
})
|
23
26
|
|
24
27
|
ReactDomClient.hydrateRoot(
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { assetUrl, faviconUrl } from '#api/utils/asset-url/index'
|
1
2
|
import type { ReactRouter } from '#dep/react-router/index'
|
2
3
|
import { createRoute } from '#lib/react-router-aid/react-router-aid'
|
3
4
|
import { GitHubLogoIcon } from '@radix-ui/react-icons'
|
@@ -40,12 +41,12 @@ export const Component = () => {
|
|
40
41
|
{import.meta.env.DEV && <link rel='stylesheet' href={radixStylesUrl} />}
|
41
42
|
<link
|
42
43
|
rel='icon'
|
43
|
-
href={PROJECT_DATA.faviconPath.replace(`.svg`, `.ico`) + `?v=1
|
44
|
+
href={faviconUrl(PROJECT_DATA.faviconPath.replace(`.svg`, `.ico`) + `?v=1`, PROJECT_DATA.basePath)}
|
44
45
|
sizes='256 x 256'
|
45
46
|
/>
|
46
47
|
<link
|
47
48
|
rel='icon'
|
48
|
-
href={PROJECT_DATA.faviconPath + `?v=1
|
49
|
+
href={faviconUrl(PROJECT_DATA.faviconPath + `?v=1`, PROJECT_DATA.basePath)}
|
49
50
|
sizes='any'
|
50
51
|
type='image/svg+xml'
|
51
52
|
/>
|
@@ -120,6 +121,40 @@ const Layout = () => {
|
|
120
121
|
my='8'
|
121
122
|
mx='auto'
|
122
123
|
>
|
124
|
+
<style>
|
125
|
+
{`
|
126
|
+
/* Shiki code blocks */
|
127
|
+
pre.shiki {
|
128
|
+
margin: 1rem 0;
|
129
|
+
padding: 1rem;
|
130
|
+
border-radius: 8px;
|
131
|
+
overflow-x: auto;
|
132
|
+
font-size: 14px;
|
133
|
+
line-height: 1.6;
|
134
|
+
background-color: #f6f8fa;
|
135
|
+
}
|
136
|
+
|
137
|
+
/* Light mode: use --shiki-light CSS variables from inline styles */
|
138
|
+
pre.shiki span {
|
139
|
+
color: var(--shiki-light);
|
140
|
+
}
|
141
|
+
|
142
|
+
/* Dark mode - Radix Themes uses [data-is-root-theme="dark"] */
|
143
|
+
[data-is-root-theme="dark"] pre.shiki {
|
144
|
+
background-color: #1a1b26;
|
145
|
+
}
|
146
|
+
|
147
|
+
[data-is-root-theme="dark"] pre.shiki span {
|
148
|
+
color: var(--shiki-dark);
|
149
|
+
}
|
150
|
+
|
151
|
+
pre.shiki code {
|
152
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
153
|
+
background: transparent;
|
154
|
+
display: block;
|
155
|
+
}
|
156
|
+
`}
|
157
|
+
</style>
|
123
158
|
{header}
|
124
159
|
{isShowSidebar && (
|
125
160
|
<Sidebar
|
@@ -1,25 +1,28 @@
|
|
1
|
+
import { assetUrl } from '#api/utils/asset-url/index'
|
1
2
|
import type { Vite } from '#dep/vite/index'
|
2
3
|
import { Group, Str } from '@wollybeard/kit'
|
3
4
|
|
4
5
|
export const injectManifestIntoHtml = (
|
5
6
|
html: string,
|
6
7
|
manifest: Vite.Manifest,
|
8
|
+
basePath: string,
|
7
9
|
): string => {
|
8
|
-
const assets = getRelevantAsssetsFromManifest(manifest)
|
10
|
+
const assets = getRelevantAsssetsFromManifest(manifest, basePath)
|
9
11
|
return injectAssetsIntoHtml(html, assets)
|
10
12
|
}
|
11
13
|
|
12
14
|
const getRelevantAsssetsFromManifest = (
|
13
15
|
manifest: Vite.Manifest,
|
16
|
+
basePath: string,
|
14
17
|
): HttpAssetGroupSet => {
|
15
18
|
const htmlAssets: HttpAsset[] = []
|
16
19
|
|
17
20
|
for (const manifestChunk of Object.values(manifest)) {
|
18
21
|
if (manifestChunk.isEntry) {
|
19
|
-
htmlAssets.push({ type: `js`, path:
|
22
|
+
htmlAssets.push({ type: `js`, path: assetUrl(manifestChunk.file, basePath) })
|
20
23
|
}
|
21
24
|
for (const cssItem of manifestChunk.css ?? []) {
|
22
|
-
htmlAssets.push({ type: `css`, path:
|
25
|
+
htmlAssets.push({ type: `css`, path: assetUrl(cssItem, basePath) })
|
23
26
|
}
|
24
27
|
}
|
25
28
|
|
@@ -6,6 +6,7 @@ import { Arr } from '@wollybeard/kit'
|
|
6
6
|
import { StrictMode } from 'react'
|
7
7
|
import * as ReactDomServer from 'react-dom/server'
|
8
8
|
import { createStaticRouter, StaticRouterProvider } from 'react-router'
|
9
|
+
import PROJECT_DATA from 'virtual:polen/project/data.jsonsuper'
|
9
10
|
import viteClientAssetManifest from 'virtual:polen/vite/client/manifest'
|
10
11
|
import { injectManifestIntoHtml } from './manifest.ts'
|
11
12
|
import { view } from './view.ts'
|
@@ -29,7 +30,7 @@ export const renderPage = (
|
|
29
30
|
}
|
30
31
|
|
31
32
|
if (__BUILDING__) {
|
32
|
-
html = injectManifestIntoHtml(html, viteClientAssetManifest)
|
33
|
+
html = injectManifestIntoHtml(html, viteClientAssetManifest, PROJECT_DATA.basePath)
|
33
34
|
}
|
34
35
|
|
35
36
|
// todo: what is this?
|
@@ -8,7 +8,22 @@ import { getRoutesPaths } from './get-route-paths.ts'
|
|
8
8
|
|
9
9
|
export const generate = async (view: ReactRouter.StaticHandler) => {
|
10
10
|
const handler: Hono.Handler = async (ctx) => {
|
11
|
-
|
11
|
+
// For SSG, we need to create a request with the base path prepended
|
12
|
+
// so React Router can match it correctly
|
13
|
+
const url = new URL(ctx.req.raw.url)
|
14
|
+
const basePath = PROJECT_DATA.basePath === '/' ? '' : PROJECT_DATA.basePath.slice(0, -1)
|
15
|
+
|
16
|
+
// Create a new request with the base path prepended to the pathname
|
17
|
+
const modifiedRequest = new Request(
|
18
|
+
`${url.protocol}//${url.host}${basePath}${url.pathname}${url.search}`,
|
19
|
+
{
|
20
|
+
method: ctx.req.raw.method,
|
21
|
+
headers: ctx.req.raw.headers,
|
22
|
+
body: ctx.req.raw.body,
|
23
|
+
},
|
24
|
+
)
|
25
|
+
|
26
|
+
const staticHandlerContext = await view.query(modifiedRequest)
|
12
27
|
if (staticHandlerContext instanceof Response) {
|
13
28
|
return staticHandlerContext
|
14
29
|
}
|
@@ -21,12 +36,60 @@ export const generate = async (view: ReactRouter.StaticHandler) => {
|
|
21
36
|
app.get(routePath, handler)
|
22
37
|
}
|
23
38
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
})
|
39
|
+
// For large schemas, we need to process in smaller batches to avoid memory issues
|
40
|
+
const BATCH_SIZE = 50
|
41
|
+
const totalPaths = routePaths.length
|
42
|
+
console.log(`[info] Generating ${totalPaths} static pages...`)
|
43
|
+
|
44
|
+
for (let i = 0; i < totalPaths; i += BATCH_SIZE) {
|
45
|
+
const batchPaths = routePaths.slice(i, i + BATCH_SIZE)
|
46
|
+
const batchApp = new Hono.Hono()
|
47
|
+
|
48
|
+
// Create a custom handler for batch processing that includes base path handling
|
49
|
+
const batchHandler: Hono.Handler = async (ctx) => {
|
50
|
+
// For SSG, we need to create a request with the base path prepended
|
51
|
+
const url = new URL(ctx.req.raw.url)
|
52
|
+
const basePath = PROJECT_DATA.basePath === '/' ? '' : PROJECT_DATA.basePath.slice(0, -1)
|
53
|
+
|
54
|
+
// Create a new request with the base path prepended to the pathname
|
55
|
+
const modifiedRequest = new Request(
|
56
|
+
`${url.protocol}//${url.host}${basePath}${url.pathname}${url.search}`,
|
57
|
+
{
|
58
|
+
method: ctx.req.raw.method,
|
59
|
+
headers: ctx.req.raw.headers,
|
60
|
+
body: ctx.req.raw.body,
|
61
|
+
},
|
62
|
+
)
|
63
|
+
|
64
|
+
const staticHandlerContext = await view.query(modifiedRequest)
|
65
|
+
if (staticHandlerContext instanceof Response) {
|
66
|
+
return staticHandlerContext
|
67
|
+
}
|
68
|
+
return renderPage(staticHandlerContext)
|
69
|
+
}
|
28
70
|
|
29
|
-
|
30
|
-
|
71
|
+
// Register only the routes for this batch
|
72
|
+
for (const routePath of batchPaths) {
|
73
|
+
batchApp.get(routePath, batchHandler)
|
74
|
+
}
|
75
|
+
|
76
|
+
console.log(
|
77
|
+
`[info] Processing batch ${Math.floor(i / BATCH_SIZE) + 1}/${
|
78
|
+
Math.ceil(totalPaths / BATCH_SIZE)
|
79
|
+
} (${batchPaths.length} pages)...`,
|
80
|
+
)
|
81
|
+
|
82
|
+
const result = await Hono.SSG.toSSG(batchApp, NodeFs, {
|
83
|
+
concurrency: 5, // Reduced concurrency for memory efficiency
|
84
|
+
dir: PROJECT_DATA.paths.relative.build.root,
|
85
|
+
})
|
86
|
+
|
87
|
+
if (!result.success) {
|
88
|
+
throw new Error(`Failed to generate static site at batch ${Math.floor(i / BATCH_SIZE) + 1}`, {
|
89
|
+
cause: result.error,
|
90
|
+
})
|
91
|
+
}
|
31
92
|
}
|
93
|
+
|
94
|
+
console.log(`[info] Successfully generated ${totalPaths} static pages.`)
|
32
95
|
}
|
@@ -1,4 +1,7 @@
|
|
1
1
|
import { createStaticHandler } from 'react-router'
|
2
|
+
import PROJECT_DATA from 'virtual:polen/project/data.jsonsuper'
|
2
3
|
import { routes } from '../routes.jsx'
|
3
4
|
|
4
|
-
export const view = createStaticHandler(routes
|
5
|
+
export const view = createStaticHandler(routes, {
|
6
|
+
basename: PROJECT_DATA.basePath === `/` ? undefined : PROJECT_DATA.basePath.slice(0, -1), // Remove trailing slash for React Router
|
7
|
+
})
|