@ossy/app 1.7.0 → 1.9.0

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 CHANGED
@@ -11,6 +11,7 @@ import nodeExternals from 'rollup-plugin-node-externals'
11
11
  import copy from 'rollup-plugin-copy';
12
12
  import replace from '@rollup/plugin-replace';
13
13
  import arg from 'arg'
14
+ import { writePrerenderedPages } from './prerender-pages.js'
14
15
 
15
16
  export const PAGE_FILE_PATTERN = /\.page\.(jsx?|tsx?)$/
16
17
  export const API_FILE_PATTERN = /\.api\.(mjs|cjs|js)$/
@@ -55,11 +56,10 @@ export const OSSY_GEN_PAGES_BASENAME = 'pages.generated.jsx'
55
56
  export const OSSY_GEN_API_BASENAME = 'api.generated.js'
56
57
  export const OSSY_GEN_TASKS_BASENAME = 'tasks.generated.js'
57
58
 
58
- /** Bundled Node entries consumed by copied `build/server.js` / `build/worker.js`. */
59
- export const OSSY_PAGES_SERVER_BUNDLE = 'pages.bundle.js'
59
+ /** Bundled pages + React for build-time prerender only (not loaded by `server.js`). */
60
+ export const OSSY_PAGES_PRERENDER_BUNDLE = 'pages.prerender.bundle.js'
60
61
  export const OSSY_API_SERVER_BUNDLE = 'api.bundle.js'
61
62
  export const OSSY_TASKS_SERVER_BUNDLE = 'tasks.bundle.js'
62
- export const OSSY_CONFIG_RUNTIME_BASENAME = 'config.runtime.js'
63
63
  export const OSSY_MIDDLEWARE_RUNTIME_BASENAME = 'middleware.runtime.js'
64
64
 
65
65
  /** Per-page client entries: `hydrate-<pageId>.jsx` under `.ossy/` */
@@ -223,16 +223,10 @@ export async function bundleOssyNodeEntry ({ inputPath, outputFile, nodeEnv }) {
223
223
  }
224
224
 
225
225
  /**
226
- * Re-exports the resolved config and middleware via `file:` URLs so `src/config.js` can keep
227
- * relative imports (copying those files into `build/` would break them).
226
+ * Re-exports middleware via `file:` URL so `src/middleware.js` can keep relative imports.
228
227
  */
