@ossy/app 0.15.12 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/dev.js CHANGED
@@ -7,19 +7,20 @@ import {
7
7
  generatePagesModule,
8
8
  discoverModulePageFiles,
9
9
  resolveApiSource,
10
- ossyCleanBuildDirPlugin,
11
- ensureOssyGeneratedDir,
10
+ resetOssyBuildDir,
11
+ createOssyRollupPlugins,
12
+ writePageHydrateStubs,
13
+ buildClientHydrateInput,
14
+ clientHydrateIdForPage,
12
15
  ossyGeneratedDir,
13
16
  OSSY_GEN_PAGES_BASENAME,
14
17
  OSSY_GEN_API_BASENAME,
18
+ writeResourceTemplatesBarrelIfPresent,
19
+ resourceTemplatesDir,
20
+ OSSY_RESOURCE_TEMPLATES_OUT,
21
+ ossyServerExternal,
15
22
  } from './build.js';
16
23
  import { watch } from 'rollup';
17
- import babel from '@rollup/plugin-babel';
18
- import { nodeResolve as resolveDependencies } from '@rollup/plugin-node-resolve'
19
- import resolveCommonJsDependencies from '@rollup/plugin-commonjs'
20
- import json from "@rollup/plugin-json"
21
- import copy from 'rollup-plugin-copy';
22
- import replace from '@rollup/plugin-replace';
23
24
  import arg from 'arg'
24
25
  import { spawn } from 'node:child_process'
25
26
  // import inject from '@rollup/plugin-inject'
