@ossy/app 1.13.3 → 1.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/build.js +194 -262
- package/cli/prerender-react.task.js +1 -90
- package/cli/render-page.task.js +3 -25
- package/cli/server.js +1 -1
- package/package.json +10 -10
- package/src/index.js +0 -1
package/cli/build.js
CHANGED
|
@@ -12,8 +12,6 @@ import nodeExternals from 'rollup-plugin-node-externals'
|
|
|
12
12
|
import copy from 'rollup-plugin-copy';
|
|
13
13
|
import replace from '@rollup/plugin-replace';
|
|
14
14
|
import arg from 'arg'
|
|
15
|
-
import prerenderReactTask from './prerender-react.task.js'
|
|
16
|
-
import { createBuildDashboard } from './build-terminal.js'
|
|
17
15
|
|
|
18
16
|
export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
|
|
19
17
|
export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
|
|
@@ -23,15 +21,12 @@ const RESOURCE_TEMPLATE_FILE_PATTERN = /\.resource\.js$/
|
|
|
23
21
|
/** Written next to `*.resource.js` under `src/resource-templates/` when that dir exists. */
|
|
24
22
|
export const OSSY_RESOURCE_TEMPLATES_OUT = '.ossy-system-templates.generated.js'
|
|
25
23
|
|
|
26
|
-
/** Rollup output paths when `output.dir` is `<build>/public` (see `entryFileNames` / `chunkFileNames`). */
|
|
27
|
-
const BROWSER_STATIC_PREFIX = 'static/'
|
|
28
|
-
|
|
29
24
|
export function minifyBrowserStaticChunks () {
|
|
30
25
|
return {
|
|
31
26
|
name: 'minify-browser-static-chunks',
|
|
32
27
|
async renderChunk (code, chunk, outputOptions) {
|
|
33
28
|
const fileName = chunk.fileName
|
|
34
|
-
if (!fileName || !fileName.startsWith(
|
|
29
|
+
if (!fileName || !fileName.startsWith('public/static/')) {
|
|
35
30
|
return null
|
|
36
31
|
}
|
|
37
32
|
const useSourceMap =
|
|
@@ -87,13 +82,13 @@ export const OSSY_PAGE_SERVER_EXTERNAL = [
|
|
|
87
82
|
'react/jsx-runtime',
|
|
88
83
|
]
|
|
89
84
|
|
|
90
|
-
/** Output directory (relative to buildPath) for
|
|
85
|
+
/** Output directory (relative to buildPath) for SSR bundle. */
|
|
91
86
|
export const OSSY_SSR_DIRNAME = 'ssr'
|
|
92
|
-
/** Temp stub entries for SSR bundles (inside .ossy/). */
|
|
93
|
-
const OSSY_SSR_ENTRIES_DIRNAME = 'ssr-entries'
|
|
94
87
|
|
|
95
|
-
/**
|
|
88
|
+
/** Shared client hydrate entry filename under `.ossy/` */
|
|
96
89
|
const HYDRATE_ENTRY_FILENAME = 'hydrate-entry.jsx'
|
|
90
|
+
/** Shared SSR entry filename under `.ossy/` */
|
|
91
|
+
const SSR_ENTRY_FILENAME = 'ssr-entry.mjs'
|
|
97
92
|
|
|
98
93
|
export function ossyGeneratedDir (buildPath) {
|
|
99
94
|
return path.join(buildPath, OSSY_GEN_DIRNAME)
|
|
@@ -199,45 +194,12 @@ export function createOssyAppBundlePlugins ({ nodeEnv }) {
|
|
|
199
194
|
}
|
|
200
195
|
|
|
201
196
|
/**
|
|
202
|
-
* Rollup plugins for
|
|
203
|
-
* is
|
|
197
|
+
* Rollup plugins for the combined SSR + client bundle.
|
|
198
|
+
* `preferBuiltins: true` is correct for the SSR entry (uses `node:stream`); page component code
|
|
199
|
+
* does not import Node built-ins so this is safe for the browser entry as well.
|
|
200
|
+
* `copyPublicFrom` is handled separately before the Rollup call.
|
|
204
201
|
*/
|
|
205
|
-
export function
|
|
206
|
-
const plugins = [
|
|
207
|
-
replace({
|
|
208
|
-
preventAssignment: true,
|
|
209
|
-
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
210
|
-
}),
|
|
211
|
-
json(),
|
|
212
|
-
nodeExternals({
|
|
213
|
-
deps: false,
|
|
214
|
-
devDeps: true,
|
|
215
|
-
peerDeps: false,
|
|
216
|
-
packagePath: path.join(process.cwd(), 'package.json'),
|
|
217
|
-
}),
|
|
218
|
-
resolveCommonJsDependencies(),
|
|
219
|
-
resolveDependencies({ preferBuiltins: false }),
|
|
220
|
-
babel({
|
|
221
|
-
babelHelpers: 'bundled',
|
|
222
|
-
extensions: ['.jsx', '.tsx'],
|
|
223
|
-
presets: [['@babel/preset-react', { runtime: 'automatic' }]],
|
|
224
|
-
}),
|
|
225
|
-
]
|
|
226
|
-
if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
|
|
227
|
-
plugins.push(
|
|
228
|
-
copy({
|
|
229
|
-
targets: [{ src: `${copyPublicFrom}/**/*`, dest: path.join(buildPath, 'public') }],
|
|
230
|
-
})
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
return plugins
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Rollup plugins for per-page SSR bundles: server-side resolution, React and all deps bundled in
|
|
238
|
-
* so the output is self-contained (no node_modules needed at runtime).
|
|
239
|
-
*/
|
|
240
|
-
export function createOssySsrBundlePlugins ({ nodeEnv }) {
|
|
202
|
+
export function createCombinedBundlePlugins ({ nodeEnv }) {
|
|
241
203
|
return [
|
|
242
204
|
replace({
|
|
243
205
|
preventAssignment: true,
|
|
@@ -260,93 +222,6 @@ export function createOssySsrBundlePlugins ({ nodeEnv }) {
|
|
|
260
222
|
]
|
|
261
223
|
}
|
|
262
224
|
|
|
263
|
-
/** Generates the SSR entry stub for a page: exports renderPage(props, options) and metadata. */
|
|
264
|
-
export function generatePageSsrModule ({ pageAbsPath, stubAbsPath }) {
|
|
265
|
-
const rel = relToGeneratedImport(stubAbsPath, pageAbsPath)
|
|
266
|
-
return [
|
|
267
|
-
'// Generated by @ossy/app — do not edit',
|
|
268
|
-
'',
|
|
269
|
-
"import { createElement } from 'react'",
|
|
270
|
-
"import { renderToPipeableStream } from 'react-dom/server'",
|
|
271
|
-
"import { Writable } from 'node:stream'",
|
|
272
|
-
"import { App } from '@ossy/connected-components'",
|
|
273
|
-
`import * as _page from './${rel}'`,
|
|
274
|
-
'',
|
|
275
|
-
'export const metadata = _page.metadata',
|
|
276
|
-
'',
|
|
277
|
-
'function PageShell (props) {',
|
|
278
|
-
" return createElement('html', { lang: props.defaultLanguage || 'en' },",
|
|
279
|
-
" createElement('head', null,",
|
|
280
|
-
" createElement('meta', { charSet: 'utf-8' }),",
|
|
281
|
-
" createElement('title', null, (_page.metadata && _page.metadata.title) || ''),",
|
|
282
|
-
' ),',
|
|
283
|
-
' createElement(App, props,',
|
|
284
|
-
' createElement(_page.default, props)',
|
|
285
|
-
' )',
|
|
286
|
-
' )',
|
|
287
|
-
'}',
|
|
288
|
-
'',
|
|
289
|
-
'export function renderPage (props, options = {}) {',
|
|
290
|
-
' return new Promise((resolve, reject) => {',
|
|
291
|
-
" let html = ''",
|
|
292
|
-
' const writable = new Writable({',
|
|
293
|
-
' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
|
|
294
|
-
' })',
|
|
295
|
-
' const { pipe } = renderToPipeableStream(createElement(PageShell, props), {',
|
|
296
|
-
' ...options,',
|
|
297
|
-
' onAllReady () { pipe(writable) },',
|
|
298
|
-
' onError (err) { reject(err) },',
|
|
299
|
-
' })',
|
|
300
|
-
" writable.on('finish', () => resolve(html))",
|
|
301
|
-
' })',
|
|
302
|
-
'}',
|
|
303
|
-
'',
|
|
304
|
-
].join('\n')
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/** Writes `ssr-entries/<id>.mjs` stubs for each page into ossyDir. */
|
|
308
|
-
export function writePageSsrStubs (pageFiles, srcDir, ossyDir) {
|
|
309
|
-
const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
|
|
310
|
-
fs.rmSync(entriesDir, { recursive: true, force: true })
|
|
311
|
-
if (pageFiles.length === 0) return
|
|
312
|
-
fs.mkdirSync(entriesDir, { recursive: true })
|
|
313
|
-
for (const f of pageFiles) {
|
|
314
|
-
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
315
|
-
const stubPath = path.join(entriesDir, `${pageId}.mjs`)
|
|
316
|
-
fs.mkdirSync(path.dirname(stubPath), { recursive: true })
|
|
317
|
-
fs.writeFileSync(stubPath, generatePageSsrModule({ pageAbsPath: f, stubAbsPath: stubPath }))
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/** Compiles per-page SSR bundles to `build/ssr/<id>.mjs`. Each is self-contained with React bundled in. */
|
|
322
|
-
export async function compilePageSsrModules ({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }) {
|
|
323
|
-
const ssrDir = path.join(buildPath, OSSY_SSR_DIRNAME)
|
|
324
|
-
const entriesDir = path.join(ossyDir, OSSY_SSR_ENTRIES_DIRNAME)
|
|
325
|
-
fs.rmSync(ssrDir, { recursive: true, force: true })
|
|
326
|
-
if (pageFiles.length === 0) return []
|
|
327
|
-
fs.mkdirSync(ssrDir, { recursive: true })
|
|
328
|
-
const plugins = createOssySsrBundlePlugins({ nodeEnv })
|
|
329
|
-
const results = await Promise.all(
|
|
330
|
-
pageFiles.map(async (f) => {
|
|
331
|
-
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
332
|
-
const stubPath = path.join(entriesDir, `${pageId}.mjs`)
|
|
333
|
-
const outFile = path.join(ssrDir, `${pageId}.mjs`)
|
|
334
|
-
const bundle = await rollup({
|
|
335
|
-
input: stubPath,
|
|
336
|
-
plugins,
|
|
337
|
-
onwarn (warning, defaultHandler) {
|
|
338
|
-
if (onWarn) { onWarn(warning); return }
|
|
339
|
-
defaultHandler(warning)
|
|
340
|
-
},
|
|
341
|
-
})
|
|
342
|
-
await bundle.write({ file: outFile, format: 'esm', inlineDynamicImports: true })
|
|
343
|
-
await bundle.close()
|
|
344
|
-
return { id: pageId }
|
|
345
|
-
})
|
|
346
|
-
)
|
|
347
|
-
return results
|
|
348
|
-
}
|
|
349
|
-
|
|
350
225
|
/** Bundles a single Node ESM file (inline dynamic imports) for SSR / API / tasks. */
|
|
351
226
|
export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv, onWarn, external }) {
|
|
352
227
|
const bundle = await rollup({
|
|
@@ -567,73 +442,22 @@ export async function compileTaskServerModules ({ taskFiles, ossyDir, nodeEnv, o
|
|
|
567
442
|
}
|
|
568
443
|
|
|
569
444
|
/**
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*/
|
|
573
|
-
/**
|
|
574
|
-
* Enriches `pages.generated.json` with `module` paths pointing to SSR bundles and merges
|
|
575
|
-
* any `metadata` exported by each page. Runs after SSR bundles are compiled.
|
|
576
|
-
*/
|
|
577
|
-
export async function enrichPagesGeneratedManifest ({ ossyDir, pagesGeneratedPath }) {
|
|
578
|
-
if (!fs.existsSync(pagesGeneratedPath)) return
|
|
579
|
-
const basePages = JSON.parse(fs.readFileSync(pagesGeneratedPath, 'utf8'))
|
|
580
|
-
if (!Array.isArray(basePages) || basePages.length === 0) return
|
|
581
|
-
|
|
582
|
-
const pages = []
|
|
583
|
-
for (const basePage of basePages) {
|
|
584
|
-
const moduleRelPath = `../ssr/${basePage.id}.mjs`
|
|
585
|
-
const bundleAbs = path.join(ossyDir, moduleRelPath)
|
|
586
|
-
let meta = {}
|
|
587
|
-
if (fs.existsSync(bundleAbs)) {
|
|
588
|
-
try {
|
|
589
|
-
const mod = await import(pathToFileURL(bundleAbs).href)
|
|
590
|
-
meta = mod?.metadata && typeof mod.metadata === 'object' ? mod.metadata : {}
|
|
591
|
-
} catch {
|
|
592
|
-
// metadata unreadable — continue with defaults
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
const merged = {
|
|
596
|
-
id: basePage.id,
|
|
597
|
-
path: basePage.path,
|
|
598
|
-
...meta,
|
|
599
|
-
sourceFile: basePage.sourceFile,
|
|
600
|
-
module: moduleRelPath,
|
|
601
|
-
}
|
|
602
|
-
try {
|
|
603
|
-
JSON.stringify(merged)
|
|
604
|
-
} catch {
|
|
605
|
-
pages.push({ id: basePage.id, path: basePage.path, sourceFile: basePage.sourceFile, module: moduleRelPath })
|
|
606
|
-
continue
|
|
607
|
-
}
|
|
608
|
-
pages.push(merged)
|
|
609
|
-
}
|
|
610
|
-
writeOssyJson(pagesGeneratedPath, pages)
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Compiles server-side artifacts: per-page SSR bundles, API modules, and task modules.
|
|
445
|
+
* Compiles server-side artifacts: API modules and task modules.
|
|
446
|
+
* SSR page bundles are now produced by the combined Rollup pass in compileCombinedBundle.
|
|
615
447
|
*/
|
|
616
448
|
export async function compileOssyNodeArtifacts ({
|
|
617
|
-
pageFiles,
|
|
618
|
-
srcDir,
|
|
619
|
-
ossyDir,
|
|
620
|
-
buildPath,
|
|
621
449
|
apiFiles,
|
|
622
450
|
taskFiles,
|
|
451
|
+
ossyDir,
|
|
623
452
|
nodeEnv,
|
|
624
453
|
onWarn,
|
|
625
454
|
}) {
|
|
626
|
-
const [
|
|
627
|
-
compilePageSsrModules({ pageFiles, srcDir, ossyDir, buildPath, nodeEnv, onWarn }),
|
|
455
|
+
const [apiRouteList, taskList] = await Promise.all([
|
|
628
456
|
compileApiServerModules({ apiFiles, ossyDir, nodeEnv, onWarn }),
|
|
629
457
|
compileTaskServerModules({ taskFiles, ossyDir, nodeEnv, onWarn }),
|
|
630
458
|
])
|
|
631
459
|
writeOssyJson(path.join(ossyDir, OSSY_GEN_API_BASENAME), apiRouteList)
|
|
632
460
|
writeOssyJson(path.join(ossyDir, OSSY_GEN_TASKS_BASENAME), taskList)
|
|
633
|
-
await enrichPagesGeneratedManifest({
|
|
634
|
-
ossyDir,
|
|
635
|
-
pagesGeneratedPath: path.join(ossyDir, OSSY_GEN_PAGES_BASENAME),
|
|
636
|
-
})
|
|
637
461
|
}
|
|
638
462
|
|
|
639
463
|
export function filePathToRoute(filePath, srcDir) {
|
|
@@ -664,6 +488,38 @@ export function clientHydrateIdForPage (pageAbsPath, srcDir) {
|
|
|
664
488
|
return idMatch ? idMatch[1] : derived.id
|
|
665
489
|
}
|
|
666
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Like `clientHydrateIdForPage` but also extracts `path` from `metadata`.
|
|
493
|
+
* Used to embed the static page route map directly into the SSR entry.
|
|
494
|
+
*/
|
|
495
|
+
export function pageRouteFromSource (pageAbsPath, srcDir) {
|
|
496
|
+
const derived = filePathToRoute(pageAbsPath, srcDir)
|
|
497
|
+
try {
|
|
498
|
+
const src = fs.readFileSync(pageAbsPath, 'utf8')
|
|
499
|
+
const metaIdx = src.indexOf('export const metadata')
|
|
500
|
+
if (metaIdx === -1) return derived
|
|
501
|
+
const after = src.slice(metaIdx)
|
|
502
|
+
const id = after.match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1] ?? derived.id
|
|
503
|
+
const strPath = after.match(/\bpath\s*:\s*['"]([^'"]+)['"]/)?.[1]
|
|
504
|
+
if (strPath) {
|
|
505
|
+
return { id, path: strPath }
|
|
506
|
+
}
|
|
507
|
+
const pathObjBody = after.match(/\bpath\s*:\s*\{([\s\S]*?)\}/)?.[1]
|
|
508
|
+
if (pathObjBody) {
|
|
509
|
+
const languagePathEntries = [...pathObjBody.matchAll(/([A-Za-z0-9_]+)\s*:\s*['"]([^'"]+)['"]/g)]
|
|
510
|
+
if (languagePathEntries.length > 0) {
|
|
511
|
+
return {
|
|
512
|
+
id,
|
|
513
|
+
path: Object.fromEntries(languagePathEntries.map(([, language, routePath]) => [language, routePath])),
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return { id, path: derived.path }
|
|
518
|
+
} catch {
|
|
519
|
+
return derived
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
667
523
|
/** Posix path relative to `build/.ossy/` for the compiled **Node** page module (SSR). */
|
|
668
524
|
export function pageServerModuleRelPath (pageAbsPath, srcDir) {
|
|
669
525
|
const pageId = clientHydrateIdForPage(pageAbsPath, srcDir)
|
|
@@ -719,7 +575,7 @@ export function generateHydrateEntry ({ pageFiles, srcDir, stubAbsPath }) {
|
|
|
719
575
|
}
|
|
720
576
|
seenIds.add(hydrateId)
|
|
721
577
|
const rel = relToGeneratedImport(stubAbsPath, f)
|
|
722
|
-
pageLines.push(`
|
|
578
|
+
pageLines.push(` ${JSON.stringify(hydrateId)}: () => import('./${rel}'),`)
|
|
723
579
|
}
|
|
724
580
|
|
|
725
581
|
return [
|
|
@@ -775,18 +631,141 @@ export function writeHydrateEntry (pageFiles, srcDir, ossyDir) {
|
|
|
775
631
|
fs.writeFileSync(stubPath, generateHydrateEntry({ pageFiles, srcDir, stubAbsPath: stubPath }))
|
|
776
632
|
}
|
|
777
633
|
|
|
778
|
-
/**
|
|
779
|
-
|
|
780
|
-
|
|
634
|
+
/**
|
|
635
|
+
* Generates the single shared SSR entry that exports a static `pages` array and a
|
|
636
|
+
* `renderPage(pageId, props, options)` function. Each page is dynamically imported so
|
|
637
|
+
* Rollup can split out lazy chunks per page when code-splitting is enabled.
|
|
638
|
+
*/
|
|
639
|
+
export function generateSsrEntry ({ pageFiles, srcDir, stubAbsPath }) {
|
|
640
|
+
const seenIds = new Set()
|
|
641
|
+
const pagesLiteral = []
|
|
642
|
+
const pageModuleLines = []
|
|
643
|
+
|
|
644
|
+
for (const f of pageFiles) {
|
|
645
|
+
const { id, path: routePath } = pageRouteFromSource(f, srcDir)
|
|
646
|
+
if (seenIds.has(id)) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
`[@ossy/app] Duplicate page id "${id}" (${f}). Pages need unique ids.`
|
|
649
|
+
)
|
|
650
|
+
}
|
|
651
|
+
seenIds.add(id)
|
|
652
|
+
const rel = relToGeneratedImport(stubAbsPath, f)
|
|
653
|
+
pagesLiteral.push(` { id: ${JSON.stringify(id)}, path: ${JSON.stringify(routePath)} },`)
|
|
654
|
+
pageModuleLines.push(` ${JSON.stringify(id)}: () => import('./${rel}'),`)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return [
|
|
658
|
+
'// Generated by @ossy/app — do not edit',
|
|
659
|
+
'',
|
|
660
|
+
"import { createElement } from 'react'",
|
|
661
|
+
"import { renderToPipeableStream } from 'react-dom/server'",
|
|
662
|
+
"import { Writable } from 'node:stream'",
|
|
663
|
+
"import { App } from '@ossy/connected-components'",
|
|
664
|
+
'',
|
|
665
|
+
'export const pages = [',
|
|
666
|
+
...pagesLiteral,
|
|
667
|
+
']',
|
|
668
|
+
'',
|
|
669
|
+
'const pageModules = {',
|
|
670
|
+
...pageModuleLines,
|
|
671
|
+
'}',
|
|
672
|
+
'',
|
|
673
|
+
'function PageShell (props) {',
|
|
674
|
+
" const meta = props._pageMeta || {}",
|
|
675
|
+
" return createElement('html', { lang: props.defaultLanguage || 'en' },",
|
|
676
|
+
" createElement('head', null,",
|
|
677
|
+
" createElement('meta', { charSet: 'utf-8' }),",
|
|
678
|
+
" createElement('title', null, meta.title || ''),",
|
|
679
|
+
' ),',
|
|
680
|
+
' createElement(App, props,',
|
|
681
|
+
' createElement(props._pageComponent, props)',
|
|
682
|
+
' )',
|
|
683
|
+
' )',
|
|
684
|
+
'}',
|
|
685
|
+
'',
|
|
686
|
+
'export async function renderPage (pageId, props, options = {}) {',
|
|
687
|
+
' const load = pageModules[pageId]',
|
|
688
|
+
" if (!load) throw new Error(`[@ossy/app] Unknown page id: ${pageId}`)",
|
|
689
|
+
' const mod = await load()',
|
|
690
|
+
' const merged = { ...props, _pageComponent: mod.default, _pageMeta: mod.metadata || {} }',
|
|
691
|
+
' return new Promise((resolve, reject) => {',
|
|
692
|
+
" let html = ''",
|
|
693
|
+
' const writable = new Writable({',
|
|
694
|
+
' write (chunk, _enc, cb) { html += chunk.toString(); cb() },',
|
|
695
|
+
' })',
|
|
696
|
+
' const { pipe } = renderToPipeableStream(createElement(PageShell, merged), {',
|
|
697
|
+
' ...options,',
|
|
698
|
+
' onAllReady () { pipe(writable) },',
|
|
699
|
+
' onError (err) { reject(err) },',
|
|
700
|
+
' })',
|
|
701
|
+
" writable.on('finish', () => resolve(html))",
|
|
702
|
+
' })',
|
|
703
|
+
'}',
|
|
704
|
+
'',
|
|
705
|
+
].join('\n')
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** Writes `ssr-entry.mjs` into ossyDir; removes any stale per-page SSR stubs first. */
|
|
709
|
+
export function writeSsrEntry (pageFiles, srcDir, ossyDir) {
|
|
710
|
+
if (!fs.existsSync(ossyDir)) fs.mkdirSync(ossyDir, { recursive: true })
|
|
711
|
+
const stubPath = path.join(ossyDir, SSR_ENTRY_FILENAME)
|
|
712
|
+
fs.writeFileSync(stubPath, generateSsrEntry({ pageFiles, srcDir, stubAbsPath: stubPath }))
|
|
713
|
+
return stubPath
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Runs a single Rollup pass with both the SSR entry and the client hydrate entry as inputs.
|
|
718
|
+
* Produces:
|
|
719
|
+
* build/ssr/app.mjs — Node SSR bundle
|
|
720
|
+
* build/public/static/app.js — browser hydrate bundle
|
|
721
|
+
* build/public/static/chunks/[name]-[hash].js — shared chunks
|
|
722
|
+
*
|
|
723
|
+
* The SSR bundle imports shared chunks via relative paths (`../public/static/chunks/…`);
|
|
724
|
+
* the browser loads the same physical files from `/static/chunks/`.
|
|
725
|
+
*/
|
|
726
|
+
export async function compileCombinedBundle ({ ssrEntryPath, clientEntryPath, buildPath, nodeEnv, copyPublicFrom, onWarn }) {
|
|
727
|
+
if (copyPublicFrom && fs.existsSync(copyPublicFrom)) {
|
|
728
|
+
const destPublic = path.join(buildPath, 'public')
|
|
729
|
+
fs.mkdirSync(destPublic, { recursive: true })
|
|
730
|
+
const copyDir = (src, dest) => {
|
|
731
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
732
|
+
for (const ent of fs.readdirSync(src, { withFileTypes: true })) {
|
|
733
|
+
const srcPath = path.join(src, ent.name)
|
|
734
|
+
const destPath = path.join(dest, ent.name)
|
|
735
|
+
if (ent.isDirectory()) copyDir(srcPath, destPath)
|
|
736
|
+
else fs.copyFileSync(srcPath, destPath)
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
copyDir(copyPublicFrom, destPublic)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const bundle = await rollup({
|
|
743
|
+
input: { server: ssrEntryPath, app: clientEntryPath },
|
|
744
|
+
plugins: createCombinedBundlePlugins({ nodeEnv }),
|
|
745
|
+
onwarn (warning, defaultHandler) {
|
|
746
|
+
if (onWarn) { onWarn(warning); return }
|
|
747
|
+
defaultHandler(warning)
|
|
748
|
+
},
|
|
749
|
+
})
|
|
750
|
+
await bundle.write({
|
|
751
|
+
dir: buildPath,
|
|
752
|
+
format: 'esm',
|
|
753
|
+
entryFileNames: (chunk) =>
|
|
754
|
+
chunk.name === 'server'
|
|
755
|
+
? 'ssr/app.mjs'
|
|
756
|
+
: 'public/static/app.js',
|
|
757
|
+
chunkFileNames: 'public/static/chunks/[name]-[hash].js',
|
|
758
|
+
plugins: [minifyBrowserStaticChunks()],
|
|
759
|
+
})
|
|
760
|
+
await bundle.close()
|
|
781
761
|
}
|
|
782
762
|
|
|
783
763
|
/** JSON manifest: route ids, default paths, and page source paths (posix, relative to `cwd`). */
|
|
784
764
|
export function buildPagesGeneratedPayload (pageFiles, srcDir, cwd = process.cwd()) {
|
|
785
765
|
const pages = pageFiles.map((f) => {
|
|
786
|
-
const { path: routePath } =
|
|
787
|
-
const pageId = clientHydrateIdForPage(f, srcDir)
|
|
766
|
+
const { id, path: routePath } = pageRouteFromSource(f, srcDir)
|
|
788
767
|
return {
|
|
789
|
-
id
|
|
768
|
+
id,
|
|
790
769
|
path: routePath,
|
|
791
770
|
sourceFile: path.relative(cwd, f).replace(/\\/g, '/'),
|
|
792
771
|
}
|
|
@@ -910,8 +889,9 @@ export const build = async (cliArgs) => {
|
|
|
910
889
|
const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
911
890
|
const buildPath = path.resolve('build')
|
|
912
891
|
const srcDir = path.resolve('src')
|
|
913
|
-
const configPath = path.resolve(options['--config'] || 'src/config.js')
|
|
892
|
+
const configPath = path.resolve(options['--config'] || 'src/config.js')
|
|
914
893
|
const pageFiles = discoverFilesByPattern(srcDir, PAGE_FILE_PATTERN)
|
|
894
|
+
const publicDir = path.resolve('public')
|
|
915
895
|
|
|
916
896
|
resetOssyBuildDir(buildPath)
|
|
917
897
|
|
|
@@ -928,16 +908,14 @@ export const build = async (cliArgs) => {
|
|
|
928
908
|
srcDir,
|
|
929
909
|
pagesGeneratedPath,
|
|
930
910
|
})
|
|
911
|
+
|
|
912
|
+
// Write generated entries (both SSR and client hydrate)
|
|
913
|
+
const ssrEntryPath = writeSsrEntry(pageFiles, srcDir, ossyDir)
|
|
931
914
|
writeHydrateEntry(pageFiles, srcDir, ossyDir)
|
|
932
|
-
|
|
933
|
-
const clientHydrateInput = buildClientHydrateInput(pageFiles, srcDir, ossyDir)
|
|
915
|
+
const clientEntryPath = path.join(ossyDir, HYDRATE_ENTRY_FILENAME)
|
|
934
916
|
|
|
935
|
-
const { apiOverviewFiles } = resolveApiSource({
|
|
936
|
-
|
|
937
|
-
buildPath,
|
|
938
|
-
})
|
|
939
|
-
let middlewareSourcePath = path.resolve('src/middleware.js');
|
|
940
|
-
const publicDir = path.resolve('public')
|
|
917
|
+
const { apiOverviewFiles } = resolveApiSource({ srcDir, buildPath })
|
|
918
|
+
let middlewareSourcePath = path.resolve('src/middleware.js')
|
|
941
919
|
|
|
942
920
|
if (!fs.existsSync(middlewareSourcePath)) {
|
|
943
921
|
middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
|
|
@@ -947,45 +925,13 @@ export const build = async (cliArgs) => {
|
|
|
947
925
|
? configPath
|
|
948
926
|
: path.resolve(scriptDir, 'default-config.js')
|
|
949
927
|
|
|
950
|
-
|
|
951
|
-
|
|
928
|
+
console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
|
|
929
|
+
printBuildOverview({
|
|
952
930
|
pagesSourcePath: pagesGeneratedPath,
|
|
953
931
|
apiOverviewFiles,
|
|
954
932
|
configPath,
|
|
955
933
|
pageFiles,
|
|
956
934
|
})
|
|
957
|
-
const idToPath = Object.fromEntries(
|
|
958
|
-
pageFiles.map((f) => [
|
|
959
|
-
clientHydrateIdForPage(f, srcDir),
|
|
960
|
-
filePathToRoute(f, srcDir).path,
|
|
961
|
-
])
|
|
962
|
-
)
|
|
963
|
-
const pageIds = useDashboard
|
|
964
|
-
? [...new Set(pageFiles.map((f) => clientHydrateIdForPage(f, srcDir)))].sort()
|
|
965
|
-
: []
|
|
966
|
-
|
|
967
|
-
let dashboard = null
|
|
968
|
-
if (useDashboard && pageIds.length > 0) {
|
|
969
|
-
dashboard = createBuildDashboard({
|
|
970
|
-
mode: 'full',
|
|
971
|
-
pageIds,
|
|
972
|
-
idToPath,
|
|
973
|
-
overview: {
|
|
974
|
-
title: '@ossy/app build',
|
|
975
|
-
configRel: overviewSnap.configRel,
|
|
976
|
-
apiRoutes: overviewSnap.apiRoutes,
|
|
977
|
-
},
|
|
978
|
-
})
|
|
979
|
-
dashboard.start()
|
|
980
|
-
} else {
|
|
981
|
-
console.log('\n \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild\x1b[0m')
|
|
982
|
-
printBuildOverview({
|
|
983
|
-
pagesSourcePath: pagesGeneratedPath,
|
|
984
|
-
apiOverviewFiles,
|
|
985
|
-
configPath,
|
|
986
|
-
pageFiles,
|
|
987
|
-
})
|
|
988
|
-
}
|
|
989
935
|
|
|
990
936
|
if (resourceTemplatesResult.wrote && resourceTemplatesResult.path) {
|
|
991
937
|
console.log(
|
|
@@ -995,12 +941,9 @@ export const build = async (cliArgs) => {
|
|
|
995
941
|
|
|
996
942
|
const { taskOverviewFiles } = resolveTaskSource({ srcDir, buildPath })
|
|
997
943
|
await compileOssyNodeArtifacts({
|
|
998
|
-
pageFiles,
|
|
999
|
-
srcDir,
|
|
1000
|
-
ossyDir,
|
|
1001
|
-
buildPath,
|
|
1002
944
|
apiFiles: apiOverviewFiles,
|
|
1003
945
|
taskFiles: taskOverviewFiles,
|
|
946
|
+
ossyDir,
|
|
1004
947
|
nodeEnv: 'production',
|
|
1005
948
|
})
|
|
1006
949
|
|
|
@@ -1011,24 +954,13 @@ export const build = async (cliArgs) => {
|
|
|
1011
954
|
})
|
|
1012
955
|
copyOssyAppRuntime({ scriptDir, buildPath })
|
|
1013
956
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
copyPublicFrom: publicDir,
|
|
1022
|
-
buildPath,
|
|
1023
|
-
nodeEnv: 'production',
|
|
1024
|
-
createClientRollupPlugins: createOssyClientRollupPlugins,
|
|
1025
|
-
minifyBrowserStaticChunks,
|
|
1026
|
-
reporter: dashboard,
|
|
1027
|
-
})
|
|
1028
|
-
} finally {
|
|
1029
|
-
dashboard.dispose()
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
957
|
+
await compileCombinedBundle({
|
|
958
|
+
ssrEntryPath,
|
|
959
|
+
clientEntryPath,
|
|
960
|
+
buildPath,
|
|
961
|
+
nodeEnv: 'production',
|
|
962
|
+
copyPublicFrom: publicDir,
|
|
963
|
+
})
|
|
1032
964
|
|
|
1033
965
|
console.log(' \x1b[32m✔\x1b[0m \x1b[1m@ossy/app\x1b[0m \x1b[2mbuild finished\x1b[0m\n')
|
|
1034
966
|
};
|
|
@@ -1,97 +1,8 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import { rollup } from 'rollup'
|
|
4
2
|
|
|
5
3
|
export function staticHtmlPathForRoute (routePath, publicDir) {
|
|
6
4
|
const segments = routePath === '/' ? [] : routePath.replace(/^\//, '').split('/')
|
|
7
5
|
return path.join(publicDir, ...segments, 'index.html')
|
|
8
6
|
}
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
if (!copyPublicFrom || !fs.existsSync(copyPublicFrom)) return
|
|
12
|
-
const dest = path.join(buildPath, 'public')
|
|
13
|
-
fs.mkdirSync(dest, { recursive: true })
|
|
14
|
-
fs.cpSync(copyPublicFrom, dest, { recursive: true })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function bundleHydrateEntry ({
|
|
18
|
-
entryPath,
|
|
19
|
-
buildPath,
|
|
20
|
-
plugins,
|
|
21
|
-
minifyPlugin,
|
|
22
|
-
}) {
|
|
23
|
-
const bundle = await rollup({
|
|
24
|
-
input: { app: entryPath },
|
|
25
|
-
plugins,
|
|
26
|
-
})
|
|
27
|
-
try {
|
|
28
|
-
await bundle.write({
|
|
29
|
-
dir: path.join(buildPath, 'public'),
|
|
30
|
-
format: 'esm',
|
|
31
|
-
entryFileNames: 'static/[name].js',
|
|
32
|
-
chunkFileNames: 'static/chunks/[name]-[hash].js',
|
|
33
|
-
plugins: minifyPlugin ? [minifyPlugin] : [],
|
|
34
|
-
})
|
|
35
|
-
} finally {
|
|
36
|
-
await bundle.close()
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export default {
|
|
41
|
-
type: '@ossy/app/prerender-react',
|
|
42
|
-
async handler (input) {
|
|
43
|
-
const op = input?.op
|
|
44
|
-
if (op === 'runProduction') {
|
|
45
|
-
const {
|
|
46
|
-
hydrateEntryPath,
|
|
47
|
-
pageFilesLength,
|
|
48
|
-
pageIds,
|
|
49
|
-
copyPublicFrom,
|
|
50
|
-
buildPath,
|
|
51
|
-
nodeEnv,
|
|
52
|
-
buildPathForPlugins,
|
|
53
|
-
createClientRollupPlugins,
|
|
54
|
-
minifyBrowserStaticChunks,
|
|
55
|
-
reporter,
|
|
56
|
-
} = input
|
|
57
|
-
|
|
58
|
-
if (pageFilesLength === 0 || !hydrateEntryPath) {
|
|
59
|
-
return { bundleFailures: 0 }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
copyPublicToBuild({ copyPublicFrom, buildPath })
|
|
63
|
-
fs.mkdirSync(path.join(buildPath, 'public', 'static'), { recursive: true })
|
|
64
|
-
fs.mkdirSync(path.join(buildPath, 'public', 'static', 'chunks'), { recursive: true })
|
|
65
|
-
|
|
66
|
-
const ids = pageIds || []
|
|
67
|
-
const t0 = Date.now()
|
|
68
|
-
for (const id of ids) reporter?.startBundle?.(id)
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const plugins = createClientRollupPlugins({
|
|
72
|
-
nodeEnv,
|
|
73
|
-
copyPublicFrom: undefined,
|
|
74
|
-
buildPath: buildPathForPlugins ?? buildPath,
|
|
75
|
-
})
|
|
76
|
-
await bundleHydrateEntry({
|
|
77
|
-
entryPath: hydrateEntryPath,
|
|
78
|
-
buildPath,
|
|
79
|
-
plugins,
|
|
80
|
-
minifyPlugin: minifyBrowserStaticChunks(),
|
|
81
|
-
})
|
|
82
|
-
const ms = Date.now() - t0
|
|
83
|
-
for (const id of ids) reporter?.completeBundle?.(id, { ok: true, ms })
|
|
84
|
-
} catch (error) {
|
|
85
|
-
const ms = Date.now() - t0
|
|
86
|
-
for (const id of ids) reporter?.completeBundle?.(id, { ok: false, ms, error })
|
|
87
|
-
console.error(`[@ossy/app][client-bundle] hydrate-entry failed:`, error)
|
|
88
|
-
throw error
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { bundleFailures: 0 }
|
|
92
|
-
}
|
|
93
|
-
throw new Error(
|
|
94
|
-
`[@ossy/app][prerender-react] Unknown op: ${String(op)} (expected runProduction)`
|
|
95
|
-
)
|
|
96
|
-
},
|
|
97
|
-
}
|
|
8
|
+
export default { type: '@ossy/app/prerender-react' }
|
package/cli/render-page.task.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
|
-
|
|
4
|
-
const __ossyDir = path.dirname(fileURLToPath(import.meta.url))
|
|
5
|
-
|
|
6
|
-
async function loadSsrBundle (route) {
|
|
7
|
-
if (typeof route?.id !== 'string' || !route.id) {
|
|
8
|
-
throw new Error(`[@ossy/app][BuildPage] Route has no id`)
|
|
9
|
-
}
|
|
10
|
-
const bundlePath = path.join(__ossyDir, '..', 'ssr', `${route.id}.mjs`)
|
|
11
|
-
const mod = await import(pathToFileURL(bundlePath).href)
|
|
12
|
-
if (typeof mod?.renderPage !== 'function') {
|
|
13
|
-
throw new Error(
|
|
14
|
-
`[@ossy/app][BuildPage] SSR bundle for "${route.id}" must export renderPage (got ${typeof mod?.renderPage}).`
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
return mod
|
|
18
|
-
}
|
|
1
|
+
import { renderPage as ssrRender } from '../ssr/app.mjs'
|
|
19
2
|
|
|
20
3
|
export function buildPrerenderAppConfig ({
|
|
21
4
|
buildTimeConfig,
|
|
@@ -62,12 +45,7 @@ export function buildHydrationAppConfig (appConfig) {
|
|
|
62
45
|
|
|
63
46
|
export const BuildPage = {
|
|
64
47
|
async handle ({ route, appConfig }) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return renderPage(hydrationConfig, {
|
|
69
|
-
bootstrapScriptContent: `window.__INITIAL_APP_CONFIG__=${JSON.stringify(hydrationConfig)}`,
|
|
70
|
-
bootstrapModules: ['/static/app.js'],
|
|
71
|
-
})
|
|
48
|
+
const config = buildHydrationAppConfig(appConfig)
|
|
49
|
+
return ssrRender(route.id, config, {})
|
|
72
50
|
},
|
|
73
51
|
}
|
package/cli/server.js
CHANGED
|
@@ -130,7 +130,7 @@ app.all('*all', async (req, res) => {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
const pageRoute = pageRouter.getPageByUrl(requestUrl)
|
|
133
|
-
if (pageRoute
|
|
133
|
+
if (pageRoute) {
|
|
134
134
|
const appConfig = buildPrerenderAppConfig({
|
|
135
135
|
buildTimeConfig,
|
|
136
136
|
pageList: sitePageList,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/app",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"source": "./src/index.js",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"@babel/eslint-parser": "^7.15.8",
|
|
28
28
|
"@babel/preset-react": "^7.26.3",
|
|
29
29
|
"@babel/register": "^7.25.9",
|
|
30
|
-
"@ossy/connected-components": "^1.13.
|
|
31
|
-
"@ossy/design-system": "^1.13.
|
|
32
|
-
"@ossy/pages": "^1.13.
|
|
33
|
-
"@ossy/router": "^1.13.
|
|
34
|
-
"@ossy/router-react": "^1.13.
|
|
35
|
-
"@ossy/sdk": "^1.13.
|
|
36
|
-
"@ossy/sdk-react": "^1.13.
|
|
37
|
-
"@ossy/themes": "^1.13.
|
|
30
|
+
"@ossy/connected-components": "^1.13.5",
|
|
31
|
+
"@ossy/design-system": "^1.13.5",
|
|
32
|
+
"@ossy/pages": "^1.13.5",
|
|
33
|
+
"@ossy/router": "^1.13.5",
|
|
34
|
+
"@ossy/router-react": "^1.13.5",
|
|
35
|
+
"@ossy/sdk": "^1.13.5",
|
|
36
|
+
"@ossy/sdk-react": "^1.13.5",
|
|
37
|
+
"@ossy/themes": "^1.13.5",
|
|
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": "6c3b6ef26328e2227ee96f830138250fc1db1126"
|
|
71
71
|
}
|
package/src/index.js
CHANGED