@ossy/app 1.11.1 → 1.11.2
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 +3 -3
- package/cli/api.runtime.mjs +22 -0
- package/cli/build.js +242 -166
- package/cli/dev.js +76 -100
- package/cli/pages.runtime.mjs +39 -0
- package/cli/prerender-react.task.js +6 -35
- package/cli/render-page.task.js +32 -0
- package/cli/server.js +51 -11
- package/cli/tasks.runtime.mjs +22 -0
- package/cli/worker-entry.js +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ export default () => <h1>About</h1>
|
|
|
23
23
|
|
|
24
24
|
For a single-file setup, use `src/pages.jsx` (legacy).
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
During `dev` / `build`, the tooling writes **JSON manifests** under **`build/.ossy/`**: **`pages.generated.json`** (route ids, default paths, and `sourceFile` paths), **`pages.bundle.json`** (compiled `page-modules/<id>.mjs` paths), plus the same pattern for API and tasks (`*.generated.json` / `*.bundle.json`). Small **`*.runtime.mjs`** loaders (copied from `@ossy/app`) dynamically `import()` those compiled modules at runtime.
|
|
27
27
|
|
|
28
28
|
**Client JS (per-page):** For each `*.page.jsx`, the build emits **`build/.ossy/hydrate-<pageId>.jsx`** → **`public/static/hydrate-<pageId>.js`**. The HTML for a request only loads the hydrate script for the **current** route (full document navigation), so other pages’ components are not part of that entry. React and shared dependencies still go into hashed shared chunks. The inline config (`window.__INITIAL_APP_CONFIG__`) no longer includes the full `pages` list—only request-time fields (theme, `apiUrl`, etc.).
|
|
29
29
|
|
|
@@ -51,7 +51,7 @@ Define HTTP handlers as an array of `{ id, path, handle(req, res) }` objects (sa
|
|
|
51
51
|
|
|
52
52
|
Add any number of `*.api.js` (or `.api.mjs` / `.api.cjs`) files under `src/` (nested dirs allowed). Each file’s **default export** is either one route object or an array of routes. Files are merged in lexicographic path order.
|
|
53
53
|
|
|
54
|
-
Build/dev
|
|
54
|
+
Build/dev writes **`build/.ossy/api.generated.json`** (discovered source paths) and **`api.bundle.json`** (Rollup outputs under **`api-modules/`**). The server imports **`api.runtime.mjs`**, which loads those modules. With no API files, both JSON files list empty arrays.
|
|
55
55
|
|
|
56
56
|
Example `src/health.api.js`:
|
|
57
57
|
|
|
@@ -69,7 +69,7 @@ API routes are matched before the app is rendered. The router supports dynamic s
|
|
|
69
69
|
|
|
70
70
|
## Background worker tasks (`*.task.js`)
|
|
71
71
|
|
|
72
|
-
For long-running job processors (separate from the SSR server), use **`npx @ossy/cli build --worker`** in a package that only needs the worker. It uses the same Rollup + Babel pipeline as `ossy build`, discovers **`*.task.js`** (and `.task.mjs` / `.task.cjs`) under `src/` (or `--pages <dir>`), and writes **`
|
|
72
|
+
For long-running job processors (separate from the SSR server), use **`npx @ossy/cli build --worker`** in a package that only needs the worker. It uses the same Rollup + Babel pipeline as `ossy build`, discovers **`*.task.js`** (and `.task.mjs` / `.task.cjs`) under `src/` (or `--pages <dir>`), and writes **`tasks.generated.json`** / **`tasks.bundle.json`** plus **`tasks.runtime.mjs`**—the same metadata + per-file compile pattern as API routes.
|
|
73
73
|
|
|
74
74
|
Optional legacy aggregate: **`src/tasks.js`** default export is merged **first**, then each `*.task.js` in path order.
|
|
75
75
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const __ossyDir = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
|
|
7
|
+
function normalizeApiExport (mod) {
|
|
8
|
+
const d = mod?.default
|
|
9
|
+
if (d == null) return []
|
|
10
|
+
return Array.isArray(d) ? d : [d]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { modules } = JSON.parse(fs.readFileSync(path.join(__ossyDir, 'api.bundle.json'), 'utf8'))
|
|
14
|
+
|
|
15
|
+
const out = []
|
|
16
|
+
for (const rel of modules) {
|
|
17
|
+
const abs = path.resolve(__ossyDir, rel)
|
|
18
|
+
const mod = await import(pathToFileURL(abs).href)
|
|
19
|
+
out.push(...normalizeApiExport(mod))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default out
|
package/cli/build.js
CHANGED
|
@@ -53,15 +53,35 @@ export function minifyBrowserStaticChunks () {
|
|
|
53
53
|
|
|
54
54
|
/** Subfolder under `build/` for generated pages/api/task entry stubs. */
|
|
55
55
|
export const OSSY_GEN_DIRNAME = '.ossy'
|
|
56
|
-
|
|
57
|
-
export const
|
|
58
|
-
export const
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
export const
|
|
62
|
-
export const
|
|
63
|
-
export const
|
|
56
|
+
/** JSON-only manifests (sources + route metadata). */
|
|
57
|
+
export const OSSY_GEN_PAGES_BASENAME = 'pages.generated.json'
|
|
58
|
+
export const OSSY_GEN_API_BASENAME = 'api.generated.json'
|
|
59
|
+
export const OSSY_GEN_TASKS_BASENAME = 'tasks.generated.json'
|
|
60
|
+
/** JSON-only build artifact index (compiled module paths, aligned to generated manifests). */
|
|
61
|
+
export const OSSY_PAGES_BUNDLE_BASENAME = 'pages.bundle.json'
|
|
62
|
+
export const OSSY_API_BUNDLE_BASENAME = 'api.bundle.json'
|
|
63
|
+
export const OSSY_TASKS_BUNDLE_BASENAME = 'tasks.bundle.json'
|
|
64
|
+
/** Small Node loaders (not JSON) that `import()` compiled modules from the bundle manifests. */
|
|
65
|
+
export const OSSY_PAGES_RUNTIME_BASENAME = 'pages.runtime.mjs'
|
|
66
|
+
export const OSSY_API_RUNTIME_BASENAME = 'api.runtime.mjs'
|
|
67
|
+
export const OSSY_TASKS_RUNTIME_BASENAME = 'tasks.runtime.mjs'
|
|
68
|
+
|
|
69
|
+
export const OSSY_PAGE_MODULES_DIRNAME = 'page-modules'
|
|
70
|
+
export const OSSY_API_MODULES_DIRNAME = 'api-modules'
|
|
71
|
+
export const OSSY_TASK_MODULES_DIRNAME = 'task-modules'
|
|
72
|
+
|
|
64
73
|
export const OSSY_MIDDLEWARE_RUNTIME_BASENAME = 'middleware.runtime.js'
|
|
74
|
+
export const OSSY_SERVER_CONFIG_RUNTIME_BASENAME = 'server-config.runtime.mjs'
|
|
75
|
+
export const OSSY_RENDER_PAGE_RUNTIME_BASENAME = 'render-page.task.js'
|
|
76
|
+
|
|
77
|
+
/** Keep React external across per-page server chunks so `pages.runtime.mjs` shares one React. */
|
|
78
|
+
export const OSSY_PAGE_SERVER_EXTERNAL = [
|
|
79
|
+
'react',
|
|
80
|
+
'react-dom',
|
|
81
|
+
'react-dom/static',
|
|
82
|
+
'react-dom/client',
|
|
83
|
+
'react/jsx-runtime',
|
|
84
|
+
]
|
|
65
85
|
|
|
66
86
|
/** Per-page client entries: `hydrate-<pageId>.jsx` under `.ossy/` */
|
|
67
87
|
const HYDRATE_STUB_PREFIX = 'hydrate-'
|
|
@@ -210,9 +230,10 @@ export function createOssyClientRollupPlugins ({ nodeEnv, copyPublicFrom, buildP
|
|
|
210
230
|
}
|
|
211
231
|
|
|
212
232
|
/** Bundles a single Node ESM file (inline dynamic imports) for SSR / API / tasks. */
|
|
213
|
-
export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv, onWarn }) {
|
|
233
|
+
export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv, onWarn, external }) {
|
|
214
234
|
const bundle = await rollup({
|
|
215
235
|
input: inputPath,
|
|
236
|
+
...(external && external.length ? { external } : {}),
|
|
216
237
|
plugins: createOssyAppBundlePlugins({ nodeEnv }),
|
|
217
238
|
onwarn (warning, defaultHandler) {
|
|
218
239
|
if (onWarn) {
|
|
@@ -231,14 +252,19 @@ export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv, onW
|
|
|
231
252
|
}
|
|
232
253
|
|
|
233
254
|
/**
|
|
234
|
-
* Re-exports middleware via `file:`
|
|
255
|
+
* Re-exports middleware and app config via `file:` URLs so `src/*.js` can keep relative imports.
|
|
235
256
|
*/
|
|
236
|
-
export function writeAppRuntimeShims ({ middlewareSourcePath, ossyDir }) {
|
|
257
|
+
export function writeAppRuntimeShims ({ middlewareSourcePath, configSourcePath, ossyDir }) {
|
|
237
258
|
const mwHref = url.pathToFileURL(path.resolve(middlewareSourcePath)).href
|
|
238
259
|
fs.writeFileSync(
|
|
239
260
|
path.join(ossyDir, OSSY_MIDDLEWARE_RUNTIME_BASENAME),
|
|
240
261
|
`// Generated by @ossy/app — do not edit\nexport { default } from '${mwHref}'\n`
|
|
241
262
|
)
|
|
263
|
+
const cfgHref = url.pathToFileURL(path.resolve(configSourcePath)).href
|
|
264
|
+
fs.writeFileSync(
|
|
265
|
+
path.join(ossyDir, OSSY_SERVER_CONFIG_RUNTIME_BASENAME),
|
|
266
|
+
`// Generated by @ossy/app — do not edit\nexport { default } from '${cfgHref}'\n`
|
|
267
|
+
)
|
|
242
268
|
}
|
|
243
269
|
|
|
244
270
|
/**
|
|
@@ -258,6 +284,18 @@ export function copyOssyAppRuntime ({ scriptDir, buildPath }) {
|
|
|
258
284
|
}
|
|
259
285
|
fs.copyFileSync(path.join(scriptDir, 'worker-entry.js'), path.join(buildPath, 'worker.js'))
|
|
260
286
|
fs.copyFileSync(path.join(scriptDir, 'worker-runtime.js'), path.join(buildPath, 'worker-runtime.js'))
|
|
287
|
+
const ossyOut = ossyGeneratedDir(buildPath)
|
|
288
|
+
for (const name of [
|
|
289
|
+
OSSY_PAGES_RUNTIME_BASENAME,
|
|
290
|
+
OSSY_API_RUNTIME_BASENAME,
|
|
291
|
+
OSSY_TASKS_RUNTIME_BASENAME,
|
|
292
|
+
]) {
|
|
293
|
+
fs.copyFileSync(path.join(scriptDir, name), path.join(ossyOut, name))
|
|
294
|
+
}
|
|
295
|
+
fs.copyFileSync(
|
|
296
|
+
path.join(scriptDir, OSSY_RENDER_PAGE_RUNTIME_BASENAME),
|
|
297
|
+
path.join(ossyOut, OSSY_RENDER_PAGE_RUNTIME_BASENAME)
|
|
298
|
+
)
|
|
261
299
|
}
|
|
262
300
|
|
|
263
301
|
/**
|
|
@@ -281,107 +319,157 @@ export function discoverFilesByPattern (srcDir, filePattern) {
|
|
|
281
319
|
return files.sort()
|
|
282
320
|
}
|
|
283
321
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
322
|
+
export function writeOssyJson (filePath, data) {
|
|
323
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
324
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** JSON manifest: discovered API source paths (posix, relative to `cwd`). */
|
|
328
|
+
export function buildApiManifestPayload (apiFiles, cwd = process.cwd()) {
|
|
329
|
+
return {
|
|
330
|
+
version: 1,
|
|
331
|
+
files: apiFiles.map((f) => path.relative(cwd, f).replace(/\\/g, '/')),
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** JSON manifest: discovered task source paths (posix, relative to `cwd`). */
|
|
336
|
+
export function buildTasksManifestPayload (taskFiles, cwd = process.cwd()) {
|
|
337
|
+
return {
|
|
338
|
+
version: 1,
|
|
339
|
+
files: taskFiles.map((f) => path.relative(cwd, f).replace(/\\/g, '/')),
|
|
296
340
|
}
|
|
297
|
-
const lines = [
|
|
298
|
-
'// Generated by @ossy/app — do not edit',
|
|
299
|
-
'',
|
|
300
|
-
]
|
|
301
|
-
apiFiles.forEach((f, i) => {
|
|
302
|
-
const rel = relToGeneratedImport(generatedPath, f)
|
|
303
|
-
lines.push(`import * as _api${i} from './${rel}'`)
|
|
304
|
-
})
|
|
305
|
-
lines.push(
|
|
306
|
-
'',
|
|
307
|
-
'function _normalizeApiExport(mod) {',
|
|
308
|
-
' const d = mod?.default',
|
|
309
|
-
' if (d == null) return []',
|
|
310
|
-
' return Array.isArray(d) ? d : [d]',
|
|
311
|
-
'}',
|
|
312
|
-
'',
|
|
313
|
-
'export default [',
|
|
314
|
-
)
|
|
315
|
-
const parts = apiFiles.map((_, i) => ` ..._normalizeApiExport(_api${i}),`)
|
|
316
|
-
lines.push(parts.join('\n'))
|
|
317
|
-
lines.push(']')
|
|
318
|
-
lines.push('')
|
|
319
|
-
return lines.join('\n')
|
|
320
341
|
}
|
|
321
342
|
|
|
322
343
|
/**
|
|
323
|
-
* Writes `build/.ossy/api.generated.
|
|
324
|
-
*
|
|
344
|
+
* Writes `build/.ossy/api.generated.json` (sources only).
|
|
345
|
+
* Compiled modules + `api.bundle.json` are produced by {@link compileOssyNodeArtifacts}.
|
|
325
346
|
*/
|
|
326
|
-
export function resolveApiSource ({ srcDir, buildPath }) {
|
|
347
|
+
export function resolveApiSource ({ srcDir, buildPath, cwd = process.cwd() }) {
|
|
327
348
|
ensureOssyGeneratedDir(buildPath)
|
|
328
349
|
const generatedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_API_BASENAME)
|
|
329
350
|
const apiFiles = discoverFilesByPattern(srcDir, API_FILE_PATTERN)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
generateApiModule({ generatedPath, apiFiles })
|
|
333
|
-
)
|
|
334
|
-
return { apiSourcePath: generatedPath, apiOverviewFiles: apiFiles }
|
|
351
|
+
writeOssyJson(generatedPath, buildApiManifestPayload(apiFiles, cwd))
|
|
352
|
+
return { apiGeneratedPath: generatedPath, apiOverviewFiles: apiFiles }
|
|
335
353
|
}
|
|
336
354
|
|
|
337
355
|
/**
|
|
338
|
-
*
|
|
339
|
-
* of job handlers `{ type, handler }` for the Ossy worker.
|
|
356
|
+
* Writes `build/.ossy/tasks.generated.json` (sources only).
|
|
340
357
|
*/
|
|
341
|
-
export function
|
|
342
|
-
const lines = [
|
|
343
|
-
'// Generated by @ossy/app — do not edit',
|
|
344
|
-
'',
|
|
345
|
-
]
|
|
346
|
-
taskFiles.forEach((f, i) => {
|
|
347
|
-
const rel = relToGeneratedImport(generatedPath, f)
|
|
348
|
-
lines.push(`import * as _task${i} from './${rel}'`)
|
|
349
|
-
})
|
|
350
|
-
lines.push(
|
|
351
|
-
'',
|
|
352
|
-
'function _normalizeTaskExport(mod) {',
|
|
353
|
-
' const d = mod?.default',
|
|
354
|
-
' if (d == null) return []',
|
|
355
|
-
' return Array.isArray(d) ? d : [d]',
|
|
356
|
-
'}',
|
|
357
|
-
'',
|
|
358
|
-
'export default [',
|
|
359
|
-
)
|
|
360
|
-
const parts = []
|
|
361
|
-
taskFiles.forEach((_, i) => {
|
|
362
|
-
parts.push(` ..._normalizeTaskExport(_task${i}),`)
|
|
363
|
-
})
|
|
364
|
-
lines.push(parts.join('\n'))
|
|
365
|
-
lines.push(']')
|
|
366
|
-
lines.push('')
|
|
367
|
-
return lines.join('\n')
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Writes `build/.ossy/tasks.generated.js` and returns its path (stable Rollup input; empty when no task files).
|
|
372
|
-
*/
|
|
373
|
-
export function resolveTaskSource ({ srcDir, buildPath }) {
|
|
358
|
+
export function resolveTaskSource ({ srcDir, buildPath, cwd = process.cwd() }) {
|
|
374
359
|
ensureOssyGeneratedDir(buildPath)
|
|
375
360
|
const generatedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_TASKS_BASENAME)
|
|
376
361
|
const taskFiles = discoverFilesByPattern(srcDir, TASK_FILE_PATTERN)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
362
|
+
writeOssyJson(generatedPath, buildTasksManifestPayload(taskFiles, cwd))
|
|
363
|
+
return { tasksGeneratedPath: generatedPath, taskOverviewFiles: taskFiles }
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export async function compilePageServerModules ({
|
|
367
|
+
pageFiles,
|
|
368
|
+
srcDir,
|
|
369
|
+
ossyDir,
|
|
370
|
+
nodeEnv,
|
|
371
|
+
onWarn,
|
|
372
|
+
}) {
|
|
373
|
+
const modsDir = path.join(ossyDir, OSSY_PAGE_MODULES_DIRNAME)
|
|
374
|
+
fs.rmSync(modsDir, { recursive: true, force: true })
|
|
375
|
+
if (pageFiles.length === 0) {
|
|
376
|
+
return []
|
|
377
|
+
}
|
|
378
|
+
fs.mkdirSync(modsDir, { recursive: true })
|
|
379
|
+
const bundlePages = []
|
|
380
|
+
for (const f of pageFiles) {
|
|
381
|
+
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
382
|
+
const safeId = String(pageId).replace(/[^a-zA-Z0-9_-]+/g, '-') || 'page'
|
|
383
|
+
const outName = `${safeId}.mjs`
|
|
384
|
+
const outFile = path.join(modsDir, outName)
|
|
385
|
+
await bundleOssyNodeEntry({
|
|
386
|
+
inputPath: f,
|
|
387
|
+
outputFile: outFile,
|
|
388
|
+
nodeEnv,
|
|
389
|
+
onWarn,
|
|
390
|
+
external: OSSY_PAGE_SERVER_EXTERNAL,
|
|
382
391
|
})
|
|
383
|
-
|
|
384
|
-
|
|
392
|
+
bundlePages.push({
|
|
393
|
+
id: pageId,
|
|
394
|
+
module: `${OSSY_PAGE_MODULES_DIRNAME}/${outName}`,
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
return bundlePages
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export async function compileApiServerModules ({ apiFiles, ossyDir, nodeEnv, onWarn }) {
|
|
401
|
+
const modsDir = path.join(ossyDir, OSSY_API_MODULES_DIRNAME)
|
|
402
|
+
fs.rmSync(modsDir, { recursive: true, force: true })
|
|
403
|
+
if (apiFiles.length === 0) {
|
|
404
|
+
return []
|
|
405
|
+
}
|
|
406
|
+
fs.mkdirSync(modsDir, { recursive: true })
|
|
407
|
+
const modules = []
|
|
408
|
+
for (let i = 0; i < apiFiles.length; i++) {
|
|
409
|
+
const outName = `api-${i}.mjs`
|
|
410
|
+
const outFile = path.join(modsDir, outName)
|
|
411
|
+
await bundleOssyNodeEntry({
|
|
412
|
+
inputPath: apiFiles[i],
|
|
413
|
+
outputFile: outFile,
|
|
414
|
+
nodeEnv,
|
|
415
|
+
onWarn,
|
|
416
|
+
})
|
|
417
|
+
modules.push(`${OSSY_API_MODULES_DIRNAME}/${outName}`)
|
|
418
|
+
}
|
|
419
|
+
return modules
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function compileTaskServerModules ({ taskFiles, ossyDir, nodeEnv, onWarn }) {
|
|
423
|
+
const modsDir = path.join(ossyDir, OSSY_TASK_MODULES_DIRNAME)
|
|
424
|
+
fs.rmSync(modsDir, { recursive: true, force: true })
|
|
425
|
+
if (taskFiles.length === 0) {
|
|
426
|
+
return []
|
|
427
|
+
}
|
|
428
|
+
fs.mkdirSync(modsDir, { recursive: true })
|
|
429
|
+
const modules = []
|
|
430
|
+
for (let i = 0; i < taskFiles.length; i++) {
|
|
431
|
+
const outName = `task-${i}.mjs`
|
|
432
|
+
const outFile = path.join(modsDir, outName)
|
|
433
|
+
await bundleOssyNodeEntry({
|
|
434
|
+
inputPath: taskFiles[i],
|
|
435
|
+
outputFile: outFile,
|
|
436
|
+
nodeEnv,
|
|
437
|
+
onWarn,
|
|
438
|
+
})
|
|
439
|
+
modules.push(`${OSSY_TASK_MODULES_DIRNAME}/${outName}`)
|
|
440
|
+
}
|
|
441
|
+
return modules
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Writes `pages.bundle.json`, `api.bundle.json`, `tasks.bundle.json` by Rollup-compiling each source module.
|
|
446
|
+
*/
|
|
447
|
+
export async function compileOssyNodeArtifacts ({
|
|
448
|
+
pageFiles,
|
|
449
|
+
srcDir,
|
|
450
|
+
ossyDir,
|
|
451
|
+
apiFiles,
|
|
452
|
+
taskFiles,
|
|
453
|
+
nodeEnv,
|
|
454
|
+
onWarn,
|
|
455
|
+
}) {
|
|
456
|
+
const [pageBundleList, apiModuleList, taskModuleList] = await Promise.all([
|
|
457
|
+
compilePageServerModules({ pageFiles, srcDir, ossyDir, nodeEnv, onWarn }),
|
|
458
|
+
compileApiServerModules({ apiFiles, ossyDir, nodeEnv, onWarn }),
|
|
459
|
+
compileTaskServerModules({ taskFiles, ossyDir, nodeEnv, onWarn }),
|
|
460
|
+
])
|
|
461
|
+
writeOssyJson(path.join(ossyDir, OSSY_PAGES_BUNDLE_BASENAME), {
|
|
462
|
+
version: 1,
|
|
463
|
+
pages: pageBundleList,
|
|
464
|
+
})
|
|
465
|
+
writeOssyJson(path.join(ossyDir, OSSY_API_BUNDLE_BASENAME), {
|
|
466
|
+
version: 1,
|
|
467
|
+
modules: apiModuleList,
|
|
468
|
+
})
|
|
469
|
+
writeOssyJson(path.join(ossyDir, OSSY_TASKS_BUNDLE_BASENAME), {
|
|
470
|
+
version: 1,
|
|
471
|
+
modules: taskModuleList,
|
|
472
|
+
})
|
|
385
473
|
}
|
|
386
474
|
|
|
387
475
|
export function filePathToRoute(filePath, srcDir) {
|
|
@@ -414,7 +502,7 @@ export function clientHydrateIdForPage (pageAbsPath, srcDir) {
|
|
|
414
502
|
|
|
415
503
|
/**
|
|
416
504
|
* One client entry per page: imports only that page module and hydrates the document.
|
|
417
|
-
* Keeps the same `toPage` shape as `
|
|
505
|
+
* Keeps the same `toPage` shape as `pages.runtime.mjs` + manifests so SSR and client trees match.
|
|
418
506
|
*/
|
|
419
507
|
export function generatePageHydrateModule ({ pageAbsPath, stubAbsPath, srcDir }) {
|
|
420
508
|
const rel = relToGeneratedImport(stubAbsPath, pageAbsPath)
|
|
@@ -491,32 +579,39 @@ export function buildClientHydrateInput (pageFiles, srcDir, ossyDir) {
|
|
|
491
579
|
return input
|
|
492
580
|
}
|
|
493
581
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
582
|
+
/** JSON manifest: route ids, default paths, and page source paths (posix, relative to `cwd`). */
|
|
583
|
+
export function buildPagesGeneratedPayload (pageFiles, srcDir, cwd = process.cwd()) {
|
|
584
|
+
const pages = pageFiles.map((f) => {
|
|
585
|
+
const { path: routePath } = filePathToRoute(f, srcDir)
|
|
586
|
+
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
587
|
+
return {
|
|
588
|
+
id: pageId,
|
|
589
|
+
path: routePath,
|
|
590
|
+
sourceFile: path.relative(cwd, f).replace(/\\/g, '/'),
|
|
591
|
+
}
|
|
592
|
+
})
|
|
593
|
+
return { version: 1, pages }
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export function writePagesManifest ({
|
|
597
|
+
pageFiles,
|
|
598
|
+
srcDir,
|
|
599
|
+
pagesGeneratedPath,
|
|
600
|
+
cwd = process.cwd(),
|
|
601
|
+
}) {
|
|
602
|
+
writeOssyJson(pagesGeneratedPath, buildPagesGeneratedPayload(pageFiles, srcDir, cwd))
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function parsePagesFromManifestJson (manifestPath) {
|
|
606
|
+
try {
|
|
607
|
+
const raw = fs.readFileSync(manifestPath, 'utf8')
|
|
608
|
+
const data = JSON.parse(raw)
|
|
609
|
+
const pages = data?.pages
|
|
610
|
+
if (!Array.isArray(pages)) return []
|
|
611
|
+
return pages.map((p) => ({ id: p.id, path: p.path }))
|
|
612
|
+
} catch {
|
|
613
|
+
return []
|
|
614
|
+
}
|
|
520
615
|
}
|
|
521
616
|
|
|
522
617
|
export function parsePagesFromSource(filePath) {
|
|
@@ -557,7 +652,6 @@ export function parsePagesFromSource(filePath) {
|
|
|
557
652
|
*/
|
|
558
653
|
export function getBuildOverviewSnapshot ({
|
|
559
654
|
pagesSourcePath,
|
|
560
|
-
apiSourcePath,
|
|
561
655
|
apiOverviewFiles = [],
|
|
562
656
|
configPath,
|
|
563
657
|
pageFiles,
|
|
@@ -567,17 +661,14 @@ export function getBuildOverviewSnapshot ({
|
|
|
567
661
|
const configRel = fs.existsSync(configPath) ? rel(configPath) : null
|
|
568
662
|
|
|
569
663
|
const pages = pageFiles?.length
|
|
570
|
-
? pageFiles.map((f) =>
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
: fs.existsSync(apiSourcePath)
|
|
577
|
-
? [apiSourcePath]
|
|
578
|
-
: []
|
|
664
|
+
? pageFiles.map((f) => {
|
|
665
|
+
const { id, path: routePath } = filePathToRoute(f, srcDir)
|
|
666
|
+
return { id: clientHydrateIdForPage(f, srcDir), path: routePath }
|
|
667
|
+
})
|
|
668
|
+
: parsePagesFromManifestJson(pagesSourcePath)
|
|
669
|
+
|
|
579
670
|
const apiRoutes = []
|
|
580
|
-
for (const f of
|
|
671
|
+
for (const f of apiOverviewFiles) {
|
|
581
672
|
if (fs.existsSync(f)) apiRoutes.push(...parsePagesFromSource(f))
|
|
582
673
|
}
|
|
583
674
|
|
|
@@ -633,24 +724,21 @@ export const build = async (cliArgs) => {
|
|
|
633
724
|
log: false,
|
|
634
725
|
})
|
|
635
726
|
|
|
636
|
-
const
|
|
727
|
+
const ossyDir = ossyGeneratedDir(buildPath)
|
|
728
|
+
const pagesGeneratedPath = path.join(ossyDir, OSSY_GEN_PAGES_BASENAME)
|
|
637
729
|
|
|
638
|
-
|
|
730
|
+
writePagesManifest({
|
|
731
|
+
pageFiles,
|
|
732
|
+
srcDir,
|
|
639
733
|
pagesGeneratedPath,
|
|
640
|
-
|
|
641
|
-
)
|
|
642
|
-
const ossyDir = ossyGeneratedDir(buildPath)
|
|
734
|
+
})
|
|
643
735
|
writePageHydrateStubs(pageFiles, srcDir, ossyDir)
|
|
644
736
|
const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
|
|
645
737
|
|
|
646
|
-
const {
|
|
647
|
-
apiSourcePath: resolvedApi,
|
|
648
|
-
apiOverviewFiles,
|
|
649
|
-
} = resolveApiSource({
|
|
738
|
+
const { apiOverviewFiles } = resolveApiSource({
|
|
650
739
|
srcDir,
|
|
651
740
|
buildPath,
|
|
652
741
|
})
|
|
653
|
-
let apiSourcePath = resolvedApi
|
|
654
742
|
let middlewareSourcePath = path.resolve('src/middleware.js');
|
|
655
743
|
const publicDir = path.resolve('public')
|
|
656
744
|
|
|
@@ -662,14 +750,11 @@ export const build = async (cliArgs) => {
|
|
|
662
750
|
? configPath
|
|
663
751
|
: path.resolve(scriptDir, 'default-config.js')
|
|
664
752
|
|
|
665
|
-
const
|
|
666
|
-
const apiBundlePath = path.join(ossyDir, OSSY_API_SERVER_BUNDLE)
|
|
667
|
-
const tasksBundlePath = path.join(ossyDir, OSSY_TASKS_SERVER_BUNDLE)
|
|
753
|
+
const pagesEntryPath = path.join(ossyDir, OSSY_PAGES_RUNTIME_BASENAME)
|
|
668
754
|
|
|
669
755
|
const useDashboard = Object.keys(clientHydrateInput).length > 0
|
|
670
756
|
const overviewSnap = getBuildOverviewSnapshot({
|
|
671
757
|
pagesSourcePath: pagesGeneratedPath,
|
|
672
|
-
apiSourcePath,
|
|
673
758
|
apiOverviewFiles,
|
|
674
759
|
configPath,
|
|
675
760
|
pageFiles,
|
|
@@ -706,7 +791,6 @@ export const build = async (cliArgs) => {
|
|
|
706
791
|
console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
|
|
707
792
|
printBuildOverview({
|
|
708
793
|
pagesSourcePath: pagesGeneratedPath,
|
|
709
|
-
apiSourcePath,
|
|
710
794
|
apiOverviewFiles,
|
|
711
795
|
configPath,
|
|
712
796
|
pageFiles,
|
|
@@ -724,28 +808,20 @@ export const build = async (cliArgs) => {
|
|
|
724
808
|
}
|
|
725
809
|
: undefined
|
|
726
810
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
inputPath: apiSourcePath,
|
|
735
|
-
outputFile: apiBundlePath,
|
|
736
|
-
nodeEnv: 'production',
|
|
737
|
-
onWarn: warnSink,
|
|
738
|
-
})
|
|
739
|
-
const { taskSourcePath } = resolveTaskSource({ srcDir, buildPath })
|
|
740
|
-
await bundleOssyNodeEntry({
|
|
741
|
-
inputPath: taskSourcePath,
|
|
742
|
-
outputFile: tasksBundlePath,
|
|
811
|
+
const { taskOverviewFiles } = resolveTaskSource({ srcDir, buildPath })
|
|
812
|
+
await compileOssyNodeArtifacts({
|
|
813
|
+
pageFiles,
|
|
814
|
+
srcDir,
|
|
815
|
+
ossyDir,
|
|
816
|
+
apiFiles: apiOverviewFiles,
|
|
817
|
+
taskFiles: taskOverviewFiles,
|
|
743
818
|
nodeEnv: 'production',
|
|
744
819
|
onWarn: warnSink,
|
|
745
820
|
})
|
|
746
821
|
|
|
747
822
|
writeAppRuntimeShims({
|
|
748
823
|
middlewareSourcePath,
|
|
824
|
+
configSourcePath,
|
|
749
825
|
ossyDir,
|
|
750
826
|
})
|
|
751
827
|
copyOssyAppRuntime({ scriptDir, buildPath })
|
|
@@ -759,7 +835,7 @@ export const build = async (cliArgs) => {
|
|
|
759
835
|
copyPublicFrom: publicDir,
|
|
760
836
|
buildPath,
|
|
761
837
|
nodeEnv: 'production',
|
|
762
|
-
|
|
838
|
+
pagesEntryPath,
|
|
763
839
|
configSourcePath,
|
|
764
840
|
createClientRollupPlugins: createOssyClientRollupPlugins,
|
|
765
841
|
minifyBrowserStaticChunks,
|
package/cli/dev.js
CHANGED
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
filePathToRoute,
|
|
8
8
|
discoverFilesByPattern,
|
|
9
9
|
PAGE_FILE_PATTERN,
|
|
10
|
-
|
|
10
|
+
writePagesManifest,
|
|
11
11
|
resolveApiSource,
|
|
12
12
|
resolveTaskSource,
|
|
13
13
|
resetOssyBuildDir,
|
|
14
|
-
|
|
14
|
+
compileOssyNodeArtifacts,
|
|
15
15
|
copyOssyAppRuntime,
|
|
16
16
|
writeAppRuntimeShims,
|
|
17
17
|
createOssyAppBundlePlugins,
|
|
@@ -21,11 +21,7 @@ import {
|
|
|
21
21
|
clientHydrateIdForPage,
|
|
22
22
|
ossyGeneratedDir,
|
|
23
23
|
OSSY_GEN_PAGES_BASENAME,
|
|
24
|
-
|
|
25
|
-
OSSY_GEN_TASKS_BASENAME,
|
|
26
|
-
OSSY_PAGES_PRERENDER_BUNDLE,
|
|
27
|
-
OSSY_API_SERVER_BUNDLE,
|
|
28
|
-
OSSY_TASKS_SERVER_BUNDLE,
|
|
24
|
+
OSSY_PAGES_RUNTIME_BASENAME,
|
|
29
25
|
writeResourceTemplatesBarrelIfPresent,
|
|
30
26
|
resourceTemplatesDir,
|
|
31
27
|
OSSY_RESOURCE_TEMPLATES_OUT,
|
|
@@ -48,7 +44,7 @@ export const dev = async (cliArgs) => {
|
|
|
48
44
|
const buildPath = path.resolve('build')
|
|
49
45
|
const srcDir = path.resolve('src')
|
|
50
46
|
const configPath = path.resolve(options['--config'] || 'src/config.js');
|
|
51
|
-
|
|
47
|
+
let currentPageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
|
|
52
48
|
|
|
53
49
|
resetOssyBuildDir(buildPath)
|
|
54
50
|
|
|
@@ -58,35 +54,33 @@ export const dev = async (cliArgs) => {
|
|
|
58
54
|
})
|
|
59
55
|
let resourceTemplatesDevLogged = false
|
|
60
56
|
|
|
61
|
-
const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
|
|
62
|
-
fs.writeFileSync(
|
|
63
|
-
pagesGeneratedPath,
|
|
64
|
-
generatePagesModule(pageFiles, srcDir, pagesGeneratedPath)
|
|
65
|
-
)
|
|
66
57
|
const ossyDir = ossyGeneratedDir(buildPath)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const {
|
|
71
|
-
apiSourcePath: resolvedApi,
|
|
72
|
-
apiOverviewFiles,
|
|
73
|
-
} = resolveApiSource({
|
|
58
|
+
const pagesGeneratedPath = path.join(ossyDir, OSSY_GEN_PAGES_BASENAME)
|
|
59
|
+
writePagesManifest({
|
|
60
|
+
pageFiles: currentPageFiles,
|
|
74
61
|
srcDir,
|
|
75
|
-
|
|
62
|
+
pagesGeneratedPath,
|
|
76
63
|
})
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
writePageHydrateStubs(currentPageFiles, srcDir, ossyDir)
|
|
65
|
+
const clientHydrateInput = buildClientHydrateInput(currentPageFiles, srcDir, ossyDir)
|
|
66
|
+
|
|
67
|
+
let apiOverviewFiles = []
|
|
68
|
+
let taskOverviewFiles = []
|
|
69
|
+
const refreshApiTaskManifests = () => {
|
|
70
|
+
apiOverviewFiles = resolveApiSource({ srcDir, buildPath }).apiOverviewFiles
|
|
71
|
+
taskOverviewFiles = resolveTaskSource({ srcDir, buildPath }).taskOverviewFiles
|
|
72
|
+
}
|
|
73
|
+
refreshApiTaskManifests()
|
|
79
74
|
let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/middleware.js');
|
|
80
75
|
const publicDir = path.resolve('public')
|
|
81
76
|
|
|
82
|
-
if (
|
|
77
|
+
if (currentPageFiles.length === 0) {
|
|
83
78
|
console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mdev\x1b[0m')
|
|
84
79
|
printBuildOverview({
|
|
85
80
|
pagesSourcePath: pagesGeneratedPath,
|
|
86
|
-
apiSourcePath,
|
|
87
81
|
apiOverviewFiles,
|
|
88
82
|
configPath,
|
|
89
|
-
pageFiles,
|
|
83
|
+
pageFiles: currentPageFiles,
|
|
90
84
|
})
|
|
91
85
|
if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
|
|
92
86
|
console.log(
|
|
@@ -103,29 +97,21 @@ export const dev = async (cliArgs) => {
|
|
|
103
97
|
? configPath
|
|
104
98
|
: path.resolve(scriptDir, 'default-config.js')
|
|
105
99
|
|
|
106
|
-
const
|
|
107
|
-
const apiBundlePath = path.join(ossyDir, OSSY_API_SERVER_BUNDLE)
|
|
108
|
-
const tasksBundlePath = path.join(ossyDir, OSSY_TASKS_SERVER_BUNDLE)
|
|
109
|
-
const tasksGeneratedPath = path.join(ossyDir, OSSY_GEN_TASKS_BASENAME)
|
|
100
|
+
const pagesEntryPath = path.join(ossyDir, OSSY_PAGES_RUNTIME_BASENAME)
|
|
110
101
|
|
|
111
102
|
const runNodeBundles = async () => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
outputFile: apiBundlePath,
|
|
120
|
-
nodeEnv: 'development',
|
|
121
|
-
})
|
|
122
|
-
await bundleOssyNodeEntry({
|
|
123
|
-
inputPath: tasksGeneratedPath,
|
|
124
|
-
outputFile: tasksBundlePath,
|
|
103
|
+
refreshApiTaskManifests()
|
|
104
|
+
await compileOssyNodeArtifacts({
|
|
105
|
+
pageFiles: currentPageFiles,
|
|
106
|
+
srcDir,
|
|
107
|
+
ossyDir,
|
|
108
|
+
apiFiles: apiOverviewFiles,
|
|
109
|
+
taskFiles: taskOverviewFiles,
|
|
125
110
|
nodeEnv: 'development',
|
|
126
111
|
})
|
|
127
112
|
writeAppRuntimeShims({
|
|
128
113
|
middlewareSourcePath,
|
|
114
|
+
configSourcePath,
|
|
129
115
|
ossyDir,
|
|
130
116
|
})
|
|
131
117
|
copyOssyAppRuntime({ scriptDir, buildPath })
|
|
@@ -194,38 +180,7 @@ export const dev = async (cliArgs) => {
|
|
|
194
180
|
}
|
|
195
181
|
|
|
196
182
|
const nodeWatchOpts = { watch: { clearScreen: false } }
|
|
197
|
-
const watchConfigs = [
|
|
198
|
-
{
|
|
199
|
-
input: pagesGeneratedPath,
|
|
200
|
-
output: {
|
|
201
|
-
file: pagesBundlePath,
|
|
202
|
-
format: 'esm',
|
|
203
|
-
inlineDynamicImports: true,
|
|
204
|
-
},
|
|
205
|
-
plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
|
|
206
|
-
...nodeWatchOpts,
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
input: apiSourcePath,
|
|
210
|
-
output: {
|
|
211
|
-
file: apiBundlePath,
|
|
212
|
-
format: 'esm',
|
|
213
|
-
inlineDynamicImports: true,
|
|
214
|
-
},
|
|
215
|
-
plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
|
|
216
|
-
...nodeWatchOpts,
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
input: tasksGeneratedPath,
|
|
220
|
-
output: {
|
|
221
|
-
file: tasksBundlePath,
|
|
222
|
-
format: 'esm',
|
|
223
|
-
inlineDynamicImports: true,
|
|
224
|
-
},
|
|
225
|
-
plugins: createOssyAppBundlePlugins({ nodeEnv: 'development' }),
|
|
226
|
-
...nodeWatchOpts,
|
|
227
|
-
},
|
|
228
|
-
]
|
|
183
|
+
const watchConfigs = []
|
|
229
184
|
if (Object.keys(clientHydrateInput).length > 0) {
|
|
230
185
|
watchConfigs.push({
|
|
231
186
|
input: clientHydrateInput,
|
|
@@ -233,6 +188,30 @@ export const dev = async (cliArgs) => {
|
|
|
233
188
|
plugins: clientPlugins,
|
|
234
189
|
watch: { clearScreen: false },
|
|
235
190
|
})
|
|
191
|
+
} else {
|
|
192
|
+
watchConfigs.push({
|
|
193
|
+
input: '\0ossy-dev-noop',
|
|
194
|
+
output: {
|
|
195
|
+
file: path.join(ossyDir, '.dev-noop-out.mjs'),
|
|
196
|
+
format: 'esm',
|
|
197
|
+
inlineDynamicImports: true,
|
|
198
|
+
},
|
|
199
|
+
plugins: [
|
|
200
|
+
{
|
|
201
|
+
name: 'ossy-dev-noop',
|
|
202
|
+
resolveId (id) {
|
|
203
|
+
if (id === '\0ossy-dev-noop') return id
|
|
204
|
+
return null
|
|
205
|
+
},
|
|
206
|
+
load (id) {
|
|
207
|
+
if (id === '\0ossy-dev-noop') return 'export default 0\n'
|
|
208
|
+
return null
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
...createOssyAppBundlePlugins({ nodeEnv: 'development' }),
|
|
212
|
+
],
|
|
213
|
+
...nodeWatchOpts,
|
|
214
|
+
})
|
|
236
215
|
}
|
|
237
216
|
const watcher = watch(watchConfigs)
|
|
238
217
|
|
|
@@ -251,22 +230,22 @@ export const dev = async (cliArgs) => {
|
|
|
251
230
|
if (event.code === 'END') {
|
|
252
231
|
writeAppRuntimeShims({
|
|
253
232
|
middlewareSourcePath,
|
|
233
|
+
configSourcePath,
|
|
254
234
|
ossyDir,
|
|
255
235
|
})
|
|
256
236
|
copyOssyAppRuntime({ scriptDir, buildPath })
|
|
257
|
-
if (
|
|
237
|
+
if (currentPageFiles.length > 0) {
|
|
258
238
|
const pageIds = [
|
|
259
|
-
...new Set(
|
|
239
|
+
...new Set(currentPageFiles.map((f) => clientHydrateIdForPage(f, srcDir))),
|
|
260
240
|
].sort()
|
|
261
241
|
const overviewSnap = getBuildOverviewSnapshot({
|
|
262
242
|
pagesSourcePath: pagesGeneratedPath,
|
|
263
|
-
apiSourcePath,
|
|
264
243
|
apiOverviewFiles,
|
|
265
244
|
configPath,
|
|
266
|
-
pageFiles,
|
|
245
|
+
pageFiles: currentPageFiles,
|
|
267
246
|
})
|
|
268
247
|
const idToPath = Object.fromEntries(
|
|
269
|
-
|
|
248
|
+
currentPageFiles.map((f) => [
|
|
270
249
|
clientHydrateIdForPage(f, srcDir),
|
|
271
250
|
filePathToRoute(f, srcDir).path,
|
|
272
251
|
])
|
|
@@ -298,7 +277,7 @@ export const dev = async (cliArgs) => {
|
|
|
298
277
|
try {
|
|
299
278
|
await prerenderReactTask.handler({
|
|
300
279
|
op: 'prerenderPagesParallel',
|
|
301
|
-
|
|
280
|
+
pagesEntryPath,
|
|
302
281
|
configSourcePath,
|
|
303
282
|
publicDir: path.join(buildPath, 'public'),
|
|
304
283
|
reporter,
|
|
@@ -315,37 +294,34 @@ export const dev = async (cliArgs) => {
|
|
|
315
294
|
|
|
316
295
|
const regenApiGenerated = () => {
|
|
317
296
|
if (options['--api-source']) return
|
|
318
|
-
|
|
319
|
-
srcDir,
|
|
320
|
-
buildPath,
|
|
321
|
-
})
|
|
322
|
-
const gen = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_API_BASENAME)
|
|
323
|
-
if (fs.existsSync(gen) && typeof watcher?.invalidate === 'function') {
|
|
324
|
-
watcher.invalidate(gen)
|
|
325
|
-
}
|
|
297
|
+
void runNodeBundles().then(() => scheduleRestart())
|
|
326
298
|
}
|
|
327
299
|
|
|
328
300
|
const regenTasksGenerated = () => {
|
|
329
|
-
|
|
330
|
-
if (fs.existsSync(tasksGeneratedPath) && typeof watcher?.invalidate === 'function') {
|
|
331
|
-
watcher.invalidate(tasksGeneratedPath)
|
|
332
|
-
}
|
|
301
|
+
void runNodeBundles().then(() => scheduleRestart())
|
|
333
302
|
}
|
|
334
303
|
|
|
335
304
|
fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
|
|
336
305
|
if (!filename) return
|
|
337
306
|
if (/\.page\.(jsx?|tsx?)$/.test(filename)) {
|
|
338
307
|
const refreshedPageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
|
|
308
|
+
currentPageFiles = refreshedPageFiles
|
|
339
309
|
const regenPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
|
|
340
|
-
|
|
310
|
+
writePagesManifest({
|
|
311
|
+
pageFiles: refreshedPageFiles,
|
|
312
|
+
srcDir,
|
|
313
|
+
pagesGeneratedPath: regenPath,
|
|
314
|
+
})
|
|
341
315
|
writePageHydrateStubs(refreshedPageFiles, srcDir, ossyDir)
|
|
342
|
-
|
|
343
|
-
watcher
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
316
|
+
void runNodeBundles().then(() => {
|
|
317
|
+
if (typeof watcher?.invalidate === 'function') {
|
|
318
|
+
for (const f of refreshedPageFiles) {
|
|
319
|
+
const hid = clientHydrateIdForPage(f, srcDir)
|
|
320
|
+
watcher.invalidate(path.join(ossyDir, `hydrate-${hid}.jsx`))
|
|
321
|
+
}
|
|
347
322
|
}
|
|
348
|
-
|
|
323
|
+
scheduleRestart()
|
|
324
|
+
})
|
|
349
325
|
}
|
|
350
326
|
if (/\.api\.(mjs|cjs|js)$/.test(filename)) {
|
|
351
327
|
regenApiGenerated()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
|
|
6
|
+
const __ossyDir = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
|
|
8
|
+
function readJson (name) {
|
|
9
|
+
return JSON.parse(fs.readFileSync(path.join(__ossyDir, name), 'utf8'))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function toPage (mod, derived) {
|
|
13
|
+
const meta = mod?.metadata || {}
|
|
14
|
+
const def = mod?.default
|
|
15
|
+
if (typeof def === 'function') {
|
|
16
|
+
return { ...derived, ...meta, element: React.createElement(def) }
|
|
17
|
+
}
|
|
18
|
+
return { ...derived, ...meta, ...(def || {}) }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { pages: metaPages } = readJson('pages.generated.json')
|
|
22
|
+
const { pages: bundlePages } = readJson('pages.bundle.json')
|
|
23
|
+
|
|
24
|
+
if (metaPages.length !== bundlePages.length) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'[@ossy/app][pages.runtime] pages.generated.json and pages.bundle.json must list the same number of pages'
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const out = []
|
|
31
|
+
for (let i = 0; i < metaPages.length; i++) {
|
|
32
|
+
const derived = { id: metaPages[i].id, path: metaPages[i].path }
|
|
33
|
+
const rel = bundlePages[i].module
|
|
34
|
+
const abs = path.resolve(__ossyDir, rel)
|
|
35
|
+
const mod = await import(pathToFileURL(abs).href)
|
|
36
|
+
out.push(toPage(mod, derived))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default out
|
|
@@ -2,7 +2,7 @@ import path from 'path'
|
|
|
2
2
|
import url from 'url'
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import { rollup } from 'rollup'
|
|
5
|
-
import { BuildPage } from './render-page.task.js'
|
|
5
|
+
import { BuildPage, buildPrerenderAppConfig } from './render-page.task.js'
|
|
6
6
|
import { pageIdFromHydrateEntryName } from './build-terminal.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -24,35 +24,6 @@ function pathIsPrerenderable (routePath) {
|
|
|
24
24
|
return true
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/** Mirrors server-era app shell config with static defaults (no cookies / session). */
|
|
28
|
-
export function buildPrerenderAppConfig ({
|
|
29
|
-
buildTimeConfig,
|
|
30
|
-
pageList,
|
|
31
|
-
activeRouteId,
|
|
32
|
-
urlPath,
|
|
33
|
-
}) {
|
|
34
|
-
const pages = pageList.map((page) => {
|
|
35
|
-
const entry = {
|
|
36
|
-
id: page?.id,
|
|
37
|
-
path: page?.path,
|
|
38
|
-
}
|
|
39
|
-
if (activeRouteId != null && page?.id === activeRouteId) {
|
|
40
|
-
entry.element = page?.element
|
|
41
|
-
}
|
|
42
|
-
return entry
|
|
43
|
-
})
|
|
44
|
-
return {
|
|
45
|
-
...buildTimeConfig,
|
|
46
|
-
url: urlPath,
|
|
47
|
-
theme: buildTimeConfig.theme || 'light',
|
|
48
|
-
isAuthenticated: false,
|
|
49
|
-
workspaceId: buildTimeConfig.workspaceId,
|
|
50
|
-
apiUrl: buildTimeConfig.apiUrl,
|
|
51
|
-
pages,
|
|
52
|
-
sidebarPrimaryCollapsed: false,
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
27
|
function copyPublicToBuild ({ copyPublicFrom, buildPath }) {
|
|
57
28
|
if (!copyPublicFrom || !fs.existsSync(copyPublicFrom)) return
|
|
58
29
|
const dest = path.join(buildPath, 'public')
|
|
@@ -170,13 +141,13 @@ async function prerenderOnePage ({
|
|
|
170
141
|
}
|
|
171
142
|
|
|
172
143
|
async function prerenderPagesParallel ({
|
|
173
|
-
|
|
144
|
+
pagesEntryPath,
|
|
174
145
|
configSourcePath,
|
|
175
146
|
publicDir,
|
|
176
147
|
reporter,
|
|
177
148
|
}) {
|
|
178
149
|
const cfgHref = url.pathToFileURL(path.resolve(configSourcePath)).href
|
|
179
|
-
const pagesHref = url.pathToFileURL(path.resolve(
|
|
150
|
+
const pagesHref = url.pathToFileURL(path.resolve(pagesEntryPath)).href
|
|
180
151
|
|
|
181
152
|
const configModule = await import(cfgHref)
|
|
182
153
|
const pagesModule = await import(pagesHref)
|
|
@@ -237,7 +208,7 @@ async function runProduction ({
|
|
|
237
208
|
copyPublicFrom,
|
|
238
209
|
buildPath,
|
|
239
210
|
nodeEnv,
|
|
240
|
-
|
|
211
|
+
pagesEntryPath,
|
|
241
212
|
configSourcePath,
|
|
242
213
|
createClientRollupPlugins,
|
|
243
214
|
minifyBrowserStaticChunks,
|
|
@@ -259,7 +230,7 @@ async function runProduction ({
|
|
|
259
230
|
})
|
|
260
231
|
|
|
261
232
|
const { failures: prerenderFailures } = await prerenderPagesParallel({
|
|
262
|
-
|
|
233
|
+
pagesEntryPath,
|
|
263
234
|
configSourcePath,
|
|
264
235
|
publicDir: path.join(buildPath, 'public'),
|
|
265
236
|
reporter,
|
|
@@ -284,7 +255,7 @@ export default {
|
|
|
284
255
|
}
|
|
285
256
|
if (op === 'prerenderPagesParallel') {
|
|
286
257
|
return prerenderPagesParallel({
|
|
287
|
-
|
|
258
|
+
pagesEntryPath: input.pagesEntryPath,
|
|
288
259
|
configSourcePath: input.configSourcePath,
|
|
289
260
|
publicDir: input.publicDir,
|
|
290
261
|
reporter: input.reporter,
|
package/cli/render-page.task.js
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import React, { cloneElement } from 'react'
|
|
2
2
|
import { prerenderToNodeStream } from 'react-dom/static'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* App shell config for SSR / prerender (mirrors client: theme, pages metadata, active route element).
|
|
6
|
+
*/
|
|
7
|
+
export function buildPrerenderAppConfig ({
|
|
8
|
+
buildTimeConfig,
|
|
9
|
+
pageList,
|
|
10
|
+
activeRouteId,
|
|
11
|
+
urlPath,
|
|
12
|
+
isAuthenticated = false,
|
|
13
|
+
}) {
|
|
14
|
+
const pages = pageList.map((page) => {
|
|
15
|
+
const entry = {
|
|
16
|
+
id: page?.id,
|
|
17
|
+
path: page?.path,
|
|
18
|
+
}
|
|
19
|
+
if (activeRouteId != null && page?.id === activeRouteId) {
|
|
20
|
+
entry.element = page?.element
|
|
21
|
+
}
|
|
22
|
+
return entry
|
|
23
|
+
})
|
|
24
|
+
return {
|
|
25
|
+
...buildTimeConfig,
|
|
26
|
+
url: urlPath,
|
|
27
|
+
theme: buildTimeConfig.theme || 'light',
|
|
28
|
+
isAuthenticated,
|
|
29
|
+
workspaceId: buildTimeConfig.workspaceId,
|
|
30
|
+
apiUrl: buildTimeConfig.apiUrl,
|
|
31
|
+
pages,
|
|
32
|
+
sidebarPrimaryCollapsed: false,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
4
36
|
/** Strips non-JSON content (e.g. React elements on `pages`) for the bootstrap script. */
|
|
5
37
|
export function appConfigForBootstrap (appConfig) {
|
|
6
38
|
if (!appConfig || typeof appConfig !== 'object') return appConfig
|
package/cli/server.js
CHANGED
|
@@ -6,12 +6,17 @@ import { Router as OssyRouter } from '@ossy/router'
|
|
|
6
6
|
import { ProxyInternal } from './proxy-internal.js'
|
|
7
7
|
import cookieParser from 'cookie-parser'
|
|
8
8
|
|
|
9
|
-
import ApiRoutes from './.ossy/api.
|
|
9
|
+
import ApiRoutes from './.ossy/api.runtime.mjs'
|
|
10
|
+
import pageRoutes from './.ossy/pages.runtime.mjs'
|
|
11
|
+
import buildTimeConfig from './.ossy/server-config.runtime.mjs'
|
|
12
|
+
import { BuildPage, buildPrerenderAppConfig } from './.ossy/render-page.task.js'
|
|
10
13
|
import Middleware from './.ossy/middleware.runtime.js'
|
|
11
14
|
|
|
12
15
|
/** API bundle default may be an empty array. */
|
|
13
16
|
const apiRouteList = ApiRoutes ?? []
|
|
14
17
|
|
|
18
|
+
const sitePageList = Array.isArray(pageRoutes) ? pageRoutes : []
|
|
19
|
+
|
|
15
20
|
const app = express();
|
|
16
21
|
|
|
17
22
|
const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
@@ -101,22 +106,57 @@ const middleware = [
|
|
|
101
106
|
|
|
102
107
|
app.use(middleware)
|
|
103
108
|
|
|
104
|
-
const
|
|
109
|
+
const apiRouter = OssyRouter.of({
|
|
105
110
|
pages: apiRouteList,
|
|
106
111
|
})
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
const pageRouter = OssyRouter.of({
|
|
114
|
+
pages: sitePageList,
|
|
115
|
+
defaultLanguage: buildTimeConfig.defaultLanguage,
|
|
116
|
+
supportedLanguages: buildTimeConfig.supportedLanguages || [],
|
|
117
|
+
})
|
|
110
118
|
|
|
111
|
-
|
|
119
|
+
app.all('*all', async (req, res) => {
|
|
120
|
+
const requestUrl = req.originalUrl || '/'
|
|
112
121
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
try {
|
|
123
|
+
const apiRoute = apiRouter.getPageByUrl(requestUrl)
|
|
124
|
+
if (apiRoute && typeof apiRoute.handle === 'function') {
|
|
125
|
+
console.log(`[@ossy/app][server] Handling API route: ${requestUrl}`)
|
|
126
|
+
apiRoute.handle(req, res)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
131
|
+
res.status(404).send('Not found')
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const pageRoute = pageRouter.getPageByUrl(requestUrl)
|
|
136
|
+
if (pageRoute?.element) {
|
|
137
|
+
const appConfig = buildPrerenderAppConfig({
|
|
138
|
+
buildTimeConfig,
|
|
139
|
+
pageList: sitePageList,
|
|
140
|
+
activeRouteId: pageRoute.id,
|
|
141
|
+
urlPath: requestUrl,
|
|
142
|
+
isAuthenticated: !!req.isAuthenticated,
|
|
143
|
+
})
|
|
144
|
+
const html = await BuildPage.handle({
|
|
145
|
+
route: pageRoute,
|
|
146
|
+
appConfig,
|
|
147
|
+
isDevReloadEnabled,
|
|
148
|
+
})
|
|
149
|
+
res.status(200).type('html').send(html)
|
|
150
|
+
return
|
|
151
|
+
}
|
|
118
152
|
|
|
119
|
-
|
|
153
|
+
res.status(404).send('Not found')
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error('[@ossy/app][server] Request handling failed:', err)
|
|
156
|
+
if (!res.headersSent) {
|
|
157
|
+
res.status(500).type('text').send('Internal Server Error')
|
|
158
|
+
}
|
|
159
|
+
}
|
|
120
160
|
})
|
|
121
161
|
|
|
122
162
|
app.listen(port, () => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const __ossyDir = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
|
|
7
|
+
function normalizeTaskExport (mod) {
|
|
8
|
+
const d = mod?.default
|
|
9
|
+
if (d == null) return []
|
|
10
|
+
return Array.isArray(d) ? d : [d]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { modules } = JSON.parse(fs.readFileSync(path.join(__ossyDir, 'tasks.bundle.json'), 'utf8'))
|
|
14
|
+
|
|
15
|
+
const out = []
|
|
16
|
+
for (const rel of modules) {
|
|
17
|
+
const abs = path.resolve(__ossyDir, rel)
|
|
18
|
+
const mod = await import(pathToFileURL(abs).href)
|
|
19
|
+
out.push(...normalizeTaskExport(mod))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default out
|
package/cli/worker-entry.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2",
|
|
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.11.
|
|
31
|
-
"@ossy/design-system": "^1.11.
|
|
32
|
-
"@ossy/pages": "^1.11.
|
|
33
|
-
"@ossy/router": "^1.11.
|
|
34
|
-
"@ossy/router-react": "^1.11.
|
|
35
|
-
"@ossy/sdk": "^1.11.
|
|
36
|
-
"@ossy/sdk-react": "^1.11.
|
|
37
|
-
"@ossy/themes": "^1.11.
|
|
30
|
+
"@ossy/connected-components": "^1.11.2",
|
|
31
|
+
"@ossy/design-system": "^1.11.2",
|
|
32
|
+
"@ossy/pages": "^1.11.2",
|
|
33
|
+
"@ossy/router": "^1.11.2",
|
|
34
|
+
"@ossy/router-react": "^1.11.2",
|
|
35
|
+
"@ossy/sdk": "^1.11.2",
|
|
36
|
+
"@ossy/sdk-react": "^1.11.2",
|
|
37
|
+
"@ossy/themes": "^1.11.2",
|
|
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": "
|
|
70
|
+
"gitHead": "3998c8b461e8b7317462c3d1d65b25b9454ba18a"
|
|
71
71
|
}
|