@@ -27,80 +28,55 @@ import { spawn } from 'node:child_process'
27
28
  export const dev = async (cliArgs) => {
28
29
  console.log('[@ossy/app][dev] Starting...')
29
30
 
30
- const options = arg({
31
- '--pages': String,
32
- '--p': '--pages',
33
-
34
- '--destination': String,
35
- '--d': '--destination',
36
-
31
+ const options = arg({
37
32
  '--config': String,
38
33
  '-c': '--config',
39
-
40
- '--api-source': String,
41
34
  }, { argv: cliArgs, permissive: true })
42
35
 
43
36
 
44
37
  const scriptDir = path.dirname(url.fileURLToPath(import.meta.url))
45
- const cwd = process.cwd()
46
- const pagesOpt = options['--pages'] || 'src'
47
- const buildPath = path.resolve(options['--destination'] || 'build')
48
- ensureOssyGeneratedDir(buildPath)
49
- const srcDir = path.resolve(pagesOpt)
38
+ const buildPath = path.resolve('build')
39
+ const srcDir = path.resolve('src')
50
40
  const configPath = path.resolve(options['--config'] || 'src/config.js');
51
41
  const pageFiles = discoverPageFiles(srcDir)
52
- const modulePageFiles = await discoverModulePageFiles({ cwd, configPath })
53
- const pagesJsxPath = path.resolve('src/pages.jsx')
54
- const hasPagesJsx = fs.existsSync(pagesJsxPath)
42
+ const modulePageFiles = await discoverModulePageFiles({ configPath })
55
43
 
56
- let effectivePagesSource
57
- let isPageFiles = false
58
- if (pageFiles.length > 0 || modulePageFiles.length > 0) {
59
- const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
60
- fs.writeFileSync(
61
- pagesGeneratedPath,
62
- generatePagesModule([...pageFiles, ...modulePageFiles], cwd, pagesOpt, pagesGeneratedPath)
63
- )
64
- effectivePagesSource = pagesGeneratedPath
65
- isPageFiles = true
66
- } else if (hasPagesJsx) {
67
- effectivePagesSource = pagesJsxPath
68
- } else {
69
- throw new Error(`[@ossy/app][dev] No pages found. Create *.page.jsx files in src/, or src/pages.jsx`);
70
- }
44
+ resetOssyBuildDir(buildPath)
45
+
46
+ writeResourceTemplatesBarrelIfPresent({ cwd: process.cwd(), log: true })
47
+
48
+ const allPageFiles = [...pageFiles, ...modulePageFiles]
49
+ const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
50
+ fs.writeFileSync(
51
+ pagesGeneratedPath,
52
+ generatePagesModule(allPageFiles, srcDir, pagesGeneratedPath)
53
+ )
54
+ const effectivePagesSource = pagesGeneratedPath
55
+ const ossyDir = ossyGeneratedDir(buildPath)
56
+ writePageHydrateStubs(allPageFiles, srcDir, ossyDir)
57
+ const clientHydrateInput = buildClientHydrateInput(allPageFiles, srcDir, ossyDir)
71
58
 
72
59
  const {
73
60
  apiSourcePath: resolvedApi,
74
61
  apiOverviewFiles,
75
62
  } = resolveApiSource({
76
- cwd,
77
- pagesOpt,
78
- scriptDir,
79
- explicitApiSource: options['--api-source'],
63
+ srcDir,
80
64
  buildPath,
81
65
  })
82
66
  let apiSourcePath = resolvedApi
83
67
  let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/middleware.js');
84
68
  const publicDir = path.resolve('public')
85
69
 
86
- const inputClient = path.resolve(scriptDir, 'client.js')
87
70
  const inputServer = path.resolve(scriptDir, 'server.js')
88
71
 
89
- const inputFiles = [inputClient, inputServer]
90
-
91
72
  printBuildOverview({
92
73
  pagesSourcePath: effectivePagesSource,
93
74
  apiSourcePath,
94
75
  apiOverviewFiles,
95
76
  configPath,
96
- isPageFiles,
97
- pageFiles: isPageFiles ? pageFiles : [],
77
+ pageFiles,
98
78
  });
99
79
 
100
- if (!fs.existsSync(apiSourcePath)) {
101
- apiSourcePath = path.resolve(scriptDir, 'api.js')
102
- }
103
-
104
80
  if (!fs.existsSync(middlewareSourcePath)) {
105
81
  middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
106
82
  }
@@ -109,73 +85,59 @@ export const dev = async (cliArgs) => {
109
85
  ? configPath
110
86
  : path.resolve(scriptDir, 'default-config.js')
111
87
 
112
- const inputOptions = {
113
- input: inputFiles,
114
- plugins: [
115
- ossyCleanBuildDirPlugin(buildPath),
116
- // inject({ 'React': 'react' }),
117
- replace({
118
- preventAssignment: true,
119
- delimiters: ['%%', '%%'],
120
- '@ossy/app/source-file': path.resolve(scriptDir, 'default-app.jsx'),
121
- }),
122
- replace({
123
- preventAssignment: true,
124
- delimiters: ['%%', '%%'],
125
- '@ossy/pages/source-file': effectivePagesSource,
126
- }),
127
- replace({
128
- preventAssignment: true,
129
- delimiters: ['%%', '%%'],
130
- '@ossy/api/source-file': apiSourcePath,
131
- }),
132
- replace({
133
- preventAssignment: true,
134
- delimiters: ['%%', '%%'],
135
- '@ossy/middleware/source-file': middlewareSourcePath,
136
- }),
137
- replace({
138
- preventAssignment: true,
139
- delimiters: ['%%', '%%'],
140
- '@ossy/config/source-file': configSourcePath,
141
- }),
142
- replace({
143
- preventAssignment: true,
144
- 'process.env.NODE_ENV': JSON.stringify('development')
145
- }),
146
- json(),
147
- // removeOwnPeerDependencies(),
148
- resolveCommonJsDependencies(),
149
- resolveDependencies({ preferBuiltins: true }),
150
- babel({
151
- babelHelpers: 'bundled',
152
- // exclude: ['**/node_modules/**/*'],
153
- presets: ['@babel/preset-env', '@babel/preset-react']
154
- }),
155
- // preserveDirectives(),
156
- // minifyJS(),
157
- copy({
158
- targets: [
159
- fs.existsSync(publicDir)
160
- ? { src: `${publicDir}/**/*`, dest: 'build/public' }
161
- : undefined,
162
- ].filter(x => !!x)
163
- })
164
- ],
165
- };
88
+ const sharedPluginOpts = {
89
+ pagesGeneratedPath: effectivePagesSource,
90
+ apiSourcePath,
91
+ middlewareSourcePath,
92
+ configSourcePath,
93
+ nodeEnv: 'development',
94
+ buildPath,
95
+ }
96
+
97
+ const serverPlugins = createOssyRollupPlugins({
98
+ ...sharedPluginOpts,
99
+ nodeExternals: true,
100
+ preferBuiltins: true,
101
+ copyPublicFrom: publicDir,
102
+ })
103
+
104
+ const clientPlugins = createOssyRollupPlugins({
105
+ ...sharedPluginOpts,
106
+ nodeExternals: false,
107
+ preferBuiltins: false,
108
+ })
109
+
110
+ const serverOutput = {
111
+ dir: buildPath,
112
+ format: 'esm',
113
+ preserveModules: true,
114
+ preserveModulesRoot: path.dirname(inputServer),
115
+ entryFileNames ({ name }) {
116
+ return name === 'server' ? 'server.js' : '[name].js'
117
+ },
118
+ assetFileNames: '[name][extname]',
119
+ }
166
120
 
167
- const outputOptions = {
168
- dir: 'build',
169
- // preserveModules: true,
170
- entryFileNames: ({ name }) => {
171
- const serverFileNames = ['server', 'api', 'middleware']
172
- if (serverFileNames.includes(name)) return '[name].js'
173
- if (name === 'client') return 'public/static/main.js'
174
- if (name === 'config') return 'public/static/[name].js'
121
+ const clientOutput = {
122
+ dir: buildPath,
123
+ format: 'esm',
124
+ entryFileNames ({ name }) {
125
+ if (name.startsWith('hydrate__')) {
126
+ const pageId = name.slice('hydrate__'.length)
127
+ return `public/static/hydrate-${pageId}.js`
128
+ }
175
129
  return 'public/static/[name].js'
176
130
  },
177
131
  chunkFileNames: 'public/static/[name]-[hash].js',
178
- format: 'esm',
132
+ }
133
+
134
+ let restartTimer = null
135
+ const scheduleRestart = () => {
136
+ clearTimeout(restartTimer)
137
+ restartTimer = setTimeout(async () => {
138
+ await triggerReload()
139
+ restartServer()
140
+ }, 100)
179
141
  }
180
142
 
181
143
  let serverProcess = null
@@ -210,11 +172,24 @@ export const dev = async (cliArgs) => {
210
172
  }
211
173
  }
212
174
 
213
- const watcher = watch({
214
- ...inputOptions,
215
- output: outputOptions,
216
- watch: { clearScreen: false },
217
- })
175
+ const watchConfigs = [
176
+ {
177
+ input: { server: inputServer },
178
+ output: serverOutput,
179
+ plugins: serverPlugins,
180
+ external: ossyServerExternal,
181
+ watch: { clearScreen: false },
182
+ },
183
+ ]
184
+ if (Object.keys(clientHydrateInput).length > 0) {
185
+ watchConfigs.push({
186
+ input: clientHydrateInput,
187
+ output: clientOutput,
188
+ plugins: clientPlugins,
189
+ watch: { clearScreen: false },
190
+ })
191
+ }
192
+ const watcher = watch(watchConfigs)
218
193
 
219
194
  watcher.on('event', async (event) => {
220
195
  if (event.code === 'BUNDLE_START') {
@@ -227,18 +202,14 @@ export const dev = async (cliArgs) => {
227
202
  console.log(`[@ossy/app][dev] Built in ${event.duration}ms`)
228
203
  }
229
204
  if (event.code === 'END') {
230
- await triggerReload()
231
- restartServer()
205
+ scheduleRestart()
232
206
  }
233
207
  })
234
208
 
235
209
  const regenApiBundle = () => {
236
210
  if (options['--api-source']) return
237
211
  resolveApiSource({
238
- cwd,
239
- pagesOpt,
240
- scriptDir,
241
- explicitApiSource: undefined,
212
+ srcDir,
242
213
  buildPath,
243
214
  })
244
215
  const gen = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_API_BASENAME)
@@ -250,18 +221,28 @@ export const dev = async (cliArgs) => {
250
221
  fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
251
222
  if (!filename) return
252
223
  if (/\.page\.(jsx?|tsx?)$/.test(filename)) {
253
- if (!isPageFiles) return
254
- const files = discoverPageFiles(srcDir)
255
- if (files.length > 0) {
256
- const pagesGeneratedPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
257
- fs.writeFileSync(
258
- pagesGeneratedPath,
259
- generatePagesModule(files, cwd, pagesOpt, pagesGeneratedPath)
260
- )
224
+ const combined = [...discoverPageFiles(srcDir), ...modulePageFiles]
225
+ const regenPath = path.join(ossyGeneratedDir(buildPath), OSSY_GEN_PAGES_BASENAME)
226
+ fs.writeFileSync(regenPath, generatePagesModule(combined, srcDir, regenPath))
227
+ writePageHydrateStubs(combined, srcDir, ossyDir)
228
+ if (typeof watcher?.invalidate === 'function') {
229
+ watcher.invalidate(regenPath)
230
+ for (const f of combined) {
231
+ const hid = clientHydrateIdForPage(f, srcDir)
232
+ watcher.invalidate(path.join(ossyDir, `hydrate-${hid}.jsx`))
233
+ }
261
234
  }
262
235
  }
263
- if (/\.api\.(mjs|cjs|js)$/.test(filename) || /(^|\/)api\.js$/.test(filename.replace(/\\/g, '/'))) {
236
+ if (/\.api\.(mjs|cjs|js)$/.test(filename)) {
264
237
  regenApiBundle()
265
238
  }
239
+ const norm = filename.replace(/\\/g, '/')
240
+ if (/\.resource\.js$/.test(norm) && norm.includes('resource-templates/')) {
241
+ writeResourceTemplatesBarrelIfPresent({ cwd: process.cwd(), log: true })
242
+ const rtOut = path.join(resourceTemplatesDir(process.cwd()), OSSY_RESOURCE_TEMPLATES_OUT)
243
+ if (fs.existsSync(rtOut) && typeof watcher?.invalidate === 'function') {
244
+ watcher.invalidate(rtOut)
245
+ }
246
+ }
266
247
  })
267
248
  };
package/cli/server.js CHANGED
@@ -1,20 +1,24 @@
1
1
  import path from 'path';
2
2
  import url from 'url'
3
- import React, { createElement } from 'react';
3
+ import React, { cloneElement } from 'react';
4
4
  import express from 'express'
5
5
  import morgan from 'morgan'
6
- import { Router } from '@ossy/router'
6
+ import { Router as OssyRouter } from '@ossy/router'
7
7
  import { prerenderToNodeStream } from 'react-dom/static'
8
8
  import { ProxyInternal } from './proxy-internal.js'
9
9
  import cookieParser from 'cookie-parser'
10
10
 
11
- import App from '%%@ossy/app/source-file%%'
11
+ import pages from '%%@ossy/pages/source-file%%'
12
12
  import ApiRoutes from '%%@ossy/api/source-file%%'
13
13
  import Middleware from '%%@ossy/middleware/source-file%%'
14
14
  import configModule from '%%@ossy/config/source-file%%'
15
15
 
16
16
  const buildTimeConfig = configModule?.default ?? configModule ?? {}
17
17
 
18
+ /** `api.generated.js` is always present; default may still be empty. */
19
+ const apiRouteList = ApiRoutes ?? []
20
+ const pageList = pages ?? []
21
+
18
22
  const app = express();
19
23
 
20
24
  const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
@@ -104,16 +108,18 @@ const middleware = [
104
108
 
105
109
  app.use(middleware)
106
110
 
107
- const ApiRouter = Router.of({ pages: ApiRoutes || [] })
111
+ const Router = OssyRouter.of({
112
+ pages: [...apiRouteList, ...pageList]
113
+ })
108
114
 
109
115
  app.all('*all', (req, res) => {
110
116
  const pathname = req.originalUrl
111
117
 
112
- const apiRoute = ApiRouter.getPageByUrl(pathname)
118
+ const route = Router.getPageByUrl(pathname)
113
119
 
114
- if (apiRoute) {
120
+ if (route && typeof route.handle === 'function') {
115
121
  console.log(`[@ossy/app][server] Handling API route: ${pathname}`)
116
- apiRoute.handle(req, res)
122
+ route.handle(req, res)
117
123
  return
118
124
  }
119
125
 
@@ -130,25 +136,32 @@ app.all('*all', (req, res) => {
130
136
  sidebarPrimaryCollapsed: userAppSettings.sidebarPrimaryCollapsed === true,
131
137
  }
132
138
 
133
- renderToString(App, appConfig)
139
+ if (!route?.element) {
140
+ res.status(404).send('Not found')
141
+ return
142
+ }
143
+
144
+ prerenderHtmlDocument(cloneElement(route.element, appConfig), appConfig, route.id)
134
145
  .then(html => { res.send(html) })
135
146
  .catch(err => { res.send(err) })
136
147
 
137
- });
148
+ })
138
149
 
139
150
  app.listen(port, () => {
140
151
  console.log(`[@ossy/app][server] Running on http://localhost:${port}`);
141
152
  });
142
153
 
143
- async function renderToString(App, config) {
154
+ async function prerenderHtmlDocument (rootElement, config, pageId) {
144
155
 
145
156
  const devReloadScript = isDevReloadEnabled
146
157
  ? `(function(){try{var es=new EventSource('/__ossy_reload');es.addEventListener('reload',function(){location.reload();});}catch(e){}})();`
147
158
  : ``
148
159
 
149
- const { prelude } = await prerenderToNodeStream(createElement(App, config), {
160
+ const hydrateUrl = `/static/hydrate-${pageId}.js`
161
+
162
+ const { prelude } = await prerenderToNodeStream(rootElement, {
150
163
  bootstrapScriptContent: `window.__INITIAL_APP_CONFIG__ = ${JSON.stringify(config)};${devReloadScript}`,
151
- bootstrapModules: ['/static/main.js']
164
+ bootstrapModules: [hydrateUrl]
152
165
  });
153
166
 
154
167
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "0.15.12",
3
+ "version": "1.0.1",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -22,18 +22,17 @@
22
22
  "@babel/cli": "^7.26.4",
23
23
  "@babel/core": "^7.26.0",
24
24
  "@babel/eslint-parser": "^7.15.8",
25
- "@babel/preset-env": "^7.26.0",
26
25
  "@babel/preset-react": "^7.26.3",
27
26
  "@babel/register": "^7.25.9",
28
- "@ossy/connected-components": ">=0.5.0 <1.0.0",
29
- "@ossy/design-system": ">=0.5.0 <1.0.0",
30
- "@ossy/pages": ">=0.5.0 <1.0.0",
31
- "@ossy/resource-templates": ">=0.5.0 <1.0.0",
32
- "@ossy/router": ">=0.5.0 <1.0.0",
33
- "@ossy/router-react": ">=0.5.0 <1.0.0",
34
- "@ossy/sdk": ">=0.5.0 <1.0.0",
35
- "@ossy/sdk-react": ">=0.5.0 <1.0.0",
36
- "@ossy/themes": ">=0.5.0 <1.0.0",
27
+ "@ossy/connected-components": ">=1.0.0 <2.0.0",
28
+ "@ossy/design-system": ">=1.0.0 <2.0.0",
29
+ "@ossy/pages": ">=1.0.0 <2.0.0",
30
+ "@ossy/resource-templates": ">=1.0.0 <2.0.0",
31
+ "@ossy/router": ">=1.0.0 <2.0.0",
32
+ "@ossy/router-react": ">=1.0.0 <2.0.0",
33
+ "@ossy/sdk": ">=1.0.0 <2.0.0",
34
+ "@ossy/sdk-react": ">=1.0.0 <2.0.0",
35
+ "@ossy/themes": ">=1.0.0 <2.0.0",
37
36
  "@rollup/plugin-alias": "^6.0.0",
38
37
  "@rollup/plugin-babel": "6.1.0",
39
38
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -41,7 +40,6 @@
41
40
  "@rollup/plugin-json": "^6.1.0",
42
41
  "@rollup/plugin-node-resolve": "^16.0.3",
43
42
  "@rollup/plugin-replace": "^6.0.2",
44
- "@rollup/plugin-terser": "0.4.4",
45
43
  "@rollup/plugin-typescript": "^12.3.0",
46
44
  "arg": "^5.0.2",
47
45
  "babel-loader": "^10.0.0",
@@ -57,7 +55,8 @@
57
55
  "rollup-plugin-node-externals": "^8.1.2",
58
56
  "rollup-plugin-peer-deps-external": "^2.2.4",
59
57
  "rollup-plugin-postcss-modules": "^2.1.1",
60
- "rollup-plugin-preserve-directives": "^0.4.0"
58
+ "rollup-plugin-preserve-directives": "^0.4.0",
59
+ "terser": "^5.17.4"
61
60
  },
62
61
  "files": [
63
62
  "/cli",
@@ -66,5 +65,5 @@
66
65
  "README.md",
67
66
  "tsconfig.json"
68
67
  ],
69
- "gitHead": "1da35d94f324f917d5ea790f838ce8e50214730e"
68
+ "gitHead": "e5276fdb5dedf68b966d851077461b188c613241"
70
69
  }
@@ -3,11 +3,10 @@ import path from 'path'
3
3
  import { fileURLToPath } from 'url'
4
4
 
5
5
  const STUBS = [
6
- ['api.js', 'export default []\n'],
7
6
  ['middleware.js', 'export default []\n'],
8
7
  ]
9
8
 
10
- /** Ensures build/api.js and build/middleware.js exist when Rollup omits empty chunks. */
9
+ /** Ensures build/middleware.js exists when Rollup omits an empty middleware chunk. */
11
10
  export function ensureBuildStubs(buildDir) {
12
11
  if (!fs.existsSync(buildDir)) {
13
12
  console.warn(`[ensure-build-stubs] skip: ${buildDir} missing`)
package/src/index.js CHANGED
@@ -1,2 +1,7 @@
1
- export { build, buildWorker } from '../cli/build.js'
1
+ export {
2
+ build,
3
+ writeResourceTemplatesBarrelIfPresent,
4
+ resourceTemplatesDir,
5
+ OSSY_RESOURCE_TEMPLATES_OUT,
6
+ } from '../cli/build.js'
2
7
  export { dev } from '../cli/dev.js'
package/cli/Api.js DELETED
@@ -1,3 +0,0 @@
1
- // dummy file so it can be imported
2
-
3
- export default undefined
package/cli/client.js DELETED
@@ -1,8 +0,0 @@
1
- import React, { createElement } from 'react'
2
- import 'react-dom'
3
- import { hydrateRoot } from 'react-dom/client';
4
- import App from '%%@ossy/app/source-file%%'
5
-
6
- const initialConfig = window.__INITIAL_APP_CONFIG__ || {}
7
-
8
- hydrateRoot(document, createElement(App, initialConfig))
@@ -1,10 +0,0 @@
1
- import React from 'react'
2
- import { App } from '@ossy/connected-components'
3
- import pages from '%%@ossy/pages/source-file%%'
4
-
5
- /**
6
- * App entry. Uses App from @ossy/connected-components with pages from src/pages.jsx.
7
- */
8
- export default function DefaultApp(config) {
9
- return React.createElement(App, { ...config, pages })
10
- }