@ossy/app 1.13.1 → 1.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/build.js CHANGED
@@ -92,14 +92,8 @@ export const OSSY_SSR_DIRNAME = 'ssr'
92
92
  /** Temp stub entries for SSR bundles (inside .ossy/). */
93
93
  const OSSY_SSR_ENTRIES_DIRNAME = 'ssr-entries'
94
94
 
95
- /** Per-page client entries: `hydrate-<pageId>.jsx` under `.ossy/` */
96
- const HYDRATE_STUB_PREFIX = 'hydrate-'
97
- const HYDRATE_STUB_SUFFIX = '.jsx'
98
-
99
- /** Rollup input chunk name for a page id (safe identifier; id may contain `-`). */
100
- export function hydrateEntryName (pageId) {
101
- return `hydrate__${pageId}`
102
- }
95
+ /** Single shared client hydrate entry filename under `.ossy/` */
96
+ const HYDRATE_ENTRY_FILENAME = 'hydrate-entry.jsx'
103
97
 
104
98
  export function ossyGeneratedDir (buildPath) {
105
99
  return path.join(buildPath, OSSY_GEN_DIRNAME)
@@ -709,83 +703,81 @@ export function writePageServerRollupEntry ({ pageAbsPath, stubPath }) {
709
703
  }
710
704
 
711
705
  /**
712
- * One bundle per page: exports the component (for server SSR import) and auto-hydrates in the browser.
713
- * React is kept as a bare external import resolved from node_modules on the server and via
714
- * import map on the client.
706
+ * Generates a single shared hydrate entry that dynamically imports the active page at runtime.
707
+ * Rollup processes this once, emitting React and shared deps as reusable chunks instead of
708
+ * bundling them into every page separately.
715
709
  */
716
- export function generatePageHydrateModule ({ pageAbsPath, stubAbsPath, srcDir }) {
717
- const rel = relToGeneratedImport(stubAbsPath, pageAbsPath)
710
+ export function generateHydrateEntry ({ pageFiles, srcDir, stubAbsPath }) {
711
+ const seenIds = new Set()
712
+ const pageLines = []
713
+ for (const f of pageFiles) {
714
+ const hydrateId = clientHydrateIdForPage(f, srcDir)
715
+ if (seenIds.has(hydrateId)) {
716
+ throw new Error(
717
+ `[@ossy/app] Duplicate client hydrate id "${hydrateId}" (${f}). Pages need unique ids.`
718
+ )
719
+ }
720
+ seenIds.add(hydrateId)
721
+ const rel = relToGeneratedImport(stubAbsPath, f)
722
+ pageLines.push(` '${hydrateId}': () => import('./${rel}'),`)
723
+ }
724
+
718
725
  return [
719
726
  '// Generated by @ossy/app — do not edit',
720
727
  '',
721
728
  "import { createElement } from 'react'",
722
729
  "import { hydrateRoot } from 'react-dom/client'",
723
730
  "import { App } from '@ossy/connected-components'",
724
- `import * as _page from './${rel}'`,
725
731
  '',
726
- 'export default _page.default',
727
- 'export const metadata = _page.metadata',
732
+ 'const config = window.__INITIAL_APP_CONFIG__ || {}',
733
+ '',
734
+ 'const pages = {',
735
+ ...pageLines,
736
+ '}',
728
737
  '',
729
- "if (typeof window !== 'undefined') {",
730
- " const config = window.__INITIAL_APP_CONFIG__ || {}",
731
- ' function PageShell (props) {',
732
- " return createElement('html', { lang: props.defaultLanguage || 'en' },",
733
- " createElement('head', null,",
734
- " createElement('meta', { charSet: 'utf-8' }),",
735
- " createElement('title', null, (_page.metadata && _page.metadata.title) || ''),",
736
- ' ),',
737
- ' createElement(App, props,',
738
- ' createElement(_page.default, props)',
738
+ 'const load = pages[config.pageId]',
739
+ 'if (load) {',
740
+ ' load().then((mod) => {',
741
+ ' const Page = mod.default',
742
+ " const metadata = mod.metadata || {}",
743
+ ' function PageShell (props) {',
744
+ " return createElement('html', { lang: props.defaultLanguage || 'en' },",
745
+ " createElement('head', null,",
746
+ " createElement('meta', { charSet: 'utf-8' }),",
747
+ " createElement('title', null, metadata.title || ''),",
748
+ ' ),',
749
+ ' createElement(App, props,',
750
+ ' createElement(Page, props)',
751
+ ' )',
739
752
  ' )',
740
- ' )',
741
- ' }',
742
- ' hydrateRoot(document, createElement(PageShell, config))',
753
+ ' }',
754
+ ' hydrateRoot(document, createElement(PageShell, config))',
755
+ ' })',
743
756
  '}',
744
757
  '',
745
758
  ].join('\n')
746
759
  }
747
760
 
748
- /** Writes `hydrate-<id>.jsx` for each page; removes stale `hydrate-*` outputs in `ossyDir` first. */
749
- export function writePageHydrateStubs (pageFiles, srcDir, ossyDir) {
761
+ /** Writes a single `hydrate-entry.jsx`; removes any stale per-page `hydrate-*.jsx` stubs first. */
762
+ export function writeHydrateEntry (pageFiles, srcDir, ossyDir) {
750
763
  if (!fs.existsSync(ossyDir)) fs.mkdirSync(ossyDir, { recursive: true })
751
764
  for (const ent of fs.readdirSync(ossyDir, { withFileTypes: true })) {
752
- const full = path.join(ossyDir, ent.name)
753
- if (ent.isDirectory() && ent.name.startsWith(HYDRATE_STUB_PREFIX)) {
754
- fs.rmSync(full, { recursive: true, force: true })
755
- } else if (
765
+ if (
756
766
  ent.isFile() &&
757
- ent.name.startsWith(HYDRATE_STUB_PREFIX) &&
758
- ent.name.endsWith(HYDRATE_STUB_SUFFIX)
767
+ ent.name.startsWith('hydrate-') &&
768
+ ent.name.endsWith('.jsx') &&
769
+ ent.name !== HYDRATE_ENTRY_FILENAME
759
770
  ) {
760
- fs.rmSync(full, { force: true })
761
- }
762
- }
763
- const seenIds = new Set()
764
- for (const f of pageFiles) {
765
- const hydrateId = clientHydrateIdForPage(f, srcDir)
766
- if (seenIds.has(hydrateId)) {
767
- throw new Error(
768
- `[@ossy/app] Duplicate client hydrate id "${hydrateId}" (${f}). Per-page hydrate bundles need unique ids.`
769
- )
771
+ fs.rmSync(path.join(ossyDir, ent.name), { force: true })
770
772
  }
771
- seenIds.add(hydrateId)
772
- const stubPath = path.join(ossyDir, `${HYDRATE_STUB_PREFIX}${hydrateId}${HYDRATE_STUB_SUFFIX}`)
773
- fs.mkdirSync(path.dirname(stubPath), { recursive: true })
774
- fs.writeFileSync(
775
- stubPath,
776
- generatePageHydrateModule({ pageAbsPath: f, stubAbsPath: stubPath, srcDir })
777
- )
778
773
  }
774
+ const stubPath = path.join(ossyDir, HYDRATE_ENTRY_FILENAME)
775
+ fs.writeFileSync(stubPath, generateHydrateEntry({ pageFiles, srcDir, stubAbsPath: stubPath }))
779
776
  }
780
777
 
781
- export function buildClientHydrateInput (pageFiles, srcDir, ossyDir) {
782
- const input = {}
783
- for (const f of pageFiles) {
784
- const hydrateId = clientHydrateIdForPage(f, srcDir)
785
- const stubPath = path.join(ossyDir, `${HYDRATE_STUB_PREFIX}${hydrateId}${HYDRATE_STUB_SUFFIX}`)
786
- input[hydrateEntryName(hydrateId)] = stubPath
787
- }
788
- return input
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) }
789
781
  }
790
782
 
791
783
  /** JSON manifest: route ids, default paths, and page source paths (posix, relative to `cwd`). */
@@ -936,7 +928,7 @@ export const build = async (cliArgs) => {
936
928
  srcDir,
937
929
  pagesGeneratedPath,
938
930
  })
939
- writePageHydrateStubs(pageFiles, srcDir, ossyDir)
931
+ writeHydrateEntry(pageFiles, srcDir, ossyDir)
940
932
  writePageSsrStubs(pageFiles, srcDir, ossyDir)
941
933
  const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
942
934
 
@@ -955,7 +947,7 @@ export const build = async (cliArgs) => {
955
947
  ? configPath
956
948
  : path.resolve(scriptDir, 'default-config.js')
957
949
 
958
- const useDashboard = Object.keys(clientHydrateInput).length > 0
950
+ const useDashboard = pageFiles.length > 0
959
951
  const overviewSnap = getBuildOverviewSnapshot({
960
952
  pagesSourcePath: pagesGeneratedPath,
961
953
  apiOverviewFiles,
@@ -1023,8 +1015,9 @@ export const build = async (cliArgs) => {
1023
1015
  try {
1024
1016
  await prerenderReactTask.handler({
1025
1017
  op: 'runProduction',
1026
- clientHydrateInput,
1018
+ hydrateEntryPath: clientHydrateInput.app,
1027
1019
  pageFilesLength: pageFiles.length,
1020
+ pageIds,
1028
1021
  copyPublicFrom: publicDir,
1029
1022
  buildPath,
1030
1023
  nodeEnv: 'production',
@@ -1,7 +1,11 @@
1
1
  import path from 'path'
2
2
  import fs from 'fs'
3
3
  import { rollup } from 'rollup'
4
- import { pageIdFromHydrateEntryName } from './build-terminal.js'
4
+
5
+ export function staticHtmlPathForRoute (routePath, publicDir) {
6
+ const segments = routePath === '/' ? [] : routePath.replace(/^\//, '').split('/')
7
+ return path.join(publicDir, ...segments, 'index.html')
8
+ }
5
9
 
6
10
  function copyPublicToBuild ({ copyPublicFrom, buildPath }) {
7
11
  if (!copyPublicFrom || !fs.existsSync(copyPublicFrom)) return
@@ -10,31 +14,22 @@ function copyPublicToBuild ({ copyPublicFrom, buildPath }) {
10
14
  fs.cpSync(copyPublicFrom, dest, { recursive: true })
11
15
  }
12
16
 
13
- async function bundleOneHydratePage ({
14
- entryName,
15
- stubPath,
17
+ async function bundleHydrateEntry ({
18
+ entryPath,
16
19
  buildPath,
17
20
  plugins,
18
21
  minifyPlugin,
19
22
  }) {
20
23
  const bundle = await rollup({
21
- input: { [entryName]: stubPath },
24
+ input: { app: entryPath },
22
25
  plugins,
23
26
  })
24
27
  try {
25
28
  await bundle.write({
26
29
  dir: path.join(buildPath, 'public'),
27
30
  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',
31
+ entryFileNames: 'static/[name].js',
32
+ chunkFileNames: 'static/chunks/[name]-[hash].js',
38
33
  plugins: minifyPlugin ? [minifyPlugin] : [],
39
34
  })
40
35
  } finally {
@@ -42,80 +37,58 @@ async function bundleOneHydratePage ({
42
37
  }
43
38
  }
44
39
 
45
- async function bundleHydratePagesParallel ({
46
- clientHydrateInput,
47
- buildPath,
48
- copyPublicFrom,
49
- nodeEnv,
50
- buildPathForPlugins,
51
- createClientRollupPlugins,
52
- minifyBrowserStaticChunks,
53
- reporter,
54
- }) {
55
- copyPublicToBuild({ copyPublicFrom, buildPath })
56
- fs.mkdirSync(path.join(buildPath, 'public', 'static'), { recursive: true })
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
57
 
58
- const entries = Object.entries(clientHydrateInput)
58
+ if (pageFilesLength === 0 || !hydrateEntryPath) {
59
+ return { bundleFailures: 0 }
60
+ }
59
61
 
60
- const results = await Promise.allSettled(
61
- entries.map(async ([entryName, stubPath]) => {
62
- const pageId = pageIdFromHydrateEntryName(entryName)
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 || []
63
67
  const t0 = Date.now()
64
- reporter?.startBundle?.(pageId)
68
+ for (const id of ids) reporter?.startBundle?.(id)
69
+
65
70
  try {
66
71
  const plugins = createClientRollupPlugins({
67
72
  nodeEnv,
68
73
  copyPublicFrom: undefined,
69
- buildPath: buildPathForPlugins,
74
+ buildPath: buildPathForPlugins ?? buildPath,
70
75
  })
71
- await bundleOneHydratePage({
72
- entryName,
73
- stubPath,
76
+ await bundleHydrateEntry({
77
+ entryPath: hydrateEntryPath,
74
78
  buildPath,
75
79
  plugins,
76
80
  minifyPlugin: minifyBrowserStaticChunks(),
77
81
  })
78
- reporter?.completeBundle?.(pageId, { ok: true, ms: Date.now() - t0 })
82
+ const ms = Date.now() - t0
83
+ for (const id of ids) reporter?.completeBundle?.(id, { ok: true, ms })
79
84
  } catch (error) {
80
- reporter?.completeBundle?.(pageId, {
81
- ok: false,
82
- ms: Date.now() - t0,
83
- error,
84
- })
85
- console.error(`[@ossy/app][client-bundle] ${entryName} failed:`, 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)
86
88
  throw error
87
89
  }
88
- })
89
- )
90
90
 
91
- const failures = results.filter((r) => r.status === 'rejected').length
92
- return { results, failures }
93
- }
94
-
95
- export default {
96
- type: '@ossy/app/prerender-react',
97
- async handler (input) {
98
- const op = input?.op
99
- if (op === 'runProduction') {
100
- const { clientHydrateInput, pageFilesLength, copyPublicFrom, buildPath, nodeEnv,
101
- buildPathForPlugins, createClientRollupPlugins, minifyBrowserStaticChunks, reporter } = input
102
- if (pageFilesLength === 0 || Object.keys(clientHydrateInput).length === 0) {
103
- return { bundleFailures: 0 }
104
- }
105
- const { failures: bundleFailures } = await bundleHydratePagesParallel({
106
- clientHydrateInput,
107
- buildPath,
108
- copyPublicFrom,
109
- nodeEnv,
110
- buildPathForPlugins: buildPathForPlugins ?? buildPath,
111
- createClientRollupPlugins,
112
- minifyBrowserStaticChunks,
113
- reporter,
114
- })
115
- if (bundleFailures > 0) {
116
- console.warn(`[@ossy/app][build] Finished with ${bundleFailures} client bundle error(s)`)
117
- }
118
- return { bundleFailures }
91
+ return { bundleFailures: 0 }
119
92
  }
120
93
  throw new Error(
121
94
  `[@ossy/app][prerender-react] Unknown op: ${String(op)} (expected runProduction)`
@@ -36,6 +36,7 @@ export function buildPrerenderAppConfig ({
36
36
  workspaceId: buildTimeConfig.workspaceId,
37
37
  apiUrl: buildTimeConfig.apiUrl,
38
38
  pages,
39
+ pageId: activeRouteId,
39
40
  sidebarPrimaryCollapsed: false,
40
41
  }
41
42
  }
@@ -61,12 +62,12 @@ export function buildHydrationAppConfig (appConfig) {
61
62
 
62
63
  export const BuildPage = {
63
64
  async handle ({ route, appConfig }) {
64
- const hydrationConfig = buildHydrationAppConfig(appConfig)
65
+ const hydrationConfig = { ...buildHydrationAppConfig(appConfig), pageId: route.id }
65
66
  const { renderPage } = await loadSsrBundle(route)
66
67
 
67
68
  return renderPage(hydrationConfig, {
68
69
  bootstrapScriptContent: `window.__INITIAL_APP_CONFIG__=${JSON.stringify(hydrationConfig)}`,
69
- bootstrapModules: [`/static/${route.id}.js`],
70
+ bootstrapModules: ['/static/app.js'],
70
71
  })
71
72
  },
72
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.13.1",
3
+ "version": "1.13.3",
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.1",
31
- "@ossy/design-system": "^1.13.1",
32
- "@ossy/pages": "^1.13.1",
33
- "@ossy/router": "^1.13.1",
34
- "@ossy/router-react": "^1.13.1",
35
- "@ossy/sdk": "^1.13.1",
36
- "@ossy/sdk-react": "^1.13.1",
37
- "@ossy/themes": "^1.13.1",
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",
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": "3dcfb2cb94eb920a9cb90f266904bd165772269e"
70
+ "gitHead": "0d5eae5748fb9a32e5dcb147444b7e9a8113dcba"
71
71
  }