@ossy/app 1.13.3 → 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 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(BROWSER_STATIC_PREFIX)) {
29
+ if (!fileName || !fileName.startsWith('public/static/')) {
35
30
  return null
36
31
  }
37
32
  const useSourceMap =
@@ -87,13 +82,13 @@ export const OSSY_PAGE_SERVER_EXTERNAL = [
87
82
  'react/jsx-runtime',
88
83
  ]
89
84
 
90
- /** Output directory (relative to buildPath) for per-page SSR bundles. */
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
87
 
95
- /** Single shared client hydrate entry filename under `.ossy/` */
88
+ /** Shared client hydrate entry filename under `.ossy/` */
96
89
  const HYDRATE_ENTRY_FILENAME = 'hydrate-entry.jsx'
90
+ /** Shared SSR entry filename under `.ossy/` */
91
+ const SSR_ENTRY_FILENAME = 'ssr-entry.mjs'
97
92
 
98
93
  export function ossyGeneratedDir (buildPath) {
99
94
  return path.join(buildPath, OSSY_GEN_DIRNAME)
@@ -199,45 +194,12 @@ export function createOssyAppBundlePlugins ({ nodeEnv }) {
199
194
  }
200
195
 
201
196
  /**
202
- * Rollup plugins for browser hydrate bundles. React and all deps are bundled in so the output
203
- * is self-contained and requires no import maps or CDN.
204
- */
205
- export function createOssyClientRollupPlugins ({ nodeEnv, copyPublicFrom, buildPath }) {
206
- const plugins = [
207
- replace({
208
- preventAssignment: true,
209
- 'process.env.NODE_ENV': JSON.stringify(nodeEnv),
210
- }),
211
- json(),
212
- nodeExternals({
213
- deps: false,
214
- devDeps: true,
215
- peerDeps: false,
216
- packagePath: path.join(process.cwd(), 'package.json'),
217
- }),
218
- resolveCommonJsDependencies(),
219
- resolveDependencies({ preferBuiltins: false }),
220
- babel({
221
- babelHelpers: 'bundled',
222
- extensions: ['.jsx', '.tsx'],
223
- presets: [['@babel/preset-react', { runtime: 'automatic' }]],
224
- }),
225
- ]
226
- if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
227
- plugins.push(
228
- copy({
229
- targets: [{ src: `${copyPublicFrom}/**/*`, dest: path.join(buildPath, 'public') }],
230
- })
231
- )
232
- }
233
- return plugins
234
- }
235
-
236
- /**
237
- * Rollup plugins for per-page SSR bundles: server-side resolution, React and all deps bundled in
238
- * 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.
239
201
  */
240
- export function createOssySsrBundlePlugins ({ nodeEnv }) {
202
+ export function createCombinedBundlePlugins ({ nodeEnv }) {
241
203
  return [
242
204
  replace({
243
205
  preventAssignment: true,
@@ -260,93 +222,6 @@ export function createOssySsrBundlePlugins ({ nodeEnv }) {
260
222
  ]
261
223
  }
262
224
 
263
- /** Generates the SSR entry stub for a page: exports renderPage(props, options) and metadata. */
264
- export function generatePageSsrModule ({ pageAbsPath, stubAbsPath }) {
265
- const rel = relToGeneratedImport(stubAbsPath, pageAbsPath)
266
- return [
267
- '// Generated by @ossy/app — do not edit',
268
- '',
269
- "import { createElement } from 'react'",
270
- "import { renderToPipeableStream } from 'react-dom/server'",
271
- "import { Writable } from 'node:stream'",
272
- "import { App } from '@ossy/connected-components'",
273
- `import * as _page from './${rel}'`,
274
- '',
275
- 'export const metadata = _page.metadata',
276
- '',
277
- 'function PageShell (props) {',
278
- " return createElement('html', { lang: props.defaultLanguage || 'en' },",
279
- " createElement('head', null,",
280
- " createElement('meta', { charSet: 'utf-8' }),",
281
- " createElement('title', null, (_page.metadata && _page.metadata.title) || ''),",
282
- ' ),',
283
- ' createElement(App, props,',
284
- ' createElement(_page.default, props)',
285
- ' )',
286
- ' )',
287
- '}',
288
- '',
289
- 'export function renderPage (props, options = {}) {',
290
- ' return new Promise((resolve, reject) => {',
291
- " let html = ''",
292
- ' const writable = new Writable({',
293
- ' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
294
- ' })',
295
- ' const { pipe } = renderToPipeableStream(createElement(PageShell, props), {',
296
- ' ...options,',
297
- ' onAllReady () { pipe(writable) },',
298
- ' onError (err) { reject(err) },',
299
- ' })',
300
- " writable.on('finish', () => resolve(html))",
301
- ' })',
302
- '}',
303
- '',
304
- ].join('\n')
305
- }
306
-
307
- /** Writes `ssr-entries/<id>.mjs` stubs for each page into ossyDir. */
308
- export function writePageSsrStubs (pageFiles, srcDir, ossyDir) {
309
- const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
310
- fs.rmSync(entriesDir, { recursive: true, force: true })
311
- if (pageFiles.length === 0) return
312
- fs.mkdirSync(entriesDir, { recursive: true })
313
- for (const f of pageFiles) {
314
- const pageId = clientHydrateIdForPage(f, srcDir)
315
- const stubPath = path.join(entriesDir, `${pageId}.mjs`)
316
- fs.mkdirSync(path.dirname(stubPath), { recursive: true })
317
- fs.writeFileSync(stubPath, generatePageSsrModule({ pageAbsPath: f, stubAbsPath: stubPath }))
318
- }
319
- }
320
-
321
- /** Compiles per-page SSR bundles to `build/ssr/<id>.mjs`. Each is self-contained with React bundled in. */
322
- export async function compilePageSsrModules ({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }) {
323
- const ssrDir = path.join(buildPath, OSSY_SSR_DIRNAME)
324
- const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
325
- fs.rmSync(ssrDir, { recursive: true, force: true })
326
- if (pageFiles.length === 0) return []
327
- fs.mkdirSync(ssrDir, { recursive: true })
328
- const plugins = createOssySsrBundlePlugins({ nodeEnv })
329
- const results = await Promise.all(
330
- pageFiles.map(async (f) => {
331
- const pageId = clientHydrateIdForPage(f, srcDir)
332
- const stubPath = path.join(entriesDir, `${pageId}.mjs`)
333
- const outFile = path.join(ssrDir, `${pageId}.mjs`)
334
- const bundle = await rollup({
335
- input: stubPath,
336
- plugins,
337
- onwarn (warning, defaultHandler) {
338
- if (onWarn) { onWarn(warning); return }
339
- defaultHandler(warning)
340
- },
341
- })
342
- await bundle.write({ file: outFile, format: 'esm', inlineDynamicImports: true })
343
- await bundle.close()
344
- return { id: pageId }
345
- })
346
- )
347
- return results
348
- }
349
-
350
225
  /** Bundles a single Node ESM file (inline dynamic imports) for SSR / API / tasks. */
351
226
  export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv, onWarn, external }) {
352
227
  const bundle = await rollup({
@@ -567,73 +442,22 @@ export async function compileTaskServerModules ({ taskFiles, ossyDir, nodeEnv, o
567
442
  }
568
443
 
569
444
  /**
570
- * Merges compiled `metadata` + `module` into `pages.generated.json` (same order as `pageBundleList`).
571
- * Writes `module` on each route for Node SSR (`import()` of `page-modules/*.mjs`); hydrate uses a separate Rollup client entry.
572
- */
573
- /**
574
- * Enriches `pages.generated.json` with `module` paths pointing to SSR bundles and merges
575
- * any `metadata` exported by each page. Runs after SSR bundles are compiled.
576
- */
577
- export async function enrichPagesGeneratedManifest ({ ossyDir, pagesGeneratedPath }) {
578
- if (!fs.existsSync(pagesGeneratedPath)) return
579
- const basePages = JSON.parse(fs.readFileSync(pagesGeneratedPath, 'utf8'))
580
- if (!Array.isArray(basePages) || basePages.length === 0) return
581
-
582
- const pages = []
583
- for (const basePage of basePages) {
584
- const moduleRelPath = `../ssr/${basePage.id}.mjs`
585
- const bundleAbs = path.join(ossyDir, moduleRelPath)
586
- let meta = {}
587
- if (fs.existsSync(bundleAbs)) {
588
- try {
589
- const mod = await import(pathToFileURL(bundleAbs).href)
590
- meta = mod?.metadata && typeof mod.metadata === 'object' ? mod.metadata : {}
591
- } catch {
592
- // metadata unreadable — continue with defaults
593
- }
594
- }
595
- const merged = {
596
- id: basePage.id,
597
- path: basePage.path,
598
- ...meta,
599
- sourceFile: basePage.sourceFile,
600
- module: moduleRelPath,
601
- }
602
- try {
603
- JSON.stringify(merged)
604
- } catch {
605
- pages.push({ id: basePage.id, path: basePage.path, sourceFile: basePage.sourceFile, module: moduleRelPath })
606
- continue
607
- }
608
- pages.push(merged)
609
- }
610
- writeOssyJson(pagesGeneratedPath, pages)
611
- }
612
-
613
- /**
614
- * 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.
615
447
  */
616
448
  export async function compileOssyNodeArtifacts ({
617
- pageFiles,
618
- srcDir,
619
- ossyDir,
620
- buildPath,
621
449
  apiFiles,
622
450
  taskFiles,
451
+ ossyDir,
623
452
  nodeEnv,
624
453
  onWarn,
625
454
  }) {
626
- const [, apiRouteList, taskList] = await Promise.all([
627
- compilePageSsrModules({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }),
455
+ const [apiRouteList, taskList] = await Promise.all([
628
456
  compileApiServerModules({ apiFiles, ossyDir, nodeEnv, onWarn }),
629
457
  compileTaskServerModules({ taskFiles, ossyDir, nodeEnv, onWarn }),
630
458
  ])
631
459
  writeOssyJson(path.join(ossyDir, OSSY_GEN_API_BASENAME), apiRouteList)
632
460
  writeOssyJson(path.join(ossyDir, OSSY_GEN_TASKS_BASENAME), taskList)
633
- await enrichPagesGeneratedManifest({
634
- ossyDir,
635
- pagesGeneratedPath: path.join(ossyDir, OSSY_GEN_PAGES_BASENAME),
636
- })
637
461
  }
638
462
 
639
463
  export function filePathToRoute(filePath, srcDir) {
@@ -664,6 +488,25 @@ export function clientHydrateIdForPage (pageAbsPath, srcDir) {
664
488
  return idMatch ? idMatch[1] : derived.id
665
489
  }
666
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
+
667
510
  /** Posix path relative to `build/.ossy/` for the compiled **Node** page module (SSR). */
668
511
  export function pageServerModuleRelPath (pageAbsPath, srcDir) {
669
512
  const pageId = clientHydrateIdForPage(pageAbsPath, srcDir)
@@ -775,18 +618,141 @@ export function writeHydrateEntry (pageFiles, srcDir, ossyDir) {
775
618
  fs.writeFileSync(stubPath, generateHydrateEntry({ pageFiles, srcDir, stubAbsPath: stubPath }))
776
619
  }
777
620
 
778
- /** Returns a single-entry input map pointing at the shared hydrate entry. */
779
- export function buildClientHydrateInput (_pageFiles, _srcDir, ossyDir) {
780
- return { app: path.join(ossyDir, HYDRATE_ENTRY_FILENAME) }
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 }) {
627
+ const seenIds = new Set()
628
+ const pagesLiteral = []
629
+ const pageModuleLines = []
630
+
631
+ for (const f of pageFiles) {
632
+ const { id, path: routePath } = pageRouteFromSource(f, srcDir)
633
+ if (seenIds.has(id)) {
634
+ throw new Error(
635
+ `[@ossy/app] Duplicate page id "${id}" (${f}). Pages need unique ids.`
636
+ )
637
+ }
638
+ seenIds.add(id)
639
+ const rel = relToGeneratedImport(stubAbsPath, f)
640
+ pagesLiteral.push(` { id: '${id}', path: '${routePath}' },`)
641
+ pageModuleLines.push(` '${id}': () => import('./${rel}'),`)
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')
693
+ }
694
+
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)
727
+ }
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()
781
748
  }
782
749
 
783
750
  /** JSON manifest: route ids, default paths, and page source paths (posix, relative to `cwd`). */
784
751
  export function buildPagesGeneratedPayload (pageFiles, srcDir, cwd = process.cwd()) {
785
752
  const pages = pageFiles.map((f) => {
786
- const { path: routePath } = filePathToRoute(f, srcDir)
787
- const pageId = clientHydrateIdForPage(f, srcDir)
753
+ const { id, path: routePath } = pageRouteFromSource(f, srcDir)
788
754
  return {
789
- id: pageId,
755
+ id,
790
756
  path: routePath,
791
757
  sourceFile: path.relative(cwd, f).replace(/\\/g, '/'),
792
758
  }
@@ -910,8 +876,9 @@ export const build = async (cliArgs) => {
910
876
  const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
911
877
  const buildPath = path.resolve('build')
912
878
  const srcDir = path.resolve('src')
913
- const configPath = path.resolve(options['--config'] || 'src/config.js');
879
+ const configPath = path.resolve(options['--config'] || 'src/config.js')
914
880
  const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
881
+ const publicDir = path.resolve('public')
915
882
 
916
883
  resetOssyBuildDir(buildPath)
917
884
 
@@ -928,16 +895,14 @@ export const build = async (cliArgs) => {
928
895
  srcDir,
929
896
  pagesGeneratedPath,
930
897
  })
898
+
899
+ // Write generated entries (both SSR and client hydrate)
900
+ const ssrEntryPath = writeSsrEntry(pageFiles, srcDir, ossyDir)
931
901
  writeHydrateEntry(pageFiles, srcDir, ossyDir)
932
- writePageSsrStubs(pageFiles, srcDir, ossyDir)
933
- const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
902
+ const clientEntryPath = path.join(ossyDir, HYDRATE_ENTRY_FILENAME)
934
903
 
935
- const { apiOverviewFiles } = resolveApiSource({
936
- srcDir,
937
- buildPath,
938
- })
939
- let middlewareSourcePath = path.resolve('src/middleware.js');
940
- const publicDir = path.resolve('public')
904
+ const { apiOverviewFiles } = resolveApiSource({ srcDir, buildPath })
905
+ let middlewareSourcePath = path.resolve('src/middleware.js')
941
906
 
942
907
  if (!fs.existsSync(middlewareSourcePath)) {
943
908
  middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
@@ -947,45 +912,13 @@ export const build = async (cliArgs) => {
947
912
  ? configPath
948
913
  : path.resolve(scriptDir, 'default-config.js')
949
914
 
950
- const useDashboard = pageFiles.length > 0
951
- const overviewSnap = getBuildOverviewSnapshot({
915
+ console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
916
+ printBuildOverview({
952
917
  pagesSourcePath: pagesGeneratedPath,
953
918
  apiOverviewFiles,
954
919
  configPath,
955
920
  pageFiles,
956
921
  })
957
- const idToPath = Object.fromEntries(
958
- pageFiles.map((f) => [
959
- clientHydrateIdForPage(f, srcDir),
960
- filePathToRoute(f, srcDir).path,
961
- ])
962
- )
963
- const pageIds = useDashboard
964
- ? [...new Set(pageFiles.map((f) => clientHydrateIdForPage(f, srcDir)))].sort()
965
- : []
966
-
967
- let dashboard = null
968
- if (useDashboard && pageIds.length > 0) {
969
- dashboard = createBuildDashboard({
970
- mode: 'full',
971
- pageIds,
972
- idToPath,
973
- overview: {
974
- title: '@ossy/app build',
975
- configRel: overviewSnap.configRel,
976
- apiRoutes: overviewSnap.apiRoutes,
977
- },
978
- })
979
- dashboard.start()
980
- } else {
981
- console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
982
- printBuildOverview({
983
- pagesSourcePath: pagesGeneratedPath,
984
- apiOverviewFiles,
985
- configPath,
986
- pageFiles,
987
- })
988
- }
989
922
 
990
923
  if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
991
924
  console.log(
@@ -995,12 +928,9 @@ export const build = async (cliArgs) => {
995
928
 
996
929
  const { taskOverviewFiles } = resolveTaskSource({ srcDir, buildPath })
997
930
  await compileOssyNodeArtifacts({
998
- pageFiles,
999
- srcDir,
1000
- ossyDir,
1001
- buildPath,
1002
931
  apiFiles: apiOverviewFiles,
1003
932
  taskFiles: taskOverviewFiles,
933
+ ossyDir,
1004
934
  nodeEnv: 'production',
1005
935
  })
1006
936
 
@@ -1011,24 +941,13 @@ export const build = async (cliArgs) => {
1011
941
  })
1012
942
  copyOssyAppRuntime({ scriptDir, buildPath })
1013
943
 
1014
- if (useDashboard && dashboard) {
1015
- try {
1016
- await prerenderReactTask.handler({
1017
- op: 'runProduction',
1018
- hydrateEntryPath: clientHydrateInput.app,
1019
- pageFilesLength: pageFiles.length,
1020
- pageIds,
1021
- copyPublicFrom: publicDir,
1022
- buildPath,
1023
- nodeEnv: 'production',
1024
- createClientRollupPlugins: createOssyClientRollupPlugins,
1025
- minifyBrowserStaticChunks,
1026
- reporter: dashboard,
1027
- })
1028
- } finally {
1029
- dashboard.dispose()
1030
- }
1031
- }
944
+ await compileCombinedBundle({
945
+ ssrEntryPath,
946
+ clientEntryPath,
947
+ buildPath,
948
+ nodeEnv: 'production',
949
+ copyPublicFrom: publicDir,
950
+ })
1032
951
 
1033
952
  console.log(' \x1b[32m✔\x1b[0m \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild finished\x1b[0m\n')
1034
953
  };
@@ -1,97 +1,8 @@
1
1
  import path from 'path'
2
- import fs from 'fs'
3
- import { rollup } from 'rollup'
4
2
 
5
3
  export function staticHtmlPathForRoute (routePath, publicDir) {
6
4
  const segments = routePath === '/' ? [] : routePath.replace(/^\//, '').split('/')
7
5
  return path.join(publicDir, ...segments, 'index.html')
8
6
  }
9
7
 
10
- function copyPublicToBuild ({ copyPublicFrom, buildPath }) {
11
- if (!copyPublicFrom || !fs.existsSync(copyPublicFrom)) return
12
- const dest = path.join(buildPath, 'public')
13
- fs.mkdirSync(dest, { recursive: true })
14
- fs.cpSync(copyPublicFrom, dest, { recursive: true })
15
- }
16
-
17
- async function bundleHydrateEntry ({
18
- entryPath,
19
- buildPath,
20
- plugins,
21
- minifyPlugin,
22
- }) {
23
- const bundle = await rollup({
24
- input: { app: entryPath },
25
- plugins,
26
- })
27
- try {
28
- await bundle.write({
29
- dir: path.join(buildPath, 'public'),
30
- format: 'esm',
31
- entryFileNames: 'static/[name].js',
32
- chunkFileNames: 'static/chunks/[name]-[hash].js',
33
- plugins: minifyPlugin ? [minifyPlugin] : [],
34
- })
35
- } finally {
36
- await bundle.close()
37
- }
38
- }
39
-
40
- export default {
41
- type: '@ossy/app/prerender-react',
42
- async handler (input) {
43
- const op = input?.op
44
- if (op === 'runProduction') {
45
- const {
46
- hydrateEntryPath,
47
- pageFilesLength,
48
- pageIds,
49
- copyPublicFrom,
50
- buildPath,
51
- nodeEnv,
52
- buildPathForPlugins,
53
- createClientRollupPlugins,
54
- minifyBrowserStaticChunks,
55
- reporter,
56
- } = input
57
-
58
- if (pageFilesLength === 0 || !hydrateEntryPath) {
59
- return { bundleFailures: 0 }
60
- }
61
-
62
- copyPublicToBuild({ copyPublicFrom, buildPath })
63
- fs.mkdirSync(path.join(buildPath, 'public', 'static'), { recursive: true })
64
- fs.mkdirSync(path.join(buildPath, 'public', 'static', 'chunks'), { recursive: true })
65
-
66
- const ids = pageIds || []
67
- const t0 = Date.now()
68
- for (const id of ids) reporter?.startBundle?.(id)
69
-
70
- try {
71
- const plugins = createClientRollupPlugins({
72
- nodeEnv,
73
- copyPublicFrom: undefined,
74
- buildPath: buildPathForPlugins ?? buildPath,
75
- })
76
- await bundleHydrateEntry({
77
- entryPath: hydrateEntryPath,
78
- buildPath,
79
- plugins,
80
- minifyPlugin: minifyBrowserStaticChunks(),
81
- })
82
- const ms = Date.now() - t0
83
- for (const id of ids) reporter?.completeBundle?.(id, { ok: true, ms })
84
- } catch (error) {
85
- const ms = Date.now() - t0
86
- for (const id of ids) reporter?.completeBundle?.(id, { ok: false, ms, error })
87
- console.error(`[@ossy/app][client-bundle] hydrate-entry failed:`, error)
88
- throw error
89
- }
90
-
91
- return { bundleFailures: 0 }
92
- }
93
- throw new Error(
94
- `[@ossy/app][prerender-react] Unknown op: ${String(op)} (expected runProduction)`
95
- )
96
- },
97
- }
8
+ export default { type: '@ossy/app/prerender-react' }
@@ -1,21 +1,4 @@
1
- import path from 'node:path'
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,
@@ -62,12 +45,7 @@ export function buildHydrationAppConfig (appConfig) {
62
45
 
63
46
  export const BuildPage = {
64
47
  async handle ({ route, appConfig }) {
65
- const hydrationConfig = { ...buildHydrationAppConfig(appConfig), pageId: route.id }
66
- const { renderPage } = await loadSsrBundle(route)
67
-
68
- return renderPage(hydrationConfig, {
69
- bootstrapScriptContent: `window.__INITIAL_APP_CONFIG__=${JSON.stringify(hydrationConfig)}`,
70
- bootstrapModules: ['/static/app.js'],
71
- })
48
+ const config = buildHydrationAppConfig(appConfig)
49
+ return ssrRender(route.id, config, {})
72
50
  },
73
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?.module) {
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",
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.3",
31
- "@ossy/design-system": "^1.13.3",
32
- "@ossy/pages": "^1.13.3",
33
- "@ossy/router": "^1.13.3",
34
- "@ossy/router-react": "^1.13.3",
35
- "@ossy/sdk": "^1.13.3",
36
- "@ossy/sdk-react": "^1.13.3",
37
- "@ossy/themes": "^1.13.3",
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": "0d5eae5748fb9a32e5dcb147444b7e9a8113dcba"
70
+ "gitHead": "c2640eba775b192c34a888bae8add3c3461305ee"
71
71
  }