@ossy/app 1.11.6 → 1.11.8
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/README.md +2 -2
- package/cli/build-terminal.js +16 -117
- package/cli/build.js +158 -81
- package/cli/index.js +1 -6
- package/cli/prerender-react.task.js +20 -164
- package/cli/render-page.task.js +18 -61
- package/cli/server.js +1 -40
- package/package.json +10 -10
- package/cli/dev.js +0 -342
package/README.md
CHANGED
|
@@ -21,9 +21,9 @@ export const metadata = { path: { en: '/about', sv: '/om' } }
|
|
|
21
21
|
export default () => <h1>About</h1>
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
During `dev` / `build`, the tooling writes **JSON manifests** under **`build/.ossy/`**: **`pages.generated.json`** (after compile: route ids, paths, `sourceFile`, merged `metadata`, and **`module`** — a `page-modules/<id>.mjs` path
|
|
24
|
+
During `dev` / `build`, the tooling writes **JSON manifests** under **`build/.ossy/`**: **`pages.generated.json`** (after compile: route ids, paths, `sourceFile`, merged `metadata`, and **`module`** — a `page-modules/<id>.mjs` path the **Node** server `import()`s per request for SSR), **`pages.bundle.json`** (compiled module index), plus the same pattern for API and tasks. **`pages.runtime.mjs`** exports the route table from **`pages.generated.json`** only.
|
|
25
25
|
|
|
26
|
-
**Client JS (per-page):** For each `*.page.jsx`, the build emits **`build/.ossy/hydrate-<pageId>.jsx`** → **`public/static/<pageId>.js`**.
|
|
26
|
+
**Client JS (per-page):** For each `*.page.jsx`, the build emits **`build/.ossy/hydrate-<pageId>.jsx`** → **`public/static/<pageId>.js`**. Rollup bundles that entry with the page source so **`react` resolves in the browser** (unlike the Node `page-modules/*.mjs` chunks, which keep `react` external). The HTML only loads the hydrate script for the **current** route. The inline config (`window.__INITIAL_APP_CONFIG__`) keeps request-time fields (theme, `apiUrl`, etc.); `pages` in config include `id`, `path`, and `module` for consistency with the manifest.
|
|
27
27
|
|
|
28
28
|
Add `src/config.js` for workspace and theme:
|
|
29
29
|
|
package/cli/build-terminal.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import util from 'node:util'
|
|
2
|
-
|
|
3
1
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
4
2
|
|
|
5
3
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`
|
|
@@ -69,15 +67,10 @@ function useTty (stream) {
|
|
|
69
67
|
)
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
/**
|
|
73
|
-
* Merged overview + per-page progress, split layout: left = status table, right = log tail.
|
|
74
|
-
* TTY: single redraw block + optional console capture into the right column.
|
|
75
|
-
*/
|
|
76
70
|
export function createBuildDashboard ({
|
|
77
71
|
scope = '@ossy/app',
|
|
78
72
|
mode = 'full',
|
|
79
73
|
pageIds,
|
|
80
|
-
/** @type {Record<string, string>} route path per page id */
|
|
81
74
|
idToPath = {},
|
|
82
75
|
overview = { title: '@ossy/app', configRel: null, apiRoutes: [] },
|
|
83
76
|
stream = process.stdout,
|
|
@@ -86,11 +79,7 @@ export function createBuildDashboard ({
|
|
|
86
79
|
const maxId = Math.max(6, ...ids.map((id) => String(id).length))
|
|
87
80
|
const tty = useTty(stream)
|
|
88
81
|
const termW = Math.max(60, stream.columns || 100)
|
|
89
|
-
const LEFT_W = Math.min(56, Math.max(38, Math.floor(termW * 0.42)))
|
|
90
|
-
const GAP = 2
|
|
91
|
-
const RIGHT_W = Math.max(16, termW - LEFT_W - GAP)
|
|
92
82
|
|
|
93
|
-
/** @type {Map<string, { bundle: object, prerender: object }>} */
|
|
94
83
|
const rows = new Map()
|
|
95
84
|
for (const id of ids) {
|
|
96
85
|
rows.set(id, {
|
|
@@ -99,13 +88,9 @@ export function createBuildDashboard ({
|
|
|
99
88
|
})
|
|
100
89
|
}
|
|
101
90
|
|
|
102
|
-
const logBuffer = []
|
|
103
|
-
const MAX_LOG = 120
|
|
104
91
|
let frame = 0
|
|
105
92
|
let spinTimer = null
|
|
106
93
|
let blockLines = 0
|
|
107
|
-
let captureActive = false
|
|
108
|
-
let savedConsole = null
|
|
109
94
|
|
|
110
95
|
const anyRunning = () => {
|
|
111
96
|
for (const r of rows.values()) {
|
|
@@ -114,36 +99,23 @@ export function createBuildDashboard ({
|
|
|
114
99
|
return false
|
|
115
100
|
}
|
|
116
101
|
|
|
117
|
-
function
|
|
118
|
-
const s = String(line).trim() ? String(line) : ''
|
|
119
|
-
if (!s) return
|
|
120
|
-
if (!tty) {
|
|
121
|
-
stream.write(`${s}\n`)
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
logBuffer.push(dim(s))
|
|
125
|
-
if (logBuffer.length > MAX_LOG) logBuffer.splice(0, logBuffer.length - MAX_LOG)
|
|
126
|
-
redraw()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function leftHeaderLines () {
|
|
102
|
+
function headerLines () {
|
|
130
103
|
const out = []
|
|
131
104
|
out.push(bold(overview.title || `${scope} build`))
|
|
132
105
|
if (overview.configRel) {
|
|
133
|
-
out.push(`${dim('config')} ${truncateVisible(overview.configRel,
|
|
106
|
+
out.push(`${dim('config')} ${truncateVisible(overview.configRel, termW - 10)}`)
|
|
134
107
|
}
|
|
135
108
|
const api = overview.apiRoutes || []
|
|
136
|
-
if (api.length
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
109
|
+
if (api.length > 0) {
|
|
110
|
+
if (api.length <= 2) {
|
|
111
|
+
for (const r of api) {
|
|
112
|
+
out.push(`${dim('api')} ${truncateVisible(`${r.id} ${r.path}`, termW - 6)}`)
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
out.push(`${dim('api')} ${api.length} routes`)
|
|
142
116
|
}
|
|
143
|
-
} else {
|
|
144
|
-
out.push(`${dim('api')} ${api.length} routes`)
|
|
145
117
|
}
|
|
146
|
-
out.push(dim('─'.repeat(Math.min(
|
|
118
|
+
out.push(dim('─'.repeat(Math.min(termW - 2, 44))))
|
|
147
119
|
return out
|
|
148
120
|
}
|
|
149
121
|
|
|
@@ -161,46 +133,20 @@ export function createBuildDashboard ({
|
|
|
161
133
|
return ` ${lead} ${dim(scope)} ${idCol} ${pathPad} ${padVisible(b, 20)} ${p}`
|
|
162
134
|
}
|
|
163
135
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
const body = ids.map((id) => lineForPageRow(id))
|
|
167
|
-
return [...head, ...body]
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function buildHeaderOnlyLines () {
|
|
171
|
-
return leftHeaderLines()
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Right column row i: logs bottom-aligned to the block height */
|
|
175
|
-
function rightColumnLine (i, totalRows) {
|
|
176
|
-
const start = logBuffer.length - totalRows + i
|
|
177
|
-
if (start < 0 || start >= logBuffer.length) return dim('·')
|
|
178
|
-
return truncateVisible(logBuffer[start], RIGHT_W)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function paintRow (leftLine, rightLine) {
|
|
182
|
-
const L = padVisible(truncateVisible(leftLine, LEFT_W), LEFT_W)
|
|
183
|
-
const R = truncateVisible(rightLine, RIGHT_W)
|
|
184
|
-
return `${L}${' '.repeat(GAP)}${R}\n`
|
|
136
|
+
function buildLines () {
|
|
137
|
+
return [...headerLines(), ...ids.map((id) => lineForPageRow(id))]
|
|
185
138
|
}
|
|
186
139
|
|
|
187
140
|
function redraw () {
|
|
188
141
|
if (!tty) return
|
|
189
|
-
const
|
|
190
|
-
const n = leftCol.length
|
|
191
|
-
const lines = []
|
|
192
|
-
for (let i = 0; i < n; i++) {
|
|
193
|
-
lines.push(paintRow(leftCol[i], rightColumnLine(i, n)))
|
|
194
|
-
}
|
|
142
|
+
const lines = buildLines()
|
|
195
143
|
if (blockLines === 0) {
|
|
196
|
-
for (const ln of lines) stream.write(ln)
|
|
144
|
+
for (const ln of lines) stream.write(ln + '\n')
|
|
197
145
|
blockLines = lines.length
|
|
198
146
|
return
|
|
199
147
|
}
|
|
200
148
|
stream.write(`\x1b[${blockLines}A`)
|
|
201
|
-
for (const ln of lines) {
|
|
202
|
-
stream.write(`\x1b[2K\r${ln}`)
|
|
203
|
-
}
|
|
149
|
+
for (const ln of lines) stream.write(`\x1b[2K\r${ln}\n`)
|
|
204
150
|
}
|
|
205
151
|
|
|
206
152
|
function ensureSpin () {
|
|
@@ -226,55 +172,13 @@ export function createBuildDashboard ({
|
|
|
226
172
|
if (err && !ok) stream.write(` ${red(String(err.message || err))}\n`)
|
|
227
173
|
}
|
|
228
174
|
|
|
229
|
-
function beginCapture () {
|
|
230
|
-
if (!tty || captureActive) return
|
|
231
|
-
captureActive = true
|
|
232
|
-
savedConsole = {
|
|
233
|
-
log: console.log,
|
|
234
|
-
warn: console.warn,
|
|
235
|
-
info: console.info,
|
|
236
|
-
error: console.error,
|
|
237
|
-
}
|
|
238
|
-
const fmt = (...a) => util.format(...a)
|
|
239
|
-
console.log = (...a) => {
|
|
240
|
-
pushLog(`[log] ${fmt(...a)}`)
|
|
241
|
-
}
|
|
242
|
-
console.warn = (...a) => {
|
|
243
|
-
pushLog(`[warn] ${fmt(...a)}`)
|
|
244
|
-
}
|
|
245
|
-
console.info = (...a) => {
|
|
246
|
-
pushLog(`[info] ${fmt(...a)}`)
|
|
247
|
-
}
|
|
248
|
-
console.error = (...a) => {
|
|
249
|
-
const msg = fmt(...a)
|
|
250
|
-
pushLog(`[error] ${msg}`)
|
|
251
|
-
savedConsole.error(`[@ossy/app] ${msg}`)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function endCapture () {
|
|
256
|
-
if (!captureActive || !savedConsole) return
|
|
257
|
-
console.log = savedConsole.log
|
|
258
|
-
console.warn = savedConsole.warn
|
|
259
|
-
console.info = savedConsole.info
|
|
260
|
-
console.error = savedConsole.error
|
|
261
|
-
savedConsole = null
|
|
262
|
-
captureActive = false
|
|
263
|
-
}
|
|
264
|
-
|
|
265
175
|
return {
|
|
266
|
-
pushLog,
|
|
267
|
-
|
|
268
|
-
/** Start optional console hijack + first paint (TTY). Plain header only when not a TTY. */
|
|
269
176
|
start () {
|
|
270
177
|
if (!tty) {
|
|
271
|
-
for (const ln of
|
|
272
|
-
stream.write(`${ln}\n`)
|
|
273
|
-
}
|
|
178
|
+
for (const ln of headerLines()) stream.write(`${ln}\n`)
|
|
274
179
|
stream.write('\n')
|
|
275
180
|
return
|
|
276
181
|
}
|
|
277
|
-
beginCapture()
|
|
278
182
|
redraw()
|
|
279
183
|
},
|
|
280
184
|
|
|
@@ -326,13 +230,8 @@ export function createBuildDashboard ({
|
|
|
326
230
|
redraw()
|
|
327
231
|
},
|
|
328
232
|
|
|
329
|
-
printSectionTitle () {
|
|
330
|
-
/* merged into overview.title — no-op for API compat */
|
|
331
|
-
},
|
|
332
|
-
|
|
333
233
|
dispose () {
|
|
334
234
|
stopSpin()
|
|
335
|
-
endCapture()
|
|
336
235
|
if (tty && blockLines > 0) {
|
|
337
236
|
redraw()
|
|
338
237
|
stream.write('\n')
|
package/cli/build.js
CHANGED
|
@@ -69,8 +69,6 @@ export const OSSY_TASKS_RUNTIME_BASENAME = 'tasks.runtime.mjs'
|
|
|
69
69
|
|
|
70
70
|
export const OSSY_PAGE_MODULES_DIRNAME = 'page-modules'
|
|
71
71
|
|
|
72
|
-
/** Express serves compiled page modules here for browser `import()`. Must match `server.js`. */
|
|
73
|
-
export const OSSY_PAGE_MODULE_WEB_PREFIX = '/__ossy/page-modules'
|
|
74
72
|
/** Tiny Rollup inputs that re-export `metadata` so per-page server bundles keep i18n paths. */
|
|
75
73
|
export const OSSY_PAGE_SERVER_ENTRIES_DIRNAME = 'page-server-entries'
|
|
76
74
|
export const OSSY_API_MODULES_DIRNAME = 'api-modules'
|
|
@@ -89,6 +87,11 @@ export const OSSY_PAGE_SERVER_EXTERNAL = [
|
|
|
89
87
|
'react/jsx-runtime',
|
|
90
88
|
]
|
|
91
89
|
|
|
90
|
+
/** Output directory (relative to buildPath) for per-page SSR bundles. */
|
|
91
|
+
export const OSSY_SSR_DIRNAME = 'ssr'
|
|
92
|
+
/** Temp stub entries for SSR bundles (inside .ossy/). */
|
|
93
|
+
const OSSY_SSR_ENTRIES_DIRNAME = 'ssr-entries'
|
|
94
|
+
|
|
92
95
|
/** Per-page client entries: `hydrate-<pageId>.jsx` under `.ossy/` */
|
|
93
96
|
const HYDRATE_STUB_PREFIX = 'hydrate-'
|
|
94
97
|
const HYDRATE_STUB_SUFFIX = '.jsx'
|
|
@@ -202,7 +205,8 @@ export function createOssyAppBundlePlugins ({ nodeEnv }) {
|
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
/**
|
|
205
|
-
* Rollup plugins for browser hydrate bundles.
|
|
208
|
+
* Rollup plugins for browser hydrate bundles. React and all deps are bundled in so the output
|
|
209
|
+
* is self-contained and requires no import maps or CDN.
|
|
206
210
|
*/
|
|
207
211
|
export function createOssyClientRollupPlugins ({ nodeEnv, copyPublicFrom, buildPath }) {
|
|
208
212
|
const plugins = [
|
|
@@ -214,7 +218,7 @@ export function createOssyClientRollupPlugins ({ nodeEnv, copyPublicFrom, buildP
|
|
|
214
218
|
nodeExternals({
|
|
215
219
|
deps: false,
|
|
216
220
|
devDeps: true,
|
|
217
|
-
peerDeps:
|
|
221
|
+
peerDeps: false,
|
|
218
222
|
packagePath: path.join(process.cwd(), 'package.json'),
|
|
219
223
|
}),
|
|
220
224
|
resolveCommonJsDependencies(),
|
|
@@ -235,6 +239,106 @@ export function createOssyClientRollupPlugins ({ nodeEnv, copyPublicFrom, buildP
|
|
|
235
239
|
return plugins
|
|
236
240
|
}
|
|
237
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).
|
|
245
|
+
*/
|
|
246
|
+
export function createOssySsrBundlePlugins ({ nodeEnv }) {
|
|
247
|
+
return [
|
|
248
|
+
replace({
|
|
249
|
+
preventAssignment: true,
|
|
250
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
251
|
+
}),
|
|
252
|
+
json(),
|
|
253
|
+
nodeExternals({
|
|
254
|
+
deps: false,
|
|
255
|
+
devDeps: true,
|
|
256
|
+
peerDeps: false,
|
|
257
|
+
packagePath: path.join(process.cwd(), 'package.json'),
|
|
258
|
+
}),
|
|
259
|
+
resolveCommonJsDependencies(),
|
|
260
|
+
resolveDependencies({ preferBuiltins: true }),
|
|
261
|
+
babel({
|
|
262
|
+
babelHelpers: 'bundled',
|
|
263
|
+
extensions: ['.jsx', '.tsx'],
|
|
264
|
+
presets: ['@babel/preset-react'],
|
|
265
|
+
}),
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
|
|
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 * as _page from './${rel}'`,
|
|
279
|
+
'',
|
|
280
|
+
'export const metadata = _page.metadata',
|
|
281
|
+
'',
|
|
282
|
+
'export function renderPage (props, options = {}) {',
|
|
283
|
+
' return new Promise((resolve, reject) => {',
|
|
284
|
+
" let html = ''",
|
|
285
|
+
' const writable = new Writable({',
|
|
286
|
+
' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
|
|
287
|
+
' })',
|
|
288
|
+
' const { pipe } = renderToPipeableStream(createElement(_page.default, props), {',
|
|
289
|
+
' ...options,',
|
|
290
|
+
' onAllReady () { pipe(writable) },',
|
|
291
|
+
' onError (err) { reject(err) },',
|
|
292
|
+
' })',
|
|
293
|
+
" writable.on('finish', () => resolve(html))",
|
|
294
|
+
' })',
|
|
295
|
+
'}',
|
|
296
|
+
'',
|
|
297
|
+
].join('\n')
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Writes `ssr-entries/<id>.mjs` stubs for each page into ossyDir. */
|
|
301
|
+
export function writePageSsrStubs (pageFiles, srcDir, ossyDir) {
|
|
302
|
+
const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
|
|
303
|
+
fs.rmSync(entriesDir, { recursive: true, force: true })
|
|
304
|
+
if (pageFiles.length === 0) return
|
|
305
|
+
fs.mkdirSync(entriesDir, { recursive: true })
|
|
306
|
+
for (const f of pageFiles) {
|
|
307
|
+
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
308
|
+
const stubPath = path.join(entriesDir, `${pageId}.mjs`)
|
|
309
|
+
fs.writeFileSync(stubPath, generatePageSsrModule({ pageAbsPath: f, stubAbsPath: stubPath }))
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Compiles per-page SSR bundles to `build/ssr/<id>.mjs`. Each is self-contained with React bundled in. */
|
|
314
|
+
export async function compilePageSsrModules ({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }) {
|
|
315
|
+
const ssrDir = path.join(buildPath, OSSY_SSR_DIRNAME)
|
|
316
|
+
const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
|
|
317
|
+
fs.rmSync(ssrDir, { recursive: true, force: true })
|
|
318
|
+
if (pageFiles.length === 0) return []
|
|
319
|
+
fs.mkdirSync(ssrDir, { recursive: true })
|
|
320
|
+
const plugins = createOssySsrBundlePlugins({ nodeEnv })
|
|
321
|
+
const results = await Promise.all(
|
|
322
|
+
pageFiles.map(async (f) => {
|
|
323
|
+
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
324
|
+
const stubPath = path.join(entriesDir, `${pageId}.mjs`)
|
|
325
|
+
const outFile = path.join(ssrDir, `${pageId}.mjs`)
|
|
326
|
+
const bundle = await rollup({
|
|
327
|
+
input: stubPath,
|
|
328
|
+
plugins,
|
|
329
|
+
onwarn (warning, defaultHandler) {
|
|
330
|
+
if (onWarn) { onWarn(warning); return }
|
|
331
|
+
defaultHandler(warning)
|
|
332
|
+
},
|
|
333
|
+
})
|
|
334
|
+
await bundle.write({ file: outFile, format: 'esm', inlineDynamicImports: true })
|
|
335
|
+
await bundle.close()
|
|
336
|
+
return { id: pageId }
|
|
337
|
+
})
|
|
338
|
+
)
|
|
339
|
+
return results
|
|
340
|
+
}
|
|
341
|
+
|
|
238
342
|
/** Bundles a single Node ESM file (inline dynamic imports) for SSR / API / tasks. */
|
|
239
343
|
export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv, onWarn, external }) {
|
|
240
344
|
const bundle = await rollup({
|
|
@@ -455,41 +559,42 @@ export async function compileTaskServerModules ({ taskFiles, ossyDir, nodeEnv, o
|
|
|
455
559
|
|
|
456
560
|
/**
|
|
457
561
|
* Merges compiled `metadata` + `module` into `pages.generated.json` (same order as `pageBundleList`).
|
|
458
|
-
*
|
|
562
|
+
* Writes `module` on each route for Node SSR (`import()` of `page-modules/*.mjs`); hydrate uses a separate Rollup client entry.
|
|
459
563
|
*/
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}) {
|
|
465
|
-
if (!
|
|
564
|
+
/**
|
|
565
|
+
* Enriches `pages.generated.json` with `module` paths pointing to SSR bundles and merges
|
|
566
|
+
* any `metadata` exported by each page. Runs after SSR bundles are compiled.
|
|
567
|
+
*/
|
|
568
|
+
export async function enrichPagesGeneratedManifest ({ ossyDir, pagesGeneratedPath }) {
|
|
569
|
+
if (!fs.existsSync(pagesGeneratedPath)) return
|
|
466
570
|
const raw = JSON.parse(fs.readFileSync(pagesGeneratedPath, 'utf8'))
|
|
467
571
|
const basePages = raw?.pages
|
|
468
|
-
if (!Array.isArray(basePages) || basePages.length
|
|
469
|
-
|
|
470
|
-
'[@ossy/app] pages.generated.json page count must match compiled page modules (re-run build).'
|
|
471
|
-
)
|
|
472
|
-
}
|
|
572
|
+
if (!Array.isArray(basePages) || basePages.length === 0) return
|
|
573
|
+
|
|
473
574
|
const pages = []
|
|
474
|
-
for (
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
575
|
+
for (const basePage of basePages) {
|
|
576
|
+
const moduleRelPath = `../ssr/${basePage.id}.mjs`
|
|
577
|
+
const bundleAbs = path.join(ossyDir, moduleRelPath)
|
|
578
|
+
let meta = {}
|
|
579
|
+
if (fs.existsSync(bundleAbs)) {
|
|
580
|
+
try {
|
|
581
|
+
const mod = await import(pathToFileURL(bundleAbs).href)
|
|
582
|
+
meta = mod?.metadata && typeof mod.metadata === 'object' ? mod.metadata : {}
|
|
583
|
+
} catch {
|
|
584
|
+
// metadata unreadable — continue with defaults
|
|
585
|
+
}
|
|
586
|
+
}
|
|
479
587
|
const merged = {
|
|
480
|
-
|
|
588
|
+
id: basePage.id,
|
|
589
|
+
path: basePage.path,
|
|
481
590
|
...meta,
|
|
482
|
-
sourceFile:
|
|
483
|
-
module:
|
|
591
|
+
sourceFile: basePage.sourceFile,
|
|
592
|
+
module: moduleRelPath,
|
|
484
593
|
}
|
|
485
594
|
try {
|
|
486
595
|
JSON.stringify(merged)
|
|
487
596
|
} catch {
|
|
488
|
-
pages.push({
|
|
489
|
-
...derived,
|
|
490
|
-
sourceFile: basePages[i].sourceFile,
|
|
491
|
-
module: pageBundleList[i].module,
|
|
492
|
-
})
|
|
597
|
+
pages.push({ id: basePage.id, path: basePage.path, sourceFile: basePage.sourceFile, module: moduleRelPath })
|
|
493
598
|
continue
|
|
494
599
|
}
|
|
495
600
|
pages.push(merged)
|
|
@@ -498,26 +603,23 @@ export async function enrichPagesGeneratedManifest ({
|
|
|
498
603
|
}
|
|
499
604
|
|
|
500
605
|
/**
|
|
501
|
-
*
|
|
606
|
+
* Compiles server-side artifacts: per-page SSR bundles, API modules, and task modules.
|
|
502
607
|
*/
|
|
503
608
|
export async function compileOssyNodeArtifacts ({
|
|
504
609
|
pageFiles,
|
|
505
610
|
srcDir,
|
|
506
611
|
ossyDir,
|
|
612
|
+
buildPath,
|
|
507
613
|
apiFiles,
|
|
508
614
|
taskFiles,
|
|
509
615
|
nodeEnv,
|
|
510
616
|
onWarn,
|
|
511
617
|
}) {
|
|
512
|
-
const [
|
|
513
|
-
|
|
618
|
+
const [, apiModuleList, taskModuleList] = await Promise.all([
|
|
619
|
+
compilePageSsrModules({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }),
|
|
514
620
|
compileApiServerModules({ apiFiles, ossyDir, nodeEnv, onWarn }),
|
|
515
621
|
compileTaskServerModules({ taskFiles, ossyDir, nodeEnv, onWarn }),
|
|
516
622
|
])
|
|
517
|
-
writeOssyJson(path.join(ossyDir, OSSY_PAGES_BUNDLE_BASENAME), {
|
|
518
|
-
version: 1,
|
|
519
|
-
pages: pageBundleList,
|
|
520
|
-
})
|
|
521
623
|
writeOssyJson(path.join(ossyDir, OSSY_API_BUNDLE_BASENAME), {
|
|
522
624
|
version: 1,
|
|
523
625
|
modules: apiModuleList,
|
|
@@ -529,7 +631,6 @@ export async function compileOssyNodeArtifacts ({
|
|
|
529
631
|
await enrichPagesGeneratedManifest({
|
|
530
632
|
ossyDir,
|
|
531
633
|
pagesGeneratedPath: path.join(ossyDir, OSSY_GEN_PAGES_BASENAME),
|
|
532
|
-
pageBundleList,
|
|
533
634
|
})
|
|
534
635
|
}
|
|
535
636
|
|
|
@@ -561,7 +662,7 @@ export function clientHydrateIdForPage (pageAbsPath, srcDir) {
|
|
|
561
662
|
return idMatch ? idMatch[1] : derived.id
|
|
562
663
|
}
|
|
563
664
|
|
|
564
|
-
/** Posix path relative to `build/.ossy/` for the compiled
|
|
665
|
+
/** Posix path relative to `build/.ossy/` for the compiled **Node** page module (SSR). */
|
|
565
666
|
export function pageServerModuleRelPath (pageAbsPath, srcDir) {
|
|
566
667
|
const pageId = clientHydrateIdForPage(pageAbsPath, srcDir)
|
|
567
668
|
const safeId = String(pageId).replace(/[^a-zA-Z0-9_-]+/g, '-') || 'page'
|
|
@@ -600,35 +701,27 @@ export function writePageServerRollupEntry ({ pageAbsPath, stubPath }) {
|
|
|
600
701
|
}
|
|
601
702
|
|
|
602
703
|
/**
|
|
603
|
-
* One
|
|
704
|
+
* One bundle per page: exports the component (for server SSR import) and auto-hydrates in the browser.
|
|
705
|
+
* React is kept as a bare external import — resolved from node_modules on the server and via
|
|
706
|
+
* import map on the client.
|
|
604
707
|
*/
|
|
605
708
|
export function generatePageHydrateModule ({ pageAbsPath, stubAbsPath, srcDir }) {
|
|
606
|
-
|
|
607
|
-
const outName = path.posix.basename(pageServerModuleRelPath(pageAbsPath, srcDir))
|
|
608
|
-
const pageImportUrl = `${OSSY_PAGE_MODULE_WEB_PREFIX}/${outName}`
|
|
609
|
-
const pageImportLiteral = JSON.stringify(pageImportUrl)
|
|
709
|
+
const rel = relToGeneratedImport(stubAbsPath, pageAbsPath)
|
|
610
710
|
return [
|
|
611
711
|
'// Generated by @ossy/app — do not edit',
|
|
612
712
|
'',
|
|
613
713
|
"import React, { createElement } from 'react'",
|
|
614
|
-
"import 'react-dom'",
|
|
615
714
|
"import { hydrateRoot } from 'react-dom/client'",
|
|
715
|
+
`import * as _page from './${rel}'`,
|
|
616
716
|
'',
|
|
617
|
-
'
|
|
618
|
-
'
|
|
619
|
-
` const _page = await import(${pageImportLiteral})`,
|
|
620
|
-
' const Page = _page?.default',
|
|
621
|
-
' if (typeof Page !== \'function\') {',
|
|
622
|
-
' throw new Error(`[@ossy/app] Page must export default as a function component`)',
|
|
623
|
-
' }',
|
|
624
|
-
' const rootTree = createElement(Page, initialConfig)',
|
|
625
|
-
' hydrateRoot(document, rootTree)',
|
|
626
|
-
'}',
|
|
717
|
+
'export default _page.default',
|
|
718
|
+
'export const metadata = _page.metadata',
|
|
627
719
|
'',
|
|
628
|
-
'
|
|
629
|
-
|
|
630
|
-
"
|
|
631
|
-
|
|
720
|
+
"if (typeof window !== 'undefined') {",
|
|
721
|
+
" const Page = _page.default",
|
|
722
|
+
" const config = window.__INITIAL_APP_CONFIG__ || {}",
|
|
723
|
+
" hydrateRoot(document, createElement(Page, config))",
|
|
724
|
+
'}',
|
|
632
725
|
'',
|
|
633
726
|
].join('\n')
|
|
634
727
|
}
|
|
@@ -827,6 +920,7 @@ export const build = async (cliArgs) => {
|
|
|
827
920
|
pagesGeneratedPath,
|
|
828
921
|
})
|
|
829
922
|
writePageHydrateStubs(pageFiles, srcDir, ossyDir)
|
|
923
|
+
writePageSsrStubs(pageFiles, srcDir, ossyDir)
|
|
830
924
|
const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
|
|
831
925
|
|
|
832
926
|
const { apiOverviewFiles } = resolveApiSource({
|
|
@@ -844,8 +938,6 @@ export const build = async (cliArgs) => {
|
|
|
844
938
|
? configPath
|
|
845
939
|
: path.resolve(scriptDir, 'default-config.js')
|
|
846
940
|
|
|
847
|
-
const pagesEntryPath = path.join(ossyDir, OSSY_PAGES_RUNTIME_BASENAME)
|
|
848
|
-
|
|
849
941
|
const useDashboard = Object.keys(clientHydrateInput).length > 0
|
|
850
942
|
const overviewSnap = getBuildOverviewSnapshot({
|
|
851
943
|
pagesSourcePath: pagesGeneratedPath,
|
|
@@ -876,11 +968,6 @@ export const build = async (cliArgs) => {
|
|
|
876
968
|
},
|
|
877
969
|
})
|
|
878
970
|
dashboard.start()
|
|
879
|
-
if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
|
|
880
|
-
dashboard.pushLog(
|
|
881
|
-
`[resource-templates] merged ${resourceTemplatesResult.count} → ${path.relative(process.cwd(), resourceTemplatesResult.path)}`
|
|
882
|
-
)
|
|
883
|
-
}
|
|
884
971
|
} else {
|
|
885
972
|
console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
|
|
886
973
|
printBuildOverview({
|
|
@@ -889,28 +976,23 @@ export const build = async (cliArgs) => {
|
|
|
889
976
|
configPath,
|
|
890
977
|
pageFiles,
|
|
891
978
|
})
|
|
892
|
-
if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
|
|
893
|
-
console.log(
|
|
894
|
-
`[@ossy/app][resource-templates] merged ${resourceTemplatesResult.count} template(s) → ${path.relative(process.cwd(), resourceTemplatesResult.path)}`
|
|
895
|
-
)
|
|
896
|
-
}
|
|
897
979
|
}
|
|
898
980
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
981
|
+
if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
|
|
982
|
+
console.log(
|
|
983
|
+
`[@ossy/app][resource-templates] merged ${resourceTemplatesResult.count} template(s) → ${path.relative(process.cwd(), resourceTemplatesResult.path)}`
|
|
984
|
+
)
|
|
985
|
+
}
|
|
904
986
|
|
|
905
987
|
const { taskOverviewFiles } = resolveTaskSource({ srcDir, buildPath })
|
|
906
988
|
await compileOssyNodeArtifacts({
|
|
907
989
|
pageFiles,
|
|
908
990
|
srcDir,
|
|
909
991
|
ossyDir,
|
|
992
|
+
buildPath,
|
|
910
993
|
apiFiles: apiOverviewFiles,
|
|
911
994
|
taskFiles: taskOverviewFiles,
|
|
912
995
|
nodeEnv: 'production',
|
|
913
|
-
onWarn: warnSink,
|
|
914
996
|
})
|
|
915
997
|
|
|
916
998
|
writeAppRuntimeShims({
|
|
@@ -929,8 +1011,6 @@ export const build = async (cliArgs) => {
|
|
|
929
1011
|
copyPublicFrom: publicDir,
|
|
930
1012
|
buildPath,
|
|
931
1013
|
nodeEnv: 'production',
|
|
932
|
-
pagesEntryPath,
|
|
933
|
-
configSourcePath,
|
|
934
1014
|
createClientRollupPlugins: createOssyClientRollupPlugins,
|
|
935
1015
|
minifyBrowserStaticChunks,
|
|
936
1016
|
reporter: dashboard,
|
|
@@ -940,8 +1020,5 @@ export const build = async (cliArgs) => {
|
|
|
940
1020
|
}
|
|
941
1021
|
}
|
|
942
1022
|
|
|
943
|
-
if (useDashboard && dashboard) {
|
|
944
|
-
console.log(' \x1b[2m────────────────────────────────────────\x1b[0m')
|
|
945
|
-
}
|
|
946
1023
|
console.log(' \x1b[32m✔\x1b[0m \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild finished\x1b[0m\n')
|
|
947
1024
|
};
|
package/cli/index.js
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { build } from './build.js'
|
|
3
|
-
import { dev } from './dev.js'
|
|
4
3
|
|
|
5
4
|
const [,, command, ...restArgs] = process.argv
|
|
6
5
|
|
|
7
6
|
if (!command) {
|
|
8
7
|
console.error(
|
|
9
|
-
'[@ossy/app] No command provided. Usage: app
|
|
8
|
+
'[@ossy/app] No command provided. Usage: app build'
|
|
10
9
|
)
|
|
11
10
|
process.exit(1)
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
const run = async () => {
|
|
15
|
-
if (command === 'dev') {
|
|
16
|
-
await dev(restArgs)
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
14
|
if (command === 'build') {
|
|
20
15
|
await build(restArgs)
|
|
21
16
|
return
|