@ossy/app 1.24.1 → 1.25.1
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 +21 -9
- package/cli/get-platform-files.task.js +13 -7
- package/cli/manifest-plugin.js +14 -2
- package/package.json +10 -10
- package/runtime/page-runtime.js +34 -3
- package/runtime/task-runtime.js +2 -2
package/cli/build.task.js
CHANGED
|
@@ -14,10 +14,11 @@ import getPlatformFiles, {
|
|
|
14
14
|
API_FILE_PATTERN,
|
|
15
15
|
TASK_FILE_PATTERN,
|
|
16
16
|
RESOURCE_FILE_PATTERN,
|
|
17
|
+
COMPONENT_FILE_PATTERN,
|
|
17
18
|
} from './get-platform-files.task.js'
|
|
18
19
|
import { manifestPlugin } from './manifest-plugin.js'
|
|
19
20
|
|
|
20
|
-
export { PAGE_FILE_PATTERN, API_FILE_PATTERN, TASK_FILE_PATTERN, RESOURCE_FILE_PATTERN }
|
|
21
|
+
export { PAGE_FILE_PATTERN, API_FILE_PATTERN, TASK_FILE_PATTERN, RESOURCE_FILE_PATTERN, COMPONENT_FILE_PATTERN }
|
|
21
22
|
|
|
22
23
|
// Generated entry stubs live under `build/.ossy/entries/`. Putting them
|
|
23
24
|
// inside `build/` means they're cleaned automatically with every build,
|
|
@@ -67,7 +68,7 @@ function entryNameFromSource (sourcePath, srcDir, packageName) {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
function stubFileNameFor (kind, sourcePath, srcDir, packageName) {
|
|
70
|
-
const ext = kind === 'page' ? '.entry.jsx' : '.entry.js'
|
|
71
|
+
const ext = (kind === 'page' || kind === 'component') ? '.entry.jsx' : '.entry.js'
|
|
71
72
|
return entryNameFromSource(sourcePath, srcDir, packageName) + ext
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -110,13 +111,10 @@ function generateApiStub ({ stubAbs, sourceAbs }) {
|
|
|
110
111
|
function generateTaskStub ({ stubAbs, sourceAbs }) {
|
|
111
112
|
const importPath = relImport(stubAbs, sourceAbs)
|
|
112
113
|
return [
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'',
|
|
117
|
-
'const entry = createTaskEntry(task)',
|
|
118
|
-
'export const metadata = entry.metadata',
|
|
119
|
-
'export const run = entry.run',
|
|
114
|
+
'// Generated by @ossy/app — do not edit',
|
|
115
|
+
`import * as _task from '${importPath}'`,
|
|
116
|
+
'export const metadata = _task.metadata',
|
|
117
|
+
'export const run = _task.run ?? _task.default',
|
|
120
118
|
'',
|
|
121
119
|
].join('\n')
|
|
122
120
|
}
|
|
@@ -133,10 +131,23 @@ function generateResourceStub ({ stubAbs, sourceAbs }) {
|
|
|
133
131
|
].join('\n')
|
|
134
132
|
}
|
|
135
133
|
|
|
134
|
+
// Components are re-exported as-is — the manifest plugin reads `metadata.id`
|
|
135
|
+
// from the bundled output and records the entry URL. Components don't get
|
|
136
|
+
// route stubs or hydration bootstrapping; they're loaded on-demand at runtime.
|
|
137
|
+
function generateComponentStub ({ stubAbs, sourceAbs }) {
|
|
138
|
+
const importPath = relImport(stubAbs, sourceAbs)
|
|
139
|
+
return [
|
|
140
|
+
'// Generated by @ossy/app — do not edit',
|
|
141
|
+
`export { default, metadata } from '${importPath}'`,
|
|
142
|
+
'',
|
|
143
|
+
].join('\n')
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
function stubFor (kind, args) {
|
|
137
147
|
if (kind === 'page') return generatePageStub(args)
|
|
138
148
|
if (kind === 'api') return generateApiStub(args)
|
|
139
149
|
if (kind === 'resource') return generateResourceStub(args)
|
|
150
|
+
if (kind === 'component') return generateComponentStub(args)
|
|
140
151
|
return generateTaskStub(args)
|
|
141
152
|
}
|
|
142
153
|
|
|
@@ -177,6 +188,7 @@ export async function build (cliArgs = []) {
|
|
|
177
188
|
...platformFiles.apis,
|
|
178
189
|
...platformFiles.tasks,
|
|
179
190
|
...platformFiles.resources,
|
|
191
|
+
...platformFiles.components,
|
|
180
192
|
]
|
|
181
193
|
|
|
182
194
|
const entriesByStub = new Map()
|
|
@@ -5,9 +5,10 @@ export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
|
|
|
5
5
|
export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
|
|
6
6
|
export const TASK_FILE_PATTERN = /\.task\.(mjs|cjs|js)$/
|
|
7
7
|
export const RESOURCE_FILE_PATTERN = /\.resource\.(mjs|cjs|js)$/
|
|
8
|
+
export const COMPONENT_FILE_PATTERN = /\.component\.(jsx?|tsx?)$/
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* @typedef {'page' | 'api' | 'task' | 'resource'} EntryKind
|
|
11
|
+
* @typedef {'page' | 'api' | 'task' | 'resource' | 'component'} EntryKind
|
|
11
12
|
*
|
|
12
13
|
* @typedef {object} PlatformEntry
|
|
13
14
|
* @property {EntryKind} kind Which bucket this entry lives in.
|
|
@@ -16,10 +17,11 @@ export const RESOURCE_FILE_PATTERN = /\.resource\.(mjs|cjs|js)$/
|
|
|
16
17
|
* @property {string} [packageSrcDir] Absolute path to the package's `src/` dir, set only for installed-package entries.
|
|
17
18
|
*
|
|
18
19
|
* @typedef {object} PlatformFiles
|
|
19
|
-
* @property {PlatformEntry[]} pages
|
|
20
|
-
* @property {PlatformEntry[]} apis
|
|
21
|
-
* @property {PlatformEntry[]} tasks
|
|
22
|
-
* @property {PlatformEntry[]} resources
|
|
20
|
+
* @property {PlatformEntry[]} pages Discovered `*.page.{jsx,tsx,...}` entries.
|
|
21
|
+
* @property {PlatformEntry[]} apis Discovered `*.api.{js,mjs,cjs}` entries.
|
|
22
|
+
* @property {PlatformEntry[]} tasks Discovered `*.task.{js,mjs,cjs}` entries.
|
|
23
|
+
* @property {PlatformEntry[]} resources Discovered `*.resource.{js,mjs,cjs}` entries.
|
|
24
|
+
* @property {PlatformEntry[]} components Discovered `*.component.{jsx,tsx,...}` entries.
|
|
23
25
|
*/
|
|
24
26
|
export function discoverFilesByPattern (srcDir, filePattern) {
|
|
25
27
|
const dir = path.resolve(srcDir)
|
|
@@ -42,6 +44,7 @@ function classifyFile (absPath) {
|
|
|
42
44
|
if (API_FILE_PATTERN.test(base)) return 'api'
|
|
43
45
|
if (TASK_FILE_PATTERN.test(base)) return 'task'
|
|
44
46
|
if (RESOURCE_FILE_PATTERN.test(base)) return 'resource'
|
|
47
|
+
if (COMPONENT_FILE_PATTERN.test(base)) return 'component'
|
|
45
48
|
return null
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -58,6 +61,7 @@ export function metadataIdFromFile (absPath, srcDir) {
|
|
|
58
61
|
.replace(PAGE_FILE_PATTERN, '')
|
|
59
62
|
.replace(API_FILE_PATTERN, '')
|
|
60
63
|
.replace(TASK_FILE_PATTERN, '')
|
|
64
|
+
.replace(COMPONENT_FILE_PATTERN, '')
|
|
61
65
|
if (noExt === 'home' || noExt === 'index') return 'home'
|
|
62
66
|
return noExt.replace(/\//g, '-')
|
|
63
67
|
}
|
|
@@ -109,6 +113,7 @@ export function discoverInstalledPackageEntries (projectRoot) {
|
|
|
109
113
|
...discoverFilesByPattern(packageSrcDir, API_FILE_PATTERN),
|
|
110
114
|
...discoverFilesByPattern(packageSrcDir, TASK_FILE_PATTERN),
|
|
111
115
|
...discoverFilesByPattern(packageSrcDir, RESOURCE_FILE_PATTERN),
|
|
116
|
+
...discoverFilesByPattern(packageSrcDir, COMPONENT_FILE_PATTERN),
|
|
112
117
|
]
|
|
113
118
|
for (const sourcePath of files) {
|
|
114
119
|
const stat = fs.statSync(sourcePath)
|
|
@@ -152,14 +157,15 @@ export function discoverInstalledPackageEntries (projectRoot) {
|
|
|
152
157
|
* @returns {Promise<PlatformFiles>}
|
|
153
158
|
*/
|
|
154
159
|
export default async function getPlatformFiles (srcDir) {
|
|
155
|
-
const out = { pages: [], apis: [], tasks: [], resources: [] }
|
|
156
|
-
const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources' }
|
|
160
|
+
const out = { pages: [], apis: [], tasks: [], resources: [], components: [] }
|
|
161
|
+
const bucketByKind = { page: 'pages', api: 'apis', task: 'tasks', resource: 'resources', component: 'components' }
|
|
157
162
|
|
|
158
163
|
const localFiles = [
|
|
159
164
|
...discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN),
|
|
160
165
|
...discoverFilesByPattern(srcDir, API_FILE_PATTERN),
|
|
161
166
|
...discoverFilesByPattern(srcDir, TASK_FILE_PATTERN),
|
|
162
167
|
...discoverFilesByPattern(srcDir, RESOURCE_FILE_PATTERN),
|
|
168
|
+
...discoverFilesByPattern(srcDir, COMPONENT_FILE_PATTERN),
|
|
163
169
|
]
|
|
164
170
|
for (const sourcePath of localFiles) {
|
|
165
171
|
const stat = fs.statSync(sourcePath)
|
package/cli/manifest-plugin.js
CHANGED
|
@@ -38,8 +38,13 @@ import { metadataIdFromFile, defaultPageRoute } from './get-platform-files.task.
|
|
|
38
38
|
* @property {string} entry URL the platform serves the bundle from
|
|
39
39
|
* (e.g. `/static/home.page-7f2a.js`).
|
|
40
40
|
*
|
|
41
|
+
* @typedef {object} ComponentManifestEntry
|
|
42
|
+
* @property {string} id Unique component id (from `metadata.id`).
|
|
43
|
+
* @property {string} entry URL the platform serves the component bundle from.
|
|
44
|
+
*
|
|
41
45
|
* @typedef {object} Manifest
|
|
42
46
|
* @property {ManifestEntry[]} entries Flat, ordered list of every routable thing.
|
|
47
|
+
* @property {ComponentManifestEntry[]} components Discovered `*.component.*` entries, keyed by id.
|
|
43
48
|
* @property {object[]} resourceTemplates Each `*.resource.js` file's default-exported template object.
|
|
44
49
|
* @property {object} config Inlined `src/config.js` default export.
|
|
45
50
|
*/
|
|
@@ -86,7 +91,9 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
86
91
|
const entries = []
|
|
87
92
|
/** @type {object[]} */
|
|
88
93
|
const resourceTemplates = []
|
|
89
|
-
|
|
94
|
+
/** @type {import('./get-platform-files.task.js').ComponentManifestEntry[]} */
|
|
95
|
+
const components = []
|
|
96
|
+
const seenIds = { page: new Set(), api: new Set(), task: new Set(), component: new Set() }
|
|
90
97
|
const seenResourceIds = new Set()
|
|
91
98
|
|
|
92
99
|
for (const fileName of Object.keys(bundle)) {
|
|
@@ -147,6 +154,11 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
147
154
|
}
|
|
148
155
|
seenIds[entryInfo.kind].add(id)
|
|
149
156
|
|
|
157
|
+
if (entryInfo.kind === 'component') {
|
|
158
|
+
components.push({ id, entry: url })
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
|
|
150
162
|
if (entryInfo.kind === 'page') {
|
|
151
163
|
const pagePath = rawMeta.path !== undefined ? rawMeta.path : defaultPageRoute(id)
|
|
152
164
|
entries.push({ type: 'page', id, path: pagePath, title: rawMeta.title, entry: url })
|
|
@@ -169,7 +181,7 @@ export function manifestPlugin ({ entriesByStub, srcDir, staticOutDir, configVal
|
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
/** @type {Manifest} */
|
|
172
|
-
const manifest = { entries, resourceTemplates, config: configValue || {} }
|
|
184
|
+
const manifest = { entries, components, resourceTemplates, config: configValue || {} }
|
|
173
185
|
fs.mkdirSync(path.dirname(manifestPath), { recursive: true })
|
|
174
186
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8')
|
|
175
187
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -37,15 +37,15 @@
|
|
|
37
37
|
"@babel/eslint-parser": "^7.15.8",
|
|
38
38
|
"@babel/preset-react": "^7.26.3",
|
|
39
39
|
"@babel/register": "^7.25.9",
|
|
40
|
-
"@ossy/connected-components": "^1.
|
|
41
|
-
"@ossy/design-system": "^1.
|
|
40
|
+
"@ossy/connected-components": "^1.25.1",
|
|
41
|
+
"@ossy/design-system": "^1.25.1",
|
|
42
42
|
"@ossy/pages": "^1.23.0",
|
|
43
|
-
"@ossy/platform": "^1.
|
|
44
|
-
"@ossy/router": "^1.
|
|
45
|
-
"@ossy/router-react": "^1.
|
|
46
|
-
"@ossy/sdk": "^1.
|
|
47
|
-
"@ossy/sdk-react": "^1.
|
|
48
|
-
"@ossy/themes": "^1.
|
|
43
|
+
"@ossy/platform": "^1.24.1",
|
|
44
|
+
"@ossy/router": "^1.25.1",
|
|
45
|
+
"@ossy/router-react": "^1.25.1",
|
|
46
|
+
"@ossy/sdk": "^1.25.1",
|
|
47
|
+
"@ossy/sdk-react": "^1.25.1",
|
|
48
|
+
"@ossy/themes": "^1.25.1",
|
|
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": "c8a5e5af59c1975ea8e0c5a974202c6c146236c3"
|
|
83
83
|
}
|
package/runtime/page-runtime.js
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { createElement } from 'react'
|
|
2
2
|
import { App } from '@ossy/connected-components'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Dynamically imports each component bundle listed in `entries` and returns a
|
|
6
|
+
* `Record<id, ComponentType>` suitable for passing to `App` as `components`.
|
|
7
|
+
*
|
|
8
|
+
* Entries that fail to import (network error, missing export, etc.) are silently
|
|
9
|
+
* skipped so a broken component never prevents the page from rendering.
|
|
10
|
+
*
|
|
11
|
+
* @param {Array<{ id: string, entry: string }>} entries
|
|
12
|
+
* @returns {Promise<Record<string, import('react').ComponentType>>}
|
|
13
|
+
*/
|
|
14
|
+
export async function loadComponents (entries = []) {
|
|
15
|
+
const map = {}
|
|
16
|
+
await Promise.all(
|
|
17
|
+
entries.map(async ({ id, entry }) => {
|
|
18
|
+
try {
|
|
19
|
+
const mod = await import(entry)
|
|
20
|
+
if (mod && mod.default) map[id] = mod.default
|
|
21
|
+
} catch {
|
|
22
|
+
// silently skip broken component bundles
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
)
|
|
26
|
+
return map
|
|
27
|
+
}
|
|
28
|
+
|
|
4
29
|
function buildTree ({ Component, metadata, props }) {
|
|
5
30
|
const lang = props.htmlLang || props.defaultLanguage || 'en'
|
|
6
31
|
return createElement(
|
|
@@ -50,7 +75,9 @@ export function createPageEntry (pageModule, options = {}) {
|
|
|
50
75
|
import('node:stream'),
|
|
51
76
|
])
|
|
52
77
|
|
|
53
|
-
const
|
|
78
|
+
const { componentEntries = [], ...pageProps } = props
|
|
79
|
+
const components = await loadComponents(componentEntries)
|
|
80
|
+
const tree = buildTree({ Component, metadata, props: { ...pageProps, components } })
|
|
54
81
|
const bootstrapUrl = toBootstrapUrl(entryUrl)
|
|
55
82
|
const bootstrapModules = bootstrapUrl ? [bootstrapUrl] : []
|
|
56
83
|
const bootstrapScriptContent =
|
|
@@ -82,8 +109,12 @@ export function createPageEntry (pageModule, options = {}) {
|
|
|
82
109
|
if (typeof document === 'undefined' || typeof window === 'undefined') return
|
|
83
110
|
hydrated = true
|
|
84
111
|
const props = window.__OSSY__ || {}
|
|
85
|
-
|
|
86
|
-
|
|
112
|
+
const { componentEntries = [], ...pageProps } = props
|
|
113
|
+
Promise.all([
|
|
114
|
+
import('react-dom/client'),
|
|
115
|
+
loadComponents(componentEntries),
|
|
116
|
+
]).then(([{ hydrateRoot }, components]) => {
|
|
117
|
+
hydrateRoot(document, buildTree({ Component, metadata, props: { ...pageProps, components } }))
|
|
87
118
|
})
|
|
88
119
|
}
|
|
89
120
|
|
package/runtime/task-runtime.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export function createTaskEntry (taskModule) {
|
|
2
2
|
const metadata = taskModule.metadata || {}
|
|
3
|
-
const handler = taskModule.
|
|
3
|
+
const handler = taskModule.run
|
|
4
4
|
return {
|
|
5
5
|
metadata,
|
|
6
6
|
async run (context) {
|
|
7
7
|
if (typeof handler !== 'function') {
|
|
8
|
-
throw new Error('[@ossy/app][task] No
|
|
8
|
+
throw new Error('[@ossy/app][task] No exported "run" function to call')
|
|
9
9
|
}
|
|
10
10
|
return handler(context)
|
|
11
11
|
},
|