229
- export function writeAppRuntimeShims ({ configSourcePath, middlewareSourcePath, ossyDir }) {
230
- const cfgHref = url.pathToFileURL(path.resolve(configSourcePath)).href
228
+ export function writeAppRuntimeShims ({ middlewareSourcePath, ossyDir }) {
231
229
  const mwHref = url.pathToFileURL(path.resolve(middlewareSourcePath)).href
232
- fs.writeFileSync(
233
- path.join(ossyDir, OSSY_CONFIG_RUNTIME_BASENAME),
234
- `// Generated by @ossy/app — do not edit\nexport { default } from '${cfgHref}'\n`
235
- )
236
230
  fs.writeFileSync(
237
231
  path.join(ossyDir, OSSY_MIDDLEWARE_RUNTIME_BASENAME),
238
232
  `// Generated by @ossy/app — do not edit\nexport { default } from '${mwHref}'\n`
@@ -241,7 +235,7 @@ export function writeAppRuntimeShims ({ configSourcePath, middlewareSourcePath,
241
235
 
242
236
  /**
243
237
  * Copies framework runtime into `build/`: server, worker entry, proxy.
244
- * Config/middleware load via `./.ossy/config.runtime.js` and `middleware.runtime.js`.
238
+ * Middleware loads via `./.ossy/middleware.runtime.js`.
245
239
  */
246
240
  export function copyOssyAppRuntime ({ scriptDir, buildPath }) {
247
241
  for (const name of ['server.js', 'proxy-internal.js']) {
@@ -656,7 +650,7 @@ export const build = async (cliArgs) => {
656
650
  ? configPath
657
651
  : path.resolve(scriptDir, 'default-config.js')
658
652
 
659
- const pagesBundlePath = path.join(ossyDir, OSSY_PAGES_SERVER_BUNDLE)
653
+ const pagesBundlePath = path.join(ossyDir, OSSY_PAGES_PRERENDER_BUNDLE)
660
654
  const apiBundlePath = path.join(ossyDir, OSSY_API_SERVER_BUNDLE)
661
655
  const tasksBundlePath = path.join(ossyDir, OSSY_TASKS_SERVER_BUNDLE)
662
656
 
@@ -678,7 +672,6 @@ export const build = async (cliArgs) => {
678
672
  })
679
673
 
680
674
  writeAppRuntimeShims({
681
- configSourcePath,
682
675
  middlewareSourcePath,
683
676
  ossyDir,
684
677
  })
@@ -711,5 +704,13 @@ export const build = async (cliArgs) => {
711
704
  await clientBundle.close()
712
705
  }
713
706
 
707
+ if (pageFiles.length > 0) {
708
+ await writePrerenderedPages({
709
+ pagesBundlePath,
710
+ configSourcePath,
711
+ publicDir: path.join(buildPath, 'public'),
712
+ })
713
+ }
714
+
714
715
  console.log('[@ossy/app][build] Finished');
715
716
  };
package/cli/dev.js CHANGED
@@ -21,13 +21,14 @@ import {
21
21
  OSSY_GEN_PAGES_BASENAME,
22
22
  OSSY_GEN_API_BASENAME,
23
23
  OSSY_GEN_TASKS_BASENAME,
24
- OSSY_PAGES_SERVER_BUNDLE,
24
+ OSSY_PAGES_PRERENDER_BUNDLE,
25
25
  OSSY_API_SERVER_BUNDLE,
26
26
  OSSY_TASKS_SERVER_BUNDLE,
27
27
  writeResourceTemplatesBarrelIfPresent,
28
28
  resourceTemplatesDir,
29
29
  OSSY_RESOURCE_TEMPLATES_OUT,
30
30
  } from './build.js';
31
+ import { writePrerenderedPages } from './prerender-pages.js'
31
32
  import { watch } from 'rollup';
32
33
  import arg from 'arg'
33
34
  import { spawn } from 'node:child_process'
@@ -89,7 +90,7 @@ export const dev = async (cliArgs) => {
89
90
  ? configPath
90
91
  : path.resolve(scriptDir, 'default-config.js')
91
92
 
92
- const pagesBundlePath = path.join(ossyDir, OSSY_PAGES_SERVER_BUNDLE)
93
+ const pagesBundlePath = path.join(ossyDir, OSSY_PAGES_PRERENDER_BUNDLE)
93
94
  const apiBundlePath = path.join(ossyDir, OSSY_API_SERVER_BUNDLE)
94
95
  const tasksBundlePath = path.join(ossyDir, OSSY_TASKS_SERVER_BUNDLE)
95
96
  const tasksGeneratedPath = path.join(ossyDir, OSSY_GEN_TASKS_BASENAME)
@@ -111,7 +112,6 @@ export const dev = async (cliArgs) => {
111
112
  nodeEnv: 'development',
112
113
  })
113
114
  writeAppRuntimeShims({
114
- configSourcePath,
115
115
  middlewareSourcePath,
116
116
  ossyDir,
117
117
  })
@@ -235,11 +235,21 @@ export const dev = async (cliArgs) => {
235
235
  }
236
236
  if (event.code === 'END') {
237
237
  writeAppRuntimeShims({
238
- configSourcePath,
239
238
  middlewareSourcePath,
240
239
  ossyDir,
241
240
  })
242
241
  copyOssyAppRuntime({ scriptDir, buildPath })
242
+ if (pageFiles.length > 0) {
243
+ try {
244
+ await writePrerenderedPages({
245
+ pagesBundlePath,
246
+ configSourcePath,
247
+ publicDir: path.join(buildPath, 'public'),
248
+ })
249
+ } catch (err) {
250
+ console.error('[@ossy/app][dev] Prerender failed', err)
251
+ }
252
+ }
243
253
  scheduleRestart()
244
254
  }
245
255
  })
@@ -0,0 +1,101 @@
1
+ import path from 'path'
2
+ import url from 'url'
3
+ import fs from 'fs'
4
+ import { BuildPage } from './render-page.task.js'
5
+
6
+ /**
7
+ * Maps an app route path to the file path express.static will serve for that URL
8
+ * (`/` → `public/index.html`, `/a/b` → `public/a/b/index.html`).
9
+ */
10
+ export function staticHtmlPathForRoute (routePath, publicDir) {
11
+ const p = typeof routePath === 'string' ? routePath : '/'
12
+ if (p === '/' || p === '') {
13
+ return path.join(publicDir, 'index.html')
14
+ }
15
+ const segments = p.replace(/^\//, '').split('/').filter(Boolean)
16
+ return path.join(publicDir, ...segments, 'index.html')
17
+ }
18
+
19
+ function pathIsPrerenderable (routePath) {
20
+ if (typeof routePath !== 'string') return false
21
+ if (!routePath.startsWith('/') || routePath.includes(':')) return false
22
+ return true
23
+ }
24
+
25
+ /** Mirrors server `resolveAppConfig` with static defaults (no cookies / session). */
26
+ export function buildPrerenderAppConfig ({
27
+ buildTimeConfig,
28
+ pageList,
29
+ activeRouteId,
30
+ urlPath,
31
+ }) {
32
+ const pages = pageList.map((page) => {
33
+ const entry = {
34
+ id: page?.id,
35
+ path: page?.path,
36
+ }
37
+ if (activeRouteId != null && page?.id === activeRouteId) {
38
+ entry.element = page?.element
39
+ }
40
+ return entry
41
+ })
42
+ return {
43
+ ...buildTimeConfig,
44
+ url: urlPath,
45
+ theme: buildTimeConfig.theme || 'light',
46
+ isAuthenticated: false,
47
+ workspaceId: buildTimeConfig.workspaceId,
48
+ apiUrl: buildTimeConfig.apiUrl,
49
+ pages,
50
+ sidebarPrimaryCollapsed: false,
51
+ }
52
+ }
53
+
54
+ /**
55
+ * After client bundles and `public/` exist, writes one HTML file per static page
56
+ * so express.static can serve them without runtime React rendering.
57
+ */
58
+ export async function writePrerenderedPages ({
59
+ pagesBundlePath,
60
+ configSourcePath,
61
+ publicDir,
62
+ }) {
63
+ const cfgHref = url.pathToFileURL(path.resolve(configSourcePath)).href
64
+ const pagesHref = url.pathToFileURL(path.resolve(pagesBundlePath)).href
65
+
66
+ const configModule = await import(cfgHref)
67
+ const pagesModule = await import(pagesHref)
68
+
69
+ const buildTimeConfig = configModule?.default ?? configModule ?? {}
70
+ const pageList = pagesModule?.default ?? []
71
+
72
+ for (const route of pageList) {
73
+ if (!route?.element) continue
74
+ if (!pathIsPrerenderable(route.path)) {
75
+ console.warn(
76
+ `[@ossy/app][prerender] Skipping "${route.id}" (path not prerenderable: ${JSON.stringify(route.path)})`
77
+ )
78
+ continue
79
+ }
80
+
81
+ const appConfig = buildPrerenderAppConfig({
82
+ buildTimeConfig,
83
+ pageList,
84
+ activeRouteId: route.id,
85
+ urlPath: route.path,
86
+ })
87
+
88
+ const html = await BuildPage.handle({
89
+ route,
90
+ appConfig,
91
+ isDevReloadEnabled: false,
92
+ })
93
+
94
+ const outPath = staticHtmlPathForRoute(route.path, publicDir)
95
+ fs.mkdirSync(path.dirname(outPath), { recursive: true })
96
+ fs.writeFileSync(outPath, html, 'utf8')
97
+ console.log(
98
+ `[@ossy/app][prerender] ${route.path} → ${path.relative(process.cwd(), outPath)}`
99
+ )
100
+ }
101
+ }
package/cli/server.js CHANGED
@@ -5,18 +5,12 @@ import morgan from 'morgan'
5
5
  import { Router as OssyRouter } from '@ossy/router'
6
6
  import { ProxyInternal } from './proxy-internal.js'
7
7
  import cookieParser from 'cookie-parser'
8
- import { BuildPage } from './render-page.task.js'
9
8
 
10
- import pages from './.ossy/pages.bundle.js'
11
9
  import ApiRoutes from './.ossy/api.bundle.js'
12
10
  import Middleware from './.ossy/middleware.runtime.js'
13
- import configModule from './.ossy/config.runtime.js'
14
-
15
- const buildTimeConfig = configModule?.default ?? configModule ?? {}
16
11
 
17
12
  /** API bundle default may be an empty array. */
18
13
  const apiRouteList = ApiRoutes ?? []
19
- const pageList = pages ?? []
20
14
 
21
15
  const app = express();
22
16
 
@@ -108,34 +102,9 @@ const middleware = [
108
102
  app.use(middleware)
109
103
 
110
104
  const Router = OssyRouter.of({
111
- pages: [...apiRouteList, ...pageList]
105
+ pages: apiRouteList,
112
106
  })
113
107
 
114
- function resolveAppConfig ({ req, buildTimeConfig, activeRouteId }) {
115
- const userAppSettings = req.userAppSettings || {}
116
- const pages = pageList.map((page) => {
117
- const entry = {
118
- id: page?.id,
119
- path: page?.path,
120
- }
121
- if (activeRouteId != null && page?.id === activeRouteId) {
122
- entry.element = page?.element
123
- }
124
- return entry
125
- })
126
- return {
127
- ...buildTimeConfig,
128
- url: req.originalUrl,
129
- theme: userAppSettings.theme || buildTimeConfig.theme || 'light',
130
- isAuthenticated: req.isAuthenticated || false,
131
- workspaceId: userAppSettings.workspaceId || buildTimeConfig.workspaceId,
132
- apiUrl: buildTimeConfig.apiUrl,
133
- pages,
134
- /** Primary app shell sidebar: icon rail when true (persisted in `x-ossy-user-settings`). */
135
- sidebarPrimaryCollapsed: userAppSettings.sidebarPrimaryCollapsed === true,
136
- }
137
- }
138
-
139
108
  app.all('*all', (req, res) => {
140
109
  const pathname = req.originalUrl
141
110
 
@@ -147,23 +116,9 @@ app.all('*all', (req, res) => {
147
116
  return
148
117
  }
149
118
 
150
- if (!route?.element) {
151
- res.status(404).send('Not found')
152
- return
153
- }
154
-
155
- const appConfig = resolveAppConfig({
156
- req,
157
- buildTimeConfig,
158
- activeRouteId: route.id,
159
- })
160
-
161
- BuildPage.handle({ route, appConfig, isDevReloadEnabled })
162
- .then(html => { res.send(html) })
163
- .catch(err => { res.send(err) })
164
-
119
+ res.status(404).send('Not found')
165
120
  })
166
121
 
167
122
  app.listen(port, () => {
168
123
  console.log(`[@ossy/app][server] Running on http://localhost:${port}`);
169
- });
124
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
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.7.0",
31
- "@ossy/design-system": "^1.7.0",
32
- "@ossy/pages": "^1.7.0",
33
- "@ossy/router": "^1.7.0",
34
- "@ossy/router-react": "^1.7.0",
35
- "@ossy/sdk": "^1.7.0",
36
- "@ossy/sdk-react": "^1.7.0",
37
- "@ossy/themes": "^1.7.0",
30
+ "@ossy/connected-components": "^1.9.0",
31
+ "@ossy/design-system": "^1.9.0",
32
+ "@ossy/pages": "^1.9.0",
33
+ "@ossy/router": "^1.9.0",
34
+ "@ossy/router-react": "^1.9.0",
35
+ "@ossy/sdk": "^1.9.0",
36
+ "@ossy/sdk-react": "^1.9.0",
37
+ "@ossy/themes": "^1.9.0",
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": "cb708db38cffe7bf0918e5ffb44bb0b13cf00d54"
70
+ "gitHead": "9be5f444e4cd530e29be0193fc0e7fd8da0bc995"
71
71
  }
package/cli/tasks.js DELETED
@@ -1,2 +0,0 @@
1
- /** Stub when no `*.task.js` / `src/tasks.js` — replaced at build time when tasks exist. */
2
- export default []
package/cli/temp.js DELETED
@@ -1,3 +0,0 @@
1
- import path from 'path';
2
-
3
- console.log(path.resolve('./', 'client.js'))