@ossy/app 1.39.4 → 1.39.6
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.task.js +1 -1
- package/cli/manifest-plugin.js +19 -9
- package/package.json +9 -9
- package/runtime/page-runtime.js +26 -3
package/cli/build.task.js
CHANGED
|
@@ -227,7 +227,7 @@ function generateE2eStub ({ stubAbs, sourceAbs }) {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// Layouts are React components that wrap page renders. The stub re-exports
|
|
230
|
-
// everything (including
|
|
230
|
+
// everything (including required `id`) plus the
|
|
231
231
|
// default component so the manifest plugin can validate them and the server
|
|
232
232
|
// can inject them at render time.
|
|
233
233
|
function generateLayoutStub ({ stubAbs, sourceAbs }) {
|
package/cli/manifest-plugin.js
CHANGED
|
@@ -73,7 +73,6 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
73
73
|
* @typedef {object} LayoutManifestEntry
|
|
74
74
|
* @property {string} id Unique layout id (e.g. `'app-shell'`).
|
|
75
75
|
* @property {string} entry URL the platform serves the layout bundle from.
|
|
76
|
-
* @property {string} [applies] Optional glob pattern of page paths this layout applies to (default `'*'` = all pages).
|
|
77
76
|
*
|
|
78
77
|
* @typedef {object} E2eManifestEntry
|
|
79
78
|
* @property {string} id Unique test id (e.g. `'authentication/sign-in'`).
|
|
@@ -346,11 +345,10 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
346
345
|
continue
|
|
347
346
|
}
|
|
348
347
|
|
|
349
|
-
// Layouts wrap page
|
|
350
|
-
// (
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
// match and inject them at render time.
|
|
348
|
+
// Layouts wrap every page render. The bundle exports `id` and a default
|
|
349
|
+
// export (the React component). Only one `*.layout.jsx` per app is
|
|
350
|
+
// supported. They don't produce routable entries; they accumulate into
|
|
351
|
+
// `manifest.layouts` so the server can inject them at render time.
|
|
354
352
|
if (entryInfo.kind === 'layout') {
|
|
355
353
|
const layoutId = mod && mod.id
|
|
356
354
|
const component = mod && mod.default
|
|
@@ -368,8 +366,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
368
366
|
this.error(`[@ossy/app][build] Duplicate layout id "${layoutId}"`)
|
|
369
367
|
}
|
|
370
368
|
seenLayoutIds.add(layoutId)
|
|
371
|
-
|
|
372
|
-
layouts.push({ id: layoutId, entry: url, applies })
|
|
369
|
+
layouts.push({ id: layoutId, entry: url })
|
|
373
370
|
continue
|
|
374
371
|
}
|
|
375
372
|
|
|
@@ -419,7 +416,13 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
419
416
|
|
|
420
417
|
if (entryInfo.kind === 'page') {
|
|
421
418
|
const pagePath = rawMeta.path !== undefined ? rawMeta.path : defaultPageRoute(id)
|
|
422
|
-
entries.push({
|
|
419
|
+
entries.push({
|
|
420
|
+
type: 'page',
|
|
421
|
+
id,
|
|
422
|
+
path: pagePath,
|
|
423
|
+
title: rawMeta.title,
|
|
424
|
+
entry: url,
|
|
425
|
+
})
|
|
423
426
|
} else if (entryInfo.kind === 'api') {
|
|
424
427
|
// APIs have no sensible default route (unlike pages where we can
|
|
425
428
|
// derive `/<id>`). Failing here surfaces a missing `metadata.path`
|
|
@@ -438,6 +441,13 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
438
441
|
}
|
|
439
442
|
}
|
|
440
443
|
|
|
444
|
+
if (layouts.length > 1) {
|
|
445
|
+
this.error(
|
|
446
|
+
`[@ossy/app][build] Only one *.layout.jsx per app is allowed (found ${layouts.length}). ` +
|
|
447
|
+
'Use conditional rendering inside the app layout for route-specific chrome.',
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
|
|
441
451
|
/** @type {Manifest} */
|
|
442
452
|
const manifest = { entries, components, slots, resourceTemplates, aggregates, integrations, startups, emails, actions, e2es, layouts, config: configValue || {} }
|
|
443
453
|
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "1.39.
|
|
3
|
+
"version": "1.39.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"@babel/eslint-parser": "^7.15.8",
|
|
39
39
|
"@babel/preset-react": "^7.26.3",
|
|
40
40
|
"@babel/register": "^7.25.9",
|
|
41
|
-
"@ossy/design-system": "^1.39.
|
|
41
|
+
"@ossy/design-system": "^1.39.6",
|
|
42
42
|
"@ossy/pages": "^1.23.0",
|
|
43
|
-
"@ossy/platform": "^1.38.
|
|
44
|
-
"@ossy/router": "^1.39.
|
|
45
|
-
"@ossy/router-react": "^1.39.
|
|
46
|
-
"@ossy/sdk": "^1.39.
|
|
47
|
-
"@ossy/sdk-react": "^1.39.
|
|
48
|
-
"@ossy/themes": "^1.39.
|
|
43
|
+
"@ossy/platform": "^1.38.6",
|
|
44
|
+
"@ossy/router": "^1.39.6",
|
|
45
|
+
"@ossy/router-react": "^1.39.6",
|
|
46
|
+
"@ossy/sdk": "^1.39.6",
|
|
47
|
+
"@ossy/sdk-react": "^1.39.6",
|
|
48
|
+
"@ossy/themes": "^1.39.6",
|
|
49
49
|
"@rollup/plugin-alias": "^6.0.0",
|
|
50
50
|
"@rollup/plugin-babel": "^7.0.0",
|
|
51
51
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"README.md",
|
|
80
80
|
"tsconfig.json"
|
|
81
81
|
],
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "72d150cf20ff7ba25b328bc5b429b065c2840b2b"
|
|
83
83
|
}
|
package/runtime/page-runtime.js
CHANGED
|
@@ -26,6 +26,25 @@ export async function loadComponents (entries = []) {
|
|
|
26
26
|
return map
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Dynamically imports the app's single layout bundle. Layout components cannot
|
|
31
|
+
* be serialized into the bootstrap JSON (functions are stripped), so the server
|
|
32
|
+
* passes `layoutEntry` and the client loads the same module here. When present,
|
|
33
|
+
* every page is wrapped — route-specific chrome is decided inside the layout.
|
|
34
|
+
*
|
|
35
|
+
* @param {string | null | undefined} layoutEntry
|
|
36
|
+
* @returns {Promise<import('react').ComponentType | null>}
|
|
37
|
+
*/
|
|
38
|
+
export async function loadLayout (layoutEntry) {
|
|
39
|
+
if (!layoutEntry) return null
|
|
40
|
+
try {
|
|
41
|
+
const mod = await import(layoutEntry)
|
|
42
|
+
return mod?.default ?? null
|
|
43
|
+
} catch {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
function buildTree ({ Component, Layout, metadata, props }) {
|
|
30
49
|
const lang = props.htmlLang || props.defaultLanguage || 'en'
|
|
31
50
|
const pageEl = createElement(Component, props)
|
|
@@ -82,8 +101,11 @@ export function createPageEntry (pageModule, options = {}) {
|
|
|
82
101
|
const tree = buildTree({ Component, Layout, metadata, props: { ...pageProps, components } })
|
|
83
102
|
const bootstrapUrl = toBootstrapUrl(entryUrl)
|
|
84
103
|
const bootstrapModules = bootstrapUrl ? [bootstrapUrl] : []
|
|
104
|
+
// Layout is a React component — JSON.stringify drops functions, so hydration
|
|
105
|
+
// would render without the shell unless we pass the serializable entry URL.
|
|
106
|
+
const { Layout: _layout, ...bootstrapProps } = props
|
|
85
107
|
const bootstrapScriptContent =
|
|
86
|
-
'window.__OSSY__=' + escapeBootstrapJson(JSON.stringify(
|
|
108
|
+
'window.__OSSY__=' + escapeBootstrapJson(JSON.stringify(bootstrapProps ?? {}))
|
|
87
109
|
|
|
88
110
|
return new Promise((resolve, reject) => {
|
|
89
111
|
let html = ''
|
|
@@ -111,11 +133,12 @@ export function createPageEntry (pageModule, options = {}) {
|
|
|
111
133
|
if (typeof document === 'undefined' || typeof window === 'undefined') return
|
|
112
134
|
hydrated = true
|
|
113
135
|
const props = window.__OSSY__ || {}
|
|
114
|
-
const {
|
|
136
|
+
const { layoutEntry = null, componentEntries = [], ...pageProps } = props
|
|
115
137
|
Promise.all([
|
|
116
138
|
import('react-dom/client'),
|
|
117
139
|
loadComponents(componentEntries),
|
|
118
|
-
|
|
140
|
+
loadLayout(layoutEntry),
|
|
141
|
+
]).then(([{ hydrateRoot }, components, Layout]) => {
|
|
119
142
|
hydrateRoot(document, buildTree({ Component, Layout, metadata, props: { ...pageProps, components } }))
|
|
120
143
|
})
|
|
121
144
|
}
|