@ossy/app 1.13.2 → 1.13.4
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/cli/build.js +224 -312
- package/cli/prerender-react.task.js +4 -140
- package/cli/render-page.task.js +4 -25
- package/cli/server.js +1 -1
- package/package.json +10 -10
package/cli/build.js
CHANGED
|
@@ -12,8 +12,6 @@ import nodeExternals from 'rollup-plugin-node-externals'
|
|
|
12
12
|
import copy from 'rollup-plugin-copy';
|
|
13
13
|
import replace from '@rollup/plugin-replace';
|
|
14
14
|
import arg from 'arg'
|
|
15
|
-
import prerenderReactTask from './prerender-react.task.js'
|
|
16
|
-
import { createBuildDashboard } from './build-terminal.js'
|
|
17
15
|
|
|
18
16
|
export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
|
|
19
17
|
export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
|
|
@@ -23,15 +21,12 @@ const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.js$/
|
|
|
23
21
|
/** Written next to `*.resource.js` under `src/resource-templates/` when that dir exists. */
|
|
24
22
|
export const OSSY_RESOURCE_TEMPLATES_OUT = '.ossy-system-templates.generated.js'
|
|
25
23
|
|
|
26
|
-
/** Rollup output paths when `output.dir` is `<build>/public` (see `entryFileNames` / `chunkFileNames`). */
|
|
27
|
-
const BROWSER_STATIC_PREFIX = 'static/'
|
|
28
|
-
|
|
29
24
|
export function minifyBrowserStaticChunks () {
|
|
30
25
|
return {
|
|
31
26
|
name: 'minify-browser-static-chunks',
|
|
32
27
|
async renderChunk (code, chunk, outputOptions) {
|
|
33
28
|
const fileName = chunk.fileName
|
|
34
|
-
if (!fileName || !fileName.startsWith(
|
|
29
|
+
if (!fileName || !fileName.startsWith('public/static/')) {
|
|
35
30
|
return null
|
|
36
31
|
}
|
|
37
32
|
const useSourceMap =
|
|
@@ -87,19 +82,13 @@ export const OSSY_PAGE_SERVER_EXTERNAL = [
|
|
|
87
82
|
'react/jsx-runtime',
|
|
88
83
|
]
|
|
89
84
|
|
|
90
|
-
/** Output directory (relative to buildPath) for
|
|
85
|
+
/** Output directory (relative to buildPath) for SSR bundle. */
|
|
91
86
|
export const OSSY_SSR_DIRNAME = 'ssr'
|
|
92
|
-
/** Temp stub entries for SSR bundles (inside .ossy/). */
|
|
93
|
-
const OSSY_SSR_ENTRIES_DIRNAME = 'ssr-entries'
|
|
94
|
-
|
|
95
|
-
/** Per-page client entries: `hydrate-<pageId>.jsx` under `.ossy/` */
|
|
96
|
-
const HYDRATE_STUB_PREFIX = 'hydrate-'
|
|
97
|
-
const HYDRATE_STUB_SUFFIX = '.jsx'
|
|
98
87
|
|
|
99
|
-
/**
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
88
|
+
/** Shared client hydrate entry filename under `.ossy/` */
|
|
89
|
+
const HYDRATE_ENTRY_FILENAME = 'hydrate-entry.jsx'
|
|
90
|
+
/** Shared SSR entry filename under `.ossy/` */
|
|
91
|
+
const SSR_ENTRY_FILENAME = 'ssr-entry.mjs'
|
|
103
92
|
|
|
104
93
|
export function ossyGeneratedDir (buildPath) {
|
|
105
94
|
return path.join(buildPath, OSSY_GEN_DIRNAME)
|
|
@@ -205,45 +194,12 @@ export function createOssyAppBundlePlugins ({ nodeEnv }) {
|
|
|
205
194
|
}
|
|
206
195
|
|
|
207
196
|
/**
|
|
208
|
-
* Rollup plugins for
|
|
209
|
-
* is
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const plugins = [
|
|
213
|
-
replace({
|
|
214
|
-
preventAssignment: true,
|
|
215
|
-
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
216
|
-
}),
|
|
217
|
-
json(),
|
|
218
|
-
nodeExternals({
|
|
219
|
-
deps: false,
|
|
220
|
-
devDeps: true,
|
|
221
|
-
peerDeps: false,
|
|
222
|
-
packagePath: path.join(process.cwd(), 'package.json'),
|
|
223
|
-
}),
|
|
224
|
-
resolveCommonJsDependencies(),
|
|
225
|
-
resolveDependencies({ preferBuiltins: false }),
|
|
226
|
-
babel({
|
|
227
|
-
babelHelpers: 'bundled',
|
|
228
|
-
extensions: ['.jsx', '.tsx'],
|
|
229
|
-
presets: [['@babel/preset-react', { runtime: 'automatic' }]],
|
|
230
|
-
}),
|
|
231
|
-
]
|
|
232
|
-
if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
|
|
233
|
-
plugins.push(
|
|
234
|
-
copy({
|
|
235
|
-
targets: [{ src: `${copyPublicFrom}/**/*`, dest: path.join(buildPath, 'public') }],
|
|
236
|
-
})
|
|
237
|
-
)
|
|
238
|
-
}
|
|
239
|
-
return plugins
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Rollup plugins for per-page SSR bundles: server-side resolution, React and all deps bundled in
|
|
244
|
-
* so the output is self-contained (no node_modules needed at runtime).
|
|
197
|
+
* Rollup plugins for the combined SSR + client bundle.
|
|
198
|
+
* `preferBuiltins: true` is correct for the SSR entry (uses `node:stream`); page component code
|
|
199
|
+
* does not import Node built-ins so this is safe for the browser entry as well.
|
|
200
|
+
* `copyPublicFrom` is handled separately before the Rollup call.
|
|
245
201
|
*/
|
|
246
|
-
export function
|
|
202
|
+
export function createCombinedBundlePlugins ({ nodeEnv }) {
|
|
247
203
|
return [
|
|
248
204
|
replace({
|
|
249
205
|
preventAssignment: true,
|
|
@@ -266,93 +222,6 @@ export function createOssySsrBundlePlugins ({ nodeEnv }) {
|
|
|
266
222
|
]
|
|
267
223
|
}
|
|
268
224
|
|
|
269
|
-
/** Generates the SSR entry stub for a page: exports renderPage(props, options) and metadata. */
|
|
270
|
-
export function generatePageSsrModule ({ pageAbsPath, stubAbsPath }) {
|
|
271
|
-
const rel = relToGeneratedImport(stubAbsPath, pageAbsPath)
|
|
272
|
-
return [
|
|
273
|
-
'// Generated by @ossy/app — do not edit',
|
|
274
|
-
'',
|
|
275
|
-
"import { createElement } from 'react'",
|
|
276
|
-
"import { renderToPipeableStream } from 'react-dom/server'",
|
|
277
|
-
"import { Writable } from 'node:stream'",
|
|
278
|
-
"import { App } from '@ossy/connected-components'",
|
|
279
|
-
`import * as _page from './${rel}'`,
|
|
280
|
-
'',
|
|
281
|
-
'export const metadata = _page.metadata',
|
|
282
|
-
'',
|
|
283
|
-
'function PageShell (props) {',
|
|
284
|
-
" return createElement('html', { lang: props.defaultLanguage || 'en' },",
|
|
285
|
-
" createElement('head', null,",
|
|
286
|
-
" createElement('meta', { charSet: 'utf-8' }),",
|
|
287
|
-
" createElement('title', null, (_page.metadata && _page.metadata.title) || ''),",
|
|
288
|
-
' ),',
|
|
289
|
-
' createElement(App, props,',
|
|
290
|
-
' createElement(_page.default, props)',
|
|
291
|
-
' )',
|
|
292
|
-
' )',
|
|
293
|
-
'}',
|
|
294
|
-
'',
|
|
295
|
-
'export function renderPage (props, options = {}) {',
|
|
296
|
-
' return new Promise((resolve, reject) => {',
|
|
297
|
-
" let html = ''",
|
|
298
|
-
' const writable = new Writable({',
|
|
299
|
-
' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
|
|
300
|
-
' })',
|
|
301
|
-
' const { pipe } = renderToPipeableStream(createElement(PageShell, props), {',
|
|
302
|
-
' ...options,',
|
|
303
|
-
' onAllReady () { pipe(writable) },',
|
|
304
|
-
' onError (err) { reject(err) },',
|
|
305
|
-
' })',
|
|
306
|
-
" writable.on('finish', () => resolve(html))",
|
|
307
|
-
' })',
|
|
308
|
-
'}',
|
|
309
|
-
'',
|
|
310
|
-
].join('\n')
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/** Writes `ssr-entries/<id>.mjs` stubs for each page into ossyDir. */
|
|
314
|
-
export function writePageSsrStubs (pageFiles, srcDir, ossyDir) {
|
|
315
|
-
const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
|
|
316
|
-
fs.rmSync(entriesDir, { recursive: true, force: true })
|
|
317
|
-
if (pageFiles.length === 0) return
|
|
318
|
-
fs.mkdirSync(entriesDir, { recursive: true })
|
|
319
|
-
for (const f of pageFiles) {
|
|
320
|
-
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
321
|
-
const stubPath = path.join(entriesDir, `${pageId}.mjs`)
|
|
322
|
-
fs.mkdirSync(path.dirname(stubPath), { recursive: true })
|
|
323
|
-
fs.writeFileSync(stubPath, generatePageSsrModule({ pageAbsPath: f, stubAbsPath: stubPath }))
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/** Compiles per-page SSR bundles to `build/ssr/<id>.mjs`. Each is self-contained with React bundled in. */
|
|
328
|
-
export async function compilePageSsrModules ({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }) {
|
|
329
|
-
const ssrDir = path.join(buildPath, OSSY_SSR_DIRNAME)
|
|
330
|
-
const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
|
|
331
|
-
fs.rmSync(ssrDir, { recursive: true, force: true })
|
|
332
|
-
if (pageFiles.length === 0) return []
|
|
333
|
-
fs.mkdirSync(ssrDir, { recursive: true })
|
|
334
|
-
const plugins = createOssySsrBundlePlugins({ nodeEnv })
|
|
335
|
-
const results = await Promise.all(
|
|
336
|
-
pageFiles.map(async (f) => {
|
|
337
|
-
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
338
|
-
const stubPath = path.join(entriesDir, `${pageId}.mjs`)
|
|
339
|
-
const outFile = path.join(ssrDir, `${pageId}.mjs`)
|
|
340
|
-
const bundle = await rollup({
|
|
341
|
-
input: stubPath,
|
|
342
|
-
plugins,
|
|
343
|
-
onwarn (warning, defaultHandler) {
|
|
344
|
-
if (onWarn) { onWarn(warning); return }
|
|
345
|
-
defaultHandler(warning)
|
|
346
|
-
},
|
|
347
|
-
})
|
|
348
|
-
await bundle.write({ file: outFile, format: 'esm', inlineDynamicImports: true })
|
|
349
|
-
await bundle.close()
|
|
350
|
-
return { id: pageId }
|
|
351
|
-
})
|
|
352
|
-
)
|
|
353
|
-
return results
|
|
354
|
-
}
|
|
355
|
-
|
|
356
225
|
/** Bundles a single Node ESM file (inline dynamic imports) for SSR / API / tasks. */
|
|
357
226
|
export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv, onWarn, external }) {
|
|
358
227
|
const bundle = await rollup({
|
|
@@ -573,73 +442,22 @@ export async function compileTaskServerModules ({ taskFiles, ossyDir, nodeEnv, o
|
|
|
573
442
|
}
|
|
574
443
|
|
|
575
444
|
/**
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*/
|
|
579
|
-
/**
|
|
580
|
-
* Enriches `pages.generated.json` with `module` paths pointing to SSR bundles and merges
|
|
581
|
-
* any `metadata` exported by each page. Runs after SSR bundles are compiled.
|
|
582
|
-
*/
|
|
583
|
-
export async function enrichPagesGeneratedManifest ({ ossyDir, pagesGeneratedPath }) {
|
|
584
|
-
if (!fs.existsSync(pagesGeneratedPath)) return
|
|
585
|
-
const basePages = JSON.parse(fs.readFileSync(pagesGeneratedPath, 'utf8'))
|
|
586
|
-
if (!Array.isArray(basePages) || basePages.length === 0) return
|
|
587
|
-
|
|
588
|
-
const pages = []
|
|
589
|
-
for (const basePage of basePages) {
|
|
590
|
-
const moduleRelPath = `../ssr/${basePage.id}.mjs`
|
|
591
|
-
const bundleAbs = path.join(ossyDir, moduleRelPath)
|
|
592
|
-
let meta = {}
|
|
593
|
-
if (fs.existsSync(bundleAbs)) {
|
|
594
|
-
try {
|
|
595
|
-
const mod = await import(pathToFileURL(bundleAbs).href)
|
|
596
|
-
meta = mod?.metadata && typeof mod.metadata === 'object' ? mod.metadata : {}
|
|
597
|
-
} catch {
|
|
598
|
-
// metadata unreadable — continue with defaults
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
const merged = {
|
|
602
|
-
id: basePage.id,
|
|
603
|
-
path: basePage.path,
|
|
604
|
-
...meta,
|
|
605
|
-
sourceFile: basePage.sourceFile,
|
|
606
|
-
module: moduleRelPath,
|
|
607
|
-
}
|
|
608
|
-
try {
|
|
609
|
-
JSON.stringify(merged)
|
|
610
|
-
} catch {
|
|
611
|
-
pages.push({ id: basePage.id, path: basePage.path, sourceFile: basePage.sourceFile, module: moduleRelPath })
|
|
612
|
-
continue
|
|
613
|
-
}
|
|
614
|
-
pages.push(merged)
|
|
615
|
-
}
|
|
616
|
-
writeOssyJson(pagesGeneratedPath, pages)
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* Compiles server-side artifacts: per-page SSR bundles, API modules, and task modules.
|
|
445
|
+
* Compiles server-side artifacts: API modules and task modules.
|
|
446
|
+
* SSR page bundles are now produced by the combined Rollup pass in compileCombinedBundle.
|
|
621
447
|
*/
|
|
622
448
|
export async function compileOssyNodeArtifacts ({
|
|
623
|
-
pageFiles,
|
|
624
|
-
srcDir,
|
|
625
|
-
ossyDir,
|
|
626
|
-
buildPath,
|
|
627
449
|
apiFiles,
|
|
628
450
|
taskFiles,
|
|
451
|
+
ossyDir,
|
|
629
452
|
nodeEnv,
|
|
630
453
|
onWarn,
|
|
631
454
|
}) {
|
|
632
|
-
const [
|
|
633
|
-
compilePageSsrModules({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }),
|
|
455
|
+
const [apiRouteList, taskList] = await Promise.all([
|
|
634
456
|
compileApiServerModules({ apiFiles, ossyDir, nodeEnv, onWarn }),
|
|
635
457
|
compileTaskServerModules({ taskFiles, ossyDir, nodeEnv, onWarn }),
|
|
636
458
|
])
|
|
637
459
|
writeOssyJson(path.join(ossyDir, OSSY_GEN_API_BASENAME), apiRouteList)
|
|
638
460
|
writeOssyJson(path.join(ossyDir, OSSY_GEN_TASKS_BASENAME), taskList)
|
|
639
|
-
await enrichPagesGeneratedManifest({
|
|
640
|
-
ossyDir,
|
|
641
|
-
pagesGeneratedPath: path.join(ossyDir, OSSY_GEN_PAGES_BASENAME),
|
|
642
|
-
})
|
|
643
461
|
}
|
|
644
462
|
|
|
645
463
|
export function filePathToRoute(filePath, srcDir) {
|
|
@@ -670,6 +488,25 @@ export function clientHydrateIdForPage (pageAbsPath, srcDir) {
|
|
|
670
488
|
return idMatch ? idMatch[1] : derived.id
|
|
671
489
|
}
|
|
672
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Like `clientHydrateIdForPage` but also extracts `path` from `metadata`.
|
|
493
|
+
* Used to embed the static page route map directly into the SSR entry.
|
|
494
|
+
*/
|
|
495
|
+
export function pageRouteFromSource (pageAbsPath, srcDir) {
|
|
496
|
+
const derived = filePathToRoute(pageAbsPath, srcDir)
|
|
497
|
+
try {
|
|
498
|
+
const src = fs.readFileSync(pageAbsPath, 'utf8')
|
|
499
|
+
const metaIdx = src.indexOf('export const metadata')
|
|
500
|
+
if (metaIdx === -1) return derived
|
|
501
|
+
const after = src.slice(metaIdx)
|
|
502
|
+
const id = after.match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1] ?? derived.id
|
|
503
|
+
const strPath = after.match(/\bpath\s*:\s*['"]([^'"]+)['"]/)?.[1]
|
|
504
|
+
return { id, path: strPath ?? derived.path }
|
|
505
|
+
} catch {
|
|
506
|
+
return derived
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
673
510
|
/** Posix path relative to `build/.ossy/` for the compiled **Node** page module (SSR). */
|
|
674
511
|
export function pageServerModuleRelPath (pageAbsPath, srcDir) {
|
|
675
512
|
const pageId = clientHydrateIdForPage(pageAbsPath, srcDir)
|
|
@@ -709,92 +546,213 @@ export function writePageServerRollupEntry ({ pageAbsPath, stubPath }) {
|
|
|
709
546
|
}
|
|
710
547
|
|
|
711
548
|
/**
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
549
|
+
* Generates a single shared hydrate entry that dynamically imports the active page at runtime.
|
|
550
|
+
* Rollup processes this once, emitting React and shared deps as reusable chunks instead of
|
|
551
|
+
* bundling them into every page separately.
|
|
715
552
|
*/
|
|
716
|
-
export function
|
|
717
|
-
const
|
|
553
|
+
export function generateHydrateEntry ({ pageFiles, srcDir, stubAbsPath }) {
|
|
554
|
+
const seenIds = new Set()
|
|
555
|
+
const pageLines = []
|
|
556
|
+
for (const f of pageFiles) {
|
|
557
|
+
const hydrateId = clientHydrateIdForPage(f, srcDir)
|
|
558
|
+
if (seenIds.has(hydrateId)) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`[@ossy/app] Duplicate client hydrate id "${hydrateId}" (${f}). Pages need unique ids.`
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
seenIds.add(hydrateId)
|
|
564
|
+
const rel = relToGeneratedImport(stubAbsPath, f)
|
|
565
|
+
pageLines.push(` '${hydrateId}': () => import('./${rel}'),`)
|
|
566
|
+
}
|
|
567
|
+
|
|
718
568
|
return [
|
|
719
569
|
'// Generated by @ossy/app — do not edit',
|
|
720
570
|
'',
|
|
721
571
|
"import { createElement } from 'react'",
|
|
722
572
|
"import { hydrateRoot } from 'react-dom/client'",
|
|
723
573
|
"import { App } from '@ossy/connected-components'",
|
|
724
|
-
`import * as _page from './${rel}'`,
|
|
725
574
|
'',
|
|
726
|
-
'
|
|
727
|
-
'
|
|
575
|
+
'const config = window.__INITIAL_APP_CONFIG__ || {}',
|
|
576
|
+
'',
|
|
577
|
+
'const pages = {',
|
|
578
|
+
...pageLines,
|
|
579
|
+
'}',
|
|
728
580
|
'',
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
'
|
|
732
|
-
|
|
733
|
-
"
|
|
734
|
-
|
|
735
|
-
"
|
|
736
|
-
'
|
|
737
|
-
|
|
738
|
-
|
|
581
|
+
'const load = pages[config.pageId]',
|
|
582
|
+
'if (load) {',
|
|
583
|
+
' load().then((mod) => {',
|
|
584
|
+
' const Page = mod.default',
|
|
585
|
+
" const metadata = mod.metadata || {}",
|
|
586
|
+
' function PageShell (props) {',
|
|
587
|
+
" return createElement('html', { lang: props.defaultLanguage || 'en' },",
|
|
588
|
+
" createElement('head', null,",
|
|
589
|
+
" createElement('meta', { charSet: 'utf-8' }),",
|
|
590
|
+
" createElement('title', null, metadata.title || ''),",
|
|
591
|
+
' ),',
|
|
592
|
+
' createElement(App, props,',
|
|
593
|
+
' createElement(Page, props)',
|
|
594
|
+
' )',
|
|
739
595
|
' )',
|
|
740
|
-
'
|
|
741
|
-
'
|
|
742
|
-
'
|
|
596
|
+
' }',
|
|
597
|
+
' hydrateRoot(document, createElement(PageShell, config))',
|
|
598
|
+
' })',
|
|
743
599
|
'}',
|
|
744
600
|
'',
|
|
745
601
|
].join('\n')
|
|
746
602
|
}
|
|
747
603
|
|
|
748
|
-
/** Writes `hydrate
|
|
749
|
-
export function
|
|
604
|
+
/** Writes a single `hydrate-entry.jsx`; removes any stale per-page `hydrate-*.jsx` stubs first. */
|
|
605
|
+
export function writeHydrateEntry (pageFiles, srcDir, ossyDir) {
|
|
750
606
|
if (!fs.existsSync(ossyDir)) fs.mkdirSync(ossyDir, { recursive: true })
|
|
751
607
|
for (const ent of fs.readdirSync(ossyDir, { withFileTypes: true })) {
|
|
752
|
-
|
|
753
|
-
if (ent.isDirectory() && ent.name.startsWith(HYDRATE_STUB_PREFIX)) {
|
|
754
|
-
fs.rmSync(full, { recursive: true, force: true })
|
|
755
|
-
} else if (
|
|
608
|
+
if (
|
|
756
609
|
ent.isFile() &&
|
|
757
|
-
ent.name.startsWith(
|
|
758
|
-
ent.name.endsWith(
|
|
610
|
+
ent.name.startsWith('hydrate-') &&
|
|
611
|
+
ent.name.endsWith('.jsx') &&
|
|
612
|
+
ent.name !== HYDRATE_ENTRY_FILENAME
|
|
759
613
|
) {
|
|
760
|
-
fs.rmSync(
|
|
614
|
+
fs.rmSync(path.join(ossyDir, ent.name), { force: true })
|
|
761
615
|
}
|
|
762
616
|
}
|
|
617
|
+
const stubPath = path.join(ossyDir, HYDRATE_ENTRY_FILENAME)
|
|
618
|
+
fs.writeFileSync(stubPath, generateHydrateEntry({ pageFiles, srcDir, stubAbsPath: stubPath }))
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Generates the single shared SSR entry that exports a static `pages` array and a
|
|
623
|
+
* `renderPage(pageId, props, options)` function. Each page is dynamically imported so
|
|
624
|
+
* Rollup can split out lazy chunks per page when code-splitting is enabled.
|
|
625
|
+
*/
|
|
626
|
+
export function generateSsrEntry ({ pageFiles, srcDir, stubAbsPath }) {
|
|
763
627
|
const seenIds = new Set()
|
|
628
|
+
const pagesLiteral = []
|
|
629
|
+
const pageModuleLines = []
|
|
630
|
+
|
|
764
631
|
for (const f of pageFiles) {
|
|
765
|
-
const
|
|
766
|
-
if (seenIds.has(
|
|
632
|
+
const { id, path: routePath } = pageRouteFromSource(f, srcDir)
|
|
633
|
+
if (seenIds.has(id)) {
|
|
767
634
|
throw new Error(
|
|
768
|
-
`[@ossy/app] Duplicate
|
|
635
|
+
`[@ossy/app] Duplicate page id "${id}" (${f}). Pages need unique ids.`
|
|
769
636
|
)
|
|
770
637
|
}
|
|
771
|
-
seenIds.add(
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
stubPath,
|
|
776
|
-
generatePageHydrateModule({ pageAbsPath: f, stubAbsPath: stubPath, srcDir })
|
|
777
|
-
)
|
|
638
|
+
seenIds.add(id)
|
|
639
|
+
const rel = relToGeneratedImport(stubAbsPath, f)
|
|
640
|
+
pagesLiteral.push(` { id: '${id}', path: '${routePath}' },`)
|
|
641
|
+
pageModuleLines.push(` '${id}': () => import('./${rel}'),`)
|
|
778
642
|
}
|
|
643
|
+
|
|
644
|
+
return [
|
|
645
|
+
'// Generated by @ossy/app — do not edit',
|
|
646
|
+
'',
|
|
647
|
+
"import { createElement } from 'react'",
|
|
648
|
+
"import { renderToPipeableStream } from 'react-dom/server'",
|
|
649
|
+
"import { Writable } from 'node:stream'",
|
|
650
|
+
"import { App } from '@ossy/connected-components'",
|
|
651
|
+
'',
|
|
652
|
+
'export const pages = [',
|
|
653
|
+
...pagesLiteral,
|
|
654
|
+
']',
|
|
655
|
+
'',
|
|
656
|
+
'const pageModules = {',
|
|
657
|
+
...pageModuleLines,
|
|
658
|
+
'}',
|
|
659
|
+
'',
|
|
660
|
+
'function PageShell (props) {',
|
|
661
|
+
" const meta = props._pageMeta || {}",
|
|
662
|
+
" return createElement('html', { lang: props.defaultLanguage || 'en' },",
|
|
663
|
+
" createElement('head', null,",
|
|
664
|
+
" createElement('meta', { charSet: 'utf-8' }),",
|
|
665
|
+
" createElement('title', null, meta.title || ''),",
|
|
666
|
+
' ),',
|
|
667
|
+
' createElement(App, props,',
|
|
668
|
+
' createElement(props._pageComponent, props)',
|
|
669
|
+
' )',
|
|
670
|
+
' )',
|
|
671
|
+
'}',
|
|
672
|
+
'',
|
|
673
|
+
'export async function renderPage (pageId, props, options = {}) {',
|
|
674
|
+
' const load = pageModules[pageId]',
|
|
675
|
+
" if (!load) throw new Error(`[@ossy/app] Unknown page id: ${pageId}`)",
|
|
676
|
+
' const mod = await load()',
|
|
677
|
+
' const merged = { ...props, _pageComponent: mod.default, _pageMeta: mod.metadata || {} }',
|
|
678
|
+
' return new Promise((resolve, reject) => {',
|
|
679
|
+
" let html = ''",
|
|
680
|
+
' const writable = new Writable({',
|
|
681
|
+
' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
|
|
682
|
+
' })',
|
|
683
|
+
' const { pipe } = renderToPipeableStream(createElement(PageShell, merged), {',
|
|
684
|
+
' ...options,',
|
|
685
|
+
' onAllReady () { pipe(writable) },',
|
|
686
|
+
' onError (err) { reject(err) },',
|
|
687
|
+
' })',
|
|
688
|
+
" writable.on('finish', () => resolve(html))",
|
|
689
|
+
' })',
|
|
690
|
+
'}',
|
|
691
|
+
'',
|
|
692
|
+
].join('\n')
|
|
779
693
|
}
|
|
780
694
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
695
|
+
/** Writes `ssr-entry.mjs` into ossyDir; removes any stale per-page SSR stubs first. */
|
|
696
|
+
export function writeSsrEntry (pageFiles, srcDir, ossyDir) {
|
|
697
|
+
if (!fs.existsSync(ossyDir)) fs.mkdirSync(ossyDir, { recursive: true })
|
|
698
|
+
const stubPath = path.join(ossyDir, SSR_ENTRY_FILENAME)
|
|
699
|
+
fs.writeFileSync(stubPath, generateSsrEntry({ pageFiles, srcDir, stubAbsPath: stubPath }))
|
|
700
|
+
return stubPath
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Runs a single Rollup pass with both the SSR entry and the client hydrate entry as inputs.
|
|
705
|
+
* Produces:
|
|
706
|
+
* build/ssr/app.mjs — Node SSR bundle
|
|
707
|
+
* build/public/static/app.js — browser hydrate bundle
|
|
708
|
+
* build/public/static/chunks/[name]-[hash].js — shared chunks
|
|
709
|
+
*
|
|
710
|
+
* The SSR bundle imports shared chunks via relative paths (`../public/static/chunks/…`);
|
|
711
|
+
* the browser loads the same physical files from `/static/chunks/`.
|
|
712
|
+
*/
|
|
713
|
+
export async function compileCombinedBundle ({ ssrEntryPath, clientEntryPath, buildPath, nodeEnv, copyPublicFrom, onWarn }) {
|
|
714
|
+
if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
|
|
715
|
+
const destPublic = path.join(buildPath, 'public')
|
|
716
|
+
fs.mkdirSync(destPublic, { recursive: true })
|
|
717
|
+
const copyDir = (src, dest) => {
|
|
718
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
719
|
+
for (const ent of fs.readdirSync(src, { withFileTypes: true })) {
|
|
720
|
+
const srcPath = path.join(src, ent.name)
|
|
721
|
+
const destPath = path.join(dest, ent.name)
|
|
722
|
+
if (ent.isDirectory()) copyDir(srcPath, destPath)
|
|
723
|
+
else fs.copyFileSync(srcPath, destPath)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
copyDir(copyPublicFrom, destPublic)
|
|
787
727
|
}
|
|
788
|
-
|
|
728
|
+
|
|
729
|
+
const bundle = await rollup({
|
|
730
|
+
input: { server: ssrEntryPath, app: clientEntryPath },
|
|
731
|
+
plugins: createCombinedBundlePlugins({ nodeEnv }),
|
|
732
|
+
onwarn (warning, defaultHandler) {
|
|
733
|
+
if (onWarn) { onWarn(warning); return }
|
|
734
|
+
defaultHandler(warning)
|
|
735
|
+
},
|
|
736
|
+
})
|
|
737
|
+
await bundle.write({
|
|
738
|
+
dir: buildPath,
|
|
739
|
+
format: 'esm',
|
|
740
|
+
entryFileNames: (chunk) =>
|
|
741
|
+
chunk.name === 'server'
|
|
742
|
+
? 'ssr/app.mjs'
|
|
743
|
+
: 'public/static/app.js',
|
|
744
|
+
chunkFileNames: 'public/static/chunks/[name]-[hash].js',
|
|
745
|
+
plugins: [minifyBrowserStaticChunks()],
|
|
746
|
+
})
|
|
747
|
+
await bundle.close()
|
|
789
748
|
}
|
|
790
749
|
|
|
791
750
|
/** JSON manifest: route ids, default paths, and page source paths (posix, relative to `cwd`). */
|
|
792
751
|
export function buildPagesGeneratedPayload (pageFiles, srcDir, cwd = process.cwd()) {
|
|
793
752
|
const pages = pageFiles.map((f) => {
|
|
794
|
-
const { path: routePath } =
|
|
795
|
-
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
753
|
+
const { id, path: routePath } = pageRouteFromSource(f, srcDir)
|
|
796
754
|
return {
|
|
797
|
-
id
|
|
755
|
+
id,
|
|
798
756
|
path: routePath,
|
|
799
757
|
sourceFile: path.relative(cwd, f).replace(/\\/g, '/'),
|
|
800
758
|
}
|
|
@@ -918,8 +876,9 @@ export const build = async (cliArgs) => {
|
|
|
918
876
|
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
919
877
|
const buildPath = path.resolve('build')
|
|
920
878
|
const srcDir = path.resolve('src')
|
|
921
|
-
const configPath = path.resolve(options['--config'] || 'src/config.js')
|
|
879
|
+
const configPath = path.resolve(options['--config'] || 'src/config.js')
|
|
922
880
|
const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
|
|
881
|
+
const publicDir = path.resolve('public')
|
|
923
882
|
|
|
924
883
|
resetOssyBuildDir(buildPath)
|
|
925
884
|
|
|
@@ -936,16 +895,14 @@ export const build = async (cliArgs) => {
|
|
|
936
895
|
srcDir,
|
|
937
896
|
pagesGeneratedPath,
|
|
938
897
|
})
|
|
939
|
-
writePageHydrateStubs(pageFiles, srcDir, ossyDir)
|
|
940
|
-
writePageSsrStubs(pageFiles, srcDir, ossyDir)
|
|
941
|
-
const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
|
|
942
898
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const
|
|
899
|
+
// Write generated entries (both SSR and client hydrate)
|
|
900
|
+
const ssrEntryPath = writeSsrEntry(pageFiles, srcDir, ossyDir)
|
|
901
|
+
writeHydrateEntry(pageFiles, srcDir, ossyDir)
|
|
902
|
+
const clientEntryPath = path.join(ossyDir, HYDRATE_ENTRY_FILENAME)
|
|
903
|
+
|
|
904
|
+
const { apiOverviewFiles } = resolveApiSource({ srcDir, buildPath })
|
|
905
|
+
let middlewareSourcePath = path.resolve('src/middleware.js')
|
|
949
906
|
|
|
950
907
|
if (!fs.existsSync(middlewareSourcePath)) {
|
|
951
908
|
middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
|
|
@@ -955,45 +912,13 @@ export const build = async (cliArgs) => {
|
|
|
955
912
|
? configPath
|
|
956
913
|
: path.resolve(scriptDir, 'default-config.js')
|
|
957
914
|
|
|
958
|
-
|
|
959
|
-
|
|
915
|
+
console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
|
|
916
|
+
printBuildOverview({
|
|
960
917
|
pagesSourcePath: pagesGeneratedPath,
|
|
961
918
|
apiOverviewFiles,
|
|
962
919
|
configPath,
|
|
963
920
|
pageFiles,
|
|
964
921
|
})
|
|
965
|
-
const idToPath = Object.fromEntries(
|
|
966
|
-
pageFiles.map((f) => [
|
|
967
|
-
clientHydrateIdForPage(f, srcDir),
|
|
968
|
-
filePathToRoute(f, srcDir).path,
|
|
969
|
-
])
|
|
970
|
-
)
|
|
971
|
-
const pageIds = useDashboard
|
|
972
|
-
? [...new Set(pageFiles.map((f) => clientHydrateIdForPage(f, srcDir)))].sort()
|
|
973
|
-
: []
|
|
974
|
-
|
|
975
|
-
let dashboard = null
|
|
976
|
-
if (useDashboard && pageIds.length > 0) {
|
|
977
|
-
dashboard = createBuildDashboard({
|
|
978
|
-
mode: 'full',
|
|
979
|
-
pageIds,
|
|
980
|
-
idToPath,
|
|
981
|
-
overview: {
|
|
982
|
-
title: '@ossy/app build',
|
|
983
|
-
configRel: overviewSnap.configRel,
|
|
984
|
-
apiRoutes: overviewSnap.apiRoutes,
|
|
985
|
-
},
|
|
986
|
-
})
|
|
987
|
-
dashboard.start()
|
|
988
|
-
} else {
|
|
989
|
-
console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
|
|
990
|
-
printBuildOverview({
|
|
991
|
-
pagesSourcePath: pagesGeneratedPath,
|
|
992
|
-
apiOverviewFiles,
|
|
993
|
-
configPath,
|
|
994
|
-
pageFiles,
|
|
995
|
-
})
|
|
996
|
-
}
|
|
997
922
|
|
|
998
923
|
if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
|
|
999
924
|
console.log(
|
|
@@ -1003,12 +928,9 @@ export const build = async (cliArgs) => {
|
|
|
1003
928
|
|
|
1004
929
|
const { taskOverviewFiles } = resolveTaskSource({ srcDir, buildPath })
|
|
1005
930
|
await compileOssyNodeArtifacts({
|
|
1006
|
-
pageFiles,
|
|
1007
|
-
srcDir,
|
|
1008
|
-
ossyDir,
|
|
1009
|
-
buildPath,
|
|
1010
931
|
apiFiles: apiOverviewFiles,
|
|
1011
932
|
taskFiles: taskOverviewFiles,
|
|
933
|
+
ossyDir,
|
|
1012
934
|
nodeEnv: 'production',
|
|
1013
935
|
})
|
|
1014
936
|
|
|
@@ -1019,23 +941,13 @@ export const build = async (cliArgs) => {
|
|
|
1019
941
|
})
|
|
1020
942
|
copyOssyAppRuntime({ scriptDir, buildPath })
|
|
1021
943
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
buildPath,
|
|
1030
|
-
nodeEnv: 'production',
|
|
1031
|
-
createClientRollupPlugins: createOssyClientRollupPlugins,
|
|
1032
|
-
minifyBrowserStaticChunks,
|
|
1033
|
-
reporter: dashboard,
|
|
1034
|
-
})
|
|
1035
|
-
} finally {
|
|
1036
|
-
dashboard.dispose()
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
944
|
+
await compileCombinedBundle({
|
|
945
|
+
ssrEntryPath,
|
|
946
|
+
clientEntryPath,
|
|
947
|
+
buildPath,
|
|
948
|
+
nodeEnv: 'production',
|
|
949
|
+
copyPublicFrom: publicDir,
|
|
950
|
+
})
|
|
1039
951
|
|
|
1040
952
|
console.log(' \x1b[32m✔\x1b[0m \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild finished\x1b[0m\n')
|
|
1041
953
|
};
|
|
@@ -1,144 +1,8 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import { rollup } from 'rollup'
|
|
4
|
-
import { pageIdFromHydrateEntryName } from './build-terminal.js'
|
|
5
2
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
fs.mkdirSync(dest, { recursive: true })
|
|
10
|
-
fs.cpSync(copyPublicFrom, dest, { recursive: true })
|
|
3
|
+
export function staticHtmlPathForRoute (routePath, publicDir) {
|
|
4
|
+
const segments = routePath === '/' ? [] : routePath.replace(/^\//, '').split('/')
|
|
5
|
+
return path.join(publicDir, ...segments, 'index.html')
|
|
11
6
|
}
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
entryName,
|
|
15
|
-
stubPath,
|
|
16
|
-
buildPath,
|
|
17
|
-
plugins,
|
|
18
|
-
minifyPlugin,
|
|
19
|
-
}) {
|
|
20
|
-
const bundle = await rollup({
|
|
21
|
-
input: { [entryName]: stubPath },
|
|
22
|
-
plugins,
|
|
23
|
-
})
|
|
24
|
-
try {
|
|
25
|
-
await bundle.write({
|
|
26
|
-
dir: path.join(buildPath, 'public'),
|
|
27
|
-
format: 'esm',
|
|
28
|
-
inlineDynamicImports: true,
|
|
29
|
-
entryFileNames (chunkInfo) {
|
|
30
|
-
const n = chunkInfo.name
|
|
31
|
-
if (n.startsWith('hydrate__')) {
|
|
32
|
-
const pageId = n.slice('hydrate__'.length)
|
|
33
|
-
return `static/${pageId}.js`
|
|
34
|
-
}
|
|
35
|
-
return 'static/[name].js'
|
|
36
|
-
},
|
|
37
|
-
chunkFileNames: 'static/[name]-[hash].js',
|
|
38
|
-
plugins: minifyPlugin ? [minifyPlugin] : [],
|
|
39
|
-
})
|
|
40
|
-
} finally {
|
|
41
|
-
await bundle.close()
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function bundleWithConcurrency (entries, concurrency, processFn) {
|
|
46
|
-
const results = new Array(entries.length)
|
|
47
|
-
let nextIdx = 0
|
|
48
|
-
async function worker () {
|
|
49
|
-
while (nextIdx < entries.length) {
|
|
50
|
-
const idx = nextIdx++
|
|
51
|
-
results[idx] = await processFn(entries[idx]).then(
|
|
52
|
-
(value) => ({ status: 'fulfilled', value }),
|
|
53
|
-
(reason) => ({ status: 'rejected', reason }),
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
await Promise.all(Array.from({ length: concurrency }, worker))
|
|
58
|
-
return results
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function bundleHydratePagesParallel ({
|
|
62
|
-
clientHydrateInput,
|
|
63
|
-
buildPath,
|
|
64
|
-
copyPublicFrom,
|
|
65
|
-
nodeEnv,
|
|
66
|
-
buildPathForPlugins,
|
|
67
|
-
createClientRollupPlugins,
|
|
68
|
-
minifyBrowserStaticChunks,
|
|
69
|
-
reporter,
|
|
70
|
-
}) {
|
|
71
|
-
copyPublicToBuild({ copyPublicFrom, buildPath })
|
|
72
|
-
fs.mkdirSync(path.join(buildPath, 'public', 'static'), { recursive: true })
|
|
73
|
-
|
|
74
|
-
const entries = Object.entries(clientHydrateInput)
|
|
75
|
-
|
|
76
|
-
const BUNDLE_CONCURRENCY = 4
|
|
77
|
-
|
|
78
|
-
const results = await bundleWithConcurrency(
|
|
79
|
-
entries,
|
|
80
|
-
BUNDLE_CONCURRENCY,
|
|
81
|
-
async ([entryName, stubPath]) => {
|
|
82
|
-
const pageId = pageIdFromHydrateEntryName(entryName)
|
|
83
|
-
const t0 = Date.now()
|
|
84
|
-
reporter?.startBundle?.(pageId)
|
|
85
|
-
try {
|
|
86
|
-
const plugins = createClientRollupPlugins({
|
|
87
|
-
nodeEnv,
|
|
88
|
-
copyPublicFrom: undefined,
|
|
89
|
-
buildPath: buildPathForPlugins,
|
|
90
|
-
})
|
|
91
|
-
await bundleOneHydratePage({
|
|
92
|
-
entryName,
|
|
93
|
-
stubPath,
|
|
94
|
-
buildPath,
|
|
95
|
-
plugins,
|
|
96
|
-
minifyPlugin: minifyBrowserStaticChunks(),
|
|
97
|
-
})
|
|
98
|
-
reporter?.completeBundle?.(pageId, { ok: true, ms: Date.now() - t0 })
|
|
99
|
-
} catch (error) {
|
|
100
|
-
reporter?.completeBundle?.(pageId, {
|
|
101
|
-
ok: false,
|
|
102
|
-
ms: Date.now() - t0,
|
|
103
|
-
error,
|
|
104
|
-
})
|
|
105
|
-
console.error(`[@ossy/app][client-bundle] ${entryName} failed:`, error)
|
|
106
|
-
throw error
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
const failures = results.filter((r) => r.status === 'rejected').length
|
|
112
|
-
return { results, failures }
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export default {
|
|
116
|
-
type: '@ossy/app/prerender-react',
|
|
117
|
-
async handler (input) {
|
|
118
|
-
const op = input?.op
|
|
119
|
-
if (op === 'runProduction') {
|
|
120
|
-
const { clientHydrateInput, pageFilesLength, copyPublicFrom, buildPath, nodeEnv,
|
|
121
|
-
buildPathForPlugins, createClientRollupPlugins, minifyBrowserStaticChunks, reporter } = input
|
|
122
|
-
if (pageFilesLength === 0 || Object.keys(clientHydrateInput).length === 0) {
|
|
123
|
-
return { bundleFailures: 0 }
|
|
124
|
-
}
|
|
125
|
-
const { failures: bundleFailures } = await bundleHydratePagesParallel({
|
|
126
|
-
clientHydrateInput,
|
|
127
|
-
buildPath,
|
|
128
|
-
copyPublicFrom,
|
|
129
|
-
nodeEnv,
|
|
130
|
-
buildPathForPlugins: buildPathForPlugins ?? buildPath,
|
|
131
|
-
createClientRollupPlugins,
|
|
132
|
-
minifyBrowserStaticChunks,
|
|
133
|
-
reporter,
|
|
134
|
-
})
|
|
135
|
-
if (bundleFailures > 0) {
|
|
136
|
-
console.warn(`[@ossy/app][build] Finished with ${bundleFailures} client bundle error(s)`)
|
|
137
|
-
}
|
|
138
|
-
return { bundleFailures }
|
|
139
|
-
}
|
|
140
|
-
throw new Error(
|
|
141
|
-
`[@ossy/app][prerender-react] Unknown op: ${String(op)} (expected runProduction)`
|
|
142
|
-
)
|
|
143
|
-
},
|
|
144
|
-
}
|
|
8
|
+
export default { type: '@ossy/app/prerender-react' }
|
package/cli/render-page.task.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
|
-
|
|
4
|
-
const __ossyDir = path.dirname(fileURLToPath(import.meta.url))
|
|
5
|
-
|
|
6
|
-
async function loadSsrBundle (route) {
|
|
7
|
-
if (typeof route?.id !== 'string' || !route.id) {
|
|
8
|
-
throw new Error(`[@ossy/app][BuildPage] Route has no id`)
|
|
9
|
-
}
|
|
10
|
-
const bundlePath = path.join(__ossyDir, '..', 'ssr', `${route.id}.mjs`)
|
|
11
|
-
const mod = await import(pathToFileURL(bundlePath).href)
|
|
12
|
-
if (typeof mod?.renderPage !== 'function') {
|
|
13
|
-
throw new Error(
|
|
14
|
-
`[@ossy/app][BuildPage] SSR bundle for "${route.id}" must export renderPage (got ${typeof mod?.renderPage}).`
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
return mod
|
|
18
|
-
}
|
|
1
|
+
import { renderPage as ssrRender } from '../ssr/app.mjs'
|
|
19
2
|
|
|
20
3
|
export function buildPrerenderAppConfig ({
|
|
21
4
|
buildTimeConfig,
|
|
@@ -36,6 +19,7 @@ export function buildPrerenderAppConfig ({
|
|
|
36
19
|
workspaceId: buildTimeConfig.workspaceId,
|
|
37
20
|
apiUrl: buildTimeConfig.apiUrl,
|
|
38
21
|
pages,
|
|
22
|
+
pageId: activeRouteId,
|
|
39
23
|
sidebarPrimaryCollapsed: false,
|
|
40
24
|
}
|
|
41
25
|
}
|
|
@@ -61,12 +45,7 @@ export function buildHydrationAppConfig (appConfig) {
|
|
|
61
45
|
|
|
62
46
|
export const BuildPage = {
|
|
63
47
|
async handle ({ route, appConfig }) {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return renderPage(hydrationConfig, {
|
|
68
|
-
bootstrapScriptContent: `window.__INITIAL_APP_CONFIG__=${JSON.stringify(hydrationConfig)}`,
|
|
69
|
-
bootstrapModules: [`/static/${route.id}.js`],
|
|
70
|
-
})
|
|
48
|
+
const config = buildHydrationAppConfig(appConfig)
|
|
49
|
+
return ssrRender(route.id, config, {})
|
|
71
50
|
},
|
|
72
51
|
}
|
package/cli/server.js
CHANGED
|
@@ -130,7 +130,7 @@ app.all('*all', async (req, res) => {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
const pageRoute = pageRouter.getPageByUrl(requestUrl)
|
|
133
|
-
if (pageRoute
|
|
133
|
+
if (pageRoute) {
|
|
134
134
|
const appConfig = buildPrerenderAppConfig({
|
|
135
135
|
buildTimeConfig,
|
|
136
136
|
pageList: sitePageList,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"@babel/eslint-parser": "^7.15.8",
|
|
28
28
|
"@babel/preset-react": "^7.26.3",
|
|
29
29
|
"@babel/register": "^7.25.9",
|
|
30
|
-
"@ossy/connected-components": "^1.13.
|
|
31
|
-
"@ossy/design-system": "^1.13.
|
|
32
|
-
"@ossy/pages": "^1.13.
|
|
33
|
-
"@ossy/router": "^1.13.
|
|
34
|
-
"@ossy/router-react": "^1.13.
|
|
35
|
-
"@ossy/sdk": "^1.13.
|
|
36
|
-
"@ossy/sdk-react": "^1.13.
|
|
37
|
-
"@ossy/themes": "^1.13.
|
|
30
|
+
"@ossy/connected-components": "^1.13.4",
|
|
31
|
+
"@ossy/design-system": "^1.13.4",
|
|
32
|
+
"@ossy/pages": "^1.13.4",
|
|
33
|
+
"@ossy/router": "^1.13.4",
|
|
34
|
+
"@ossy/router-react": "^1.13.4",
|
|
35
|
+
"@ossy/sdk": "^1.13.4",
|
|
36
|
+
"@ossy/sdk-react": "^1.13.4",
|
|
37
|
+
"@ossy/themes": "^1.13.4",
|
|
38
38
|
"@rollup/plugin-alias": "^6.0.0",
|
|
39
39
|
"@rollup/plugin-babel": "6.1.0",
|
|
40
40
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"README.md",
|
|
68
68
|
"tsconfig.json"
|
|
69
69
|
],
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "c2640eba775b192c34a888bae8add3c3461305ee"
|
|
71
71
|
}
|