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.
Files changed (101) hide show
  1. package/README.md +42 -0
  2. package/build/api/builder/builder.d.ts +1 -0
  3. package/build/api/builder/builder.d.ts.map +1 -1
  4. package/build/api/builder/builder.js +1 -0
  5. package/build/api/builder/builder.js.map +1 -1
  6. package/build/api/config/configurator.d.ts +21 -5
  7. package/build/api/config/configurator.d.ts.map +1 -1
  8. package/build/api/config/configurator.js +12 -0
  9. package/build/api/config/configurator.js.map +1 -1
  10. package/build/api/config-resolver/resolve.d.ts +1 -1
  11. package/build/api/config-resolver/resolve.js +1 -1
  12. package/build/api/config-resolver/vite.d.ts.map +1 -1
  13. package/build/api/config-resolver/vite.js +1 -0
  14. package/build/api/config-resolver/vite.js.map +1 -1
  15. package/build/api/schema/read.d.ts +1 -1
  16. package/build/api/singletons/markdown/markdown.d.ts.map +1 -1
  17. package/build/api/singletons/markdown/markdown.js +33 -11
  18. package/build/api/singletons/markdown/markdown.js.map +1 -1
  19. package/build/api/utils/asset-url/asset-url.d.ts +20 -0
  20. package/build/api/utils/asset-url/asset-url.d.ts.map +1 -0
  21. package/build/api/utils/asset-url/asset-url.js +35 -0
  22. package/build/api/utils/asset-url/asset-url.js.map +1 -0
  23. package/build/api/utils/asset-url/index.d.ts +2 -0
  24. package/build/api/utils/asset-url/index.d.ts.map +1 -0
  25. package/build/api/utils/asset-url/index.js +2 -0
  26. package/build/api/utils/asset-url/index.js.map +1 -0
  27. package/build/api/vite/plugins/core.d.ts.map +1 -1
  28. package/build/api/vite/plugins/core.js +10 -4
  29. package/build/api/vite/plugins/core.js.map +1 -1
  30. package/build/api/vite/plugins/pages.d.ts.map +1 -1
  31. package/build/api/vite/plugins/pages.js +27 -2
  32. package/build/api/vite/plugins/pages.js.map +1 -1
  33. package/build/cli/commands/build.js +2 -0
  34. package/build/cli/commands/build.js.map +1 -1
  35. package/build/cli/commands/dev.js +2 -0
  36. package/build/cli/commands/dev.js.map +1 -1
  37. package/build/lib/shiki/index.d.ts +2 -0
  38. package/build/lib/shiki/index.d.ts.map +1 -0
  39. package/build/lib/shiki/index.js +2 -0
  40. package/build/lib/shiki/index.js.map +1 -0
  41. package/build/lib/shiki/shiki.d.ts +26 -0
  42. package/build/lib/shiki/shiki.d.ts.map +1 -0
  43. package/build/lib/shiki/shiki.js +105 -0
  44. package/build/lib/shiki/shiki.js.map +1 -0
  45. package/build/lib/vite-virtual/identifier.d.ts +2 -2
  46. package/build/project-data.d.ts +1 -0
  47. package/build/project-data.d.ts.map +1 -1
  48. package/build/template/components/CodeBlock.d.ts +17 -0
  49. package/build/template/components/CodeBlock.d.ts.map +1 -0
  50. package/build/template/components/CodeBlock.jsx +42 -0
  51. package/build/template/components/CodeBlock.jsx.map +1 -0
  52. package/build/template/components/Link.d.ts.map +1 -1
  53. package/build/template/components/Link.jsx +2 -1
  54. package/build/template/components/Link.jsx.map +1 -1
  55. package/build/template/entry.client.jsx +3 -0
  56. package/build/template/entry.client.jsx.map +1 -1
  57. package/build/template/routes/root.d.ts.map +1 -1
  58. package/build/template/routes/root.jsx +37 -2
  59. package/build/template/routes/root.jsx.map +1 -1
  60. package/build/template/server/manifest.d.ts +1 -1
  61. package/build/template/server/manifest.d.ts.map +1 -1
  62. package/build/template/server/manifest.js +6 -5
  63. package/build/template/server/manifest.js.map +1 -1
  64. package/build/template/server/render-page.d.ts.map +1 -1
  65. package/build/template/server/render-page.jsx +2 -1
  66. package/build/template/server/render-page.jsx.map +1 -1
  67. package/build/template/server/ssg/generate.d.ts.map +1 -1
  68. package/build/template/server/ssg/generate.js +50 -7
  69. package/build/template/server/ssg/generate.js.map +1 -1
  70. package/build/template/server/view.d.ts.map +1 -1
  71. package/build/template/server/view.js +4 -1
  72. package/build/template/server/view.js.map +1 -1
  73. package/package.json +10 -1
  74. package/src/api/builder/builder.ts +2 -0
  75. package/src/api/config/configurator.ts +34 -5
  76. package/src/api/config-resolver/resolve.ts +1 -1
  77. package/src/api/config-resolver/vite.ts +1 -0
  78. package/src/api/schema/read.ts +1 -1
  79. package/src/api/singletons/markdown/markdown.test.ts +89 -0
  80. package/src/api/singletons/markdown/markdown.ts +35 -13
  81. package/src/api/utils/asset-url/asset-url.test.ts +47 -0
  82. package/src/api/utils/asset-url/asset-url.ts +38 -0
  83. package/src/api/utils/asset-url/index.ts +1 -0
  84. package/src/api/vite/plugins/core.ts +10 -4
  85. package/src/api/vite/plugins/pages.ts +27 -2
  86. package/src/cli/commands/build.ts +5 -0
  87. package/src/cli/commands/dev.ts +5 -0
  88. package/src/lib/shiki/index.ts +1 -0
  89. package/src/lib/shiki/shiki.test.ts +107 -0
  90. package/src/lib/shiki/shiki.ts +161 -0
  91. package/src/lib/vite-virtual/identifier.ts +2 -2
  92. package/src/project-data.ts +1 -0
  93. package/src/template/components/CodeBlock.tsx +73 -0
  94. package/src/template/components/Link.tsx +4 -3
  95. package/src/template/entry.client.tsx +3 -0
  96. package/src/template/routes/root.tsx +37 -2
  97. package/src/template/server/manifest.ts +6 -3
  98. package/src/template/server/render-page.tsx +2 -1
  99. package/src/template/server/ssg/generate.ts +70 -7
  100. package/src/template/server/view.ts +4 -1
  101. 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
- * @defaultValue '/'
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
- * @defaultValue false
13
+ * @default false
14
14
  */
15
15
  allowPluginProcessing?: boolean
16
16
  }
@@ -4,6 +4,7 @@ import type { Schema } from './api/schema/index.ts'
4
4
  export interface ProjectData {
5
5
  schema: null | Schema.Schema
6
6
  faviconPath: string
7
+ basePath: string
7
8
  paths: Config.Config[`paths`][`project`]
8
9
  server: {
9
10
  static: {
@@ -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 + '/') && normalizedCurrentPath !== 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: `/${manifestChunk.file}` })
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: `/${cssItem}` })
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
- const staticHandlerContext = await view.query(ctx.req.raw)
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
- const result = await Hono.SSG.toSSG(app, NodeFs, {
25
- concurrency: 10,
26
- dir: PROJECT_DATA.paths.relative.build.root,
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
- if (!result.success) {
30
- throw new Error(`Failed to generate static site`, { cause: result.error })
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
+ })