@ossy/app 0.7.15 → 0.8.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/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # `@ossy/app`
2
+
3
+ Server-side rendering runtime for Ossy apps.
4
+
5
+ ## Port configuration
6
+
7
+ By default, the server listens on port **3000**.
8
+
9
+ - **Environment variable**: set `PORT`
10
+ - **CLI argument**: pass `--port <number>` (or `-p <number>`) when running the built server file
11
+
12
+ Examples:
13
+
14
+ ```bash
15
+ # env var
16
+ PORT=4000 node build/server.js
17
+
18
+ # CLI arg
19
+ node build/server.js --port 4000
20
+ node build/server.js -p 4000
21
+ ```
22
+
package/cli/dev.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from 'path';
2
2
  import url from 'url';
3
3
  import fs from 'fs';
4
- import { rollup } from 'rollup';
4
+ import { watch } from 'rollup';
5
5
  import babel from '@rollup/plugin-babel';
6
6
  import { nodeResolve as resolveDependencies } from '@rollup/plugin-node-resolve'
7
7
  import resolveCommonJsDependencies from '@rollup/plugin-commonjs'
@@ -14,10 +14,11 @@ import copy from 'rollup-plugin-copy';
14
14
  import replace from '@rollup/plugin-replace';
15
15
  import remove from 'rollup-plugin-delete';
16
16
  import arg from 'arg'
17
+ import { spawn } from 'node:child_process'
17
18
  // import inject from '@rollup/plugin-inject'
18
19
 
19
20
  export const dev = async (cliArgs) => {
20
- console.log('[@ossy/app][build] Starting...')
21
+ console.log('[@ossy/app][dev] Starting...')
21
22
 
22
23
  const options = arg({
23
24
  '--source': String,
@@ -28,12 +29,12 @@ export const dev = async (cliArgs) => {
28
29
 
29
30
  '--config': String,
30
31
  '-c': '--config',
31
- }, { argv: cliArgs })
32
+ }, { argv: cliArgs, permissive: true })
32
33
 
33
34
 
34
35
  const appSourcePath = path.resolve(options['--source'] || 'src/App.jsx');
35
- let apiSourcePath = path.resolve(options['--api-source'] || 'src/Api.js');
36
- let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/Middleware.js');
36
+ let apiSourcePath = path.resolve(options['--api-source'] || 'src/api.js');
37
+ let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/middleware.js');
37
38
  const configPath = path.resolve(options['--config'] || 'src/config.js');
38
39
  const buildPath = path.resolve(options['--destination'] || 'build');
39
40
  const publicDir = path.resolve('public')
@@ -49,11 +50,11 @@ export const dev = async (cliArgs) => {
49
50
  }
50
51
 
51
52
  if (!fs.existsSync(apiSourcePath)) {
52
- apiSourcePath = path.resolve(scriptDir, 'Api.js')
53
+ apiSourcePath = path.resolve(scriptDir, 'api.js')
53
54
  }
54
55
 
55
56
  if (!fs.existsSync(middlewareSourcePath)) {
56
- apiSourcePath = path.resolve(scriptDir, 'Middleware.js')
57
+ middlewareSourcePath = path.resolve(scriptDir, 'middleware.js')
57
58
  }
58
59
 
59
60
  if (fs.existsSync(configPath)) {
@@ -78,11 +79,11 @@ export const dev = async (cliArgs) => {
78
79
  replace({
79
80
  preventAssignment: true,
80
81
  delimiters: ['%%', '%%'],
81
- '@ossy/middleware/source-file': apiSourcePath,
82
+ '@ossy/middleware/source-file': middlewareSourcePath,
82
83
  }),
83
84
  replace({
84
85
  preventAssignment: true,
85
- 'process.env.NODE_ENV': JSON.stringify('production')
86
+ 'process.env.NODE_ENV': JSON.stringify('development')
86
87
  }),
87
88
  json(),
88
89
  // removeOwnPeerDependencies(),
@@ -105,33 +106,71 @@ export const dev = async (cliArgs) => {
105
106
  ],
106
107
  };
107
108
 
108
- const outputOptions = [
109
- {
110
- dir: 'build',
111
- // preserveModules: true,
112
- entryFileNames: ({ name }) => {
113
- if (name === 'server') {
114
- return '[name].js'
115
- } else if (name === 'Api') {
116
- return '[name].js'
117
- } else if (name === 'client') {
118
- return 'public/static/main.js'
119
- } else if (name === 'config') {
120
- return 'public/static/[name].js'
121
- } else {
122
- return 'public/static/[name].js'
123
- }
124
- },
125
- chunkFileNames: 'public/static/[name]-[hash].js',
126
- format: 'esm',
127
- }
128
- ];
129
-
130
- const bundle = await rollup.watch(inputOptions);
131
-
132
- for (const options of outputOptions) {
133
- await bundle.write(options);
109
+ const outputOptions = {
110
+ dir: 'build',
111
+ // preserveModules: true,
112
+ entryFileNames: ({ name }) => {
113
+ const serverFileNames = ['server', 'api', 'middleware']
114
+ if (serverFileNames.includes(name)) return '[name].js'
115
+ if (name === 'client') return 'public/static/main.js'
116
+ if (name === 'config') return 'public/static/[name].js'
117
+ return 'public/static/[name].js'
118
+ },
119
+ chunkFileNames: 'public/static/[name]-[hash].js',
120
+ format: 'esm',
134
121
  }
135
122
 
136
- console.log('[@ossy/app][build] Finished');
123
+ let serverProcess = null
124
+ const startServer = () => {
125
+ if (serverProcess) return
126
+ serverProcess = spawn(process.execPath, [path.resolve(buildPath, 'server.js'), ...process.argv.slice(3)], {
127
+ stdio: 'inherit',
128
+ env: {
129
+ ...process.env,
130
+ OSSY_DEV_RELOAD: '1',
131
+ NODE_ENV: 'development',
132
+ },
133
+ })
134
+ serverProcess.on('exit', () => {
135
+ serverProcess = null
136
+ })
137
+ }
138
+
139
+ const restartServer = () => {
140
+ if (!serverProcess) return startServer()
141
+ serverProcess.kill('SIGTERM')
142
+ serverProcess = null
143
+ startServer()
144
+ }
145
+
146
+ const triggerReload = async () => {
147
+ const port = process.env.PORT || '3000'
148
+ try {
149
+ await fetch(`http://localhost:${port}/__ossy_reload`, { method: 'POST' })
150
+ } catch {
151
+ // server might not be up yet
152
+ }
153
+ }
154
+
155
+ const watcher = watch({
156
+ ...inputOptions,
157
+ output: outputOptions,
158
+ watch: { clearScreen: false },
159
+ })
160
+
161
+ watcher.on('event', async (event) => {
162
+ if (event.code === 'BUNDLE_START') {
163
+ console.log('[@ossy/app][dev] Building...')
164
+ }
165
+ if (event.code === 'ERROR') {
166
+ console.error('[@ossy/app][dev] Build error', event.error)
167
+ }
168
+ if (event.code === 'BUNDLE_END') {
169
+ console.log(`[@ossy/app][dev] Built in ${event.duration}ms`)
170
+ }
171
+ if (event.code === 'END') {
172
+ await triggerReload()
173
+ restartServer()
174
+ }
175
+ })
137
176
  };
package/cli/server.js CHANGED
@@ -17,10 +17,62 @@ const app = express();
17
17
  const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
18
18
  const ROOT_PATH = path.resolve(currentDir, 'public')
19
19
 
20
+ const isDevReloadEnabled = process.env.OSSY_DEV_RELOAD === '1'
21
+ const reloadClients = new Set()
22
+
23
+ function parsePortFromArgv(argv) {
24
+ // Supports: --port 4000, --port=4000, -p 4000
25
+ const idx = argv.findIndex(a => a === '--port' || a === '-p')
26
+ if (idx !== -1 && argv[idx + 1]) return argv[idx + 1]
27
+
28
+ const eq = argv.find(a => a.startsWith('--port='))
29
+ if (eq) return eq.split('=')[1]
30
+
31
+ return undefined
32
+ }
33
+
34
+ function normalizePort(value, fallback) {
35
+ if (value === undefined || value === null || value === '') return fallback
36
+ const n = Number.parseInt(String(value), 10)
37
+ if (!Number.isFinite(n) || n <= 0) return fallback
38
+ return n
39
+ }
40
+
41
+ const DEFAULT_PORT = 3000
42
+ const port = normalizePort(parsePortFromArgv(process.argv) ?? process.env.PORT, DEFAULT_PORT)
43
+
20
44
  if (Middleware !== undefined) {
21
45
  console.log(`[@ossy/app][server] ${Middleware?.length || 0} custom middleware loaded`)
22
46
  }
23
47
 
48
+ if (isDevReloadEnabled) {
49
+ app.get('/__ossy_reload', (req, res) => {
50
+ res.status(200)
51
+ res.setHeader('Content-Type', 'text/event-stream')
52
+ res.setHeader('Cache-Control', 'no-cache')
53
+ res.setHeader('Connection', 'keep-alive')
54
+ res.flushHeaders?.()
55
+
56
+ res.write('event: connected\ndata: ok\n\n')
57
+ reloadClients.add(res)
58
+
59
+ req.on('close', () => {
60
+ reloadClients.delete(res)
61
+ })
62
+ })
63
+
64
+ app.post('/__ossy_reload', (req, res) => {
65
+ for (const client of reloadClients) {
66
+ try {
67
+ client.write('event: reload\ndata: now\n\n')
68
+ } catch {
69
+ // ignore broken connections
70
+ }
71
+ }
72
+ res.status(204).end()
73
+ })
74
+ }
75
+
24
76
  const middleware = [
25
77
  morgan('tiny'),
26
78
  express.json({ strict: false }),
@@ -72,14 +124,18 @@ app.all('*all', (req, res) => {
72
124
 
73
125
  });
74
126
 
75
- app.listen(3000, () => {
76
- console.log('[@ossy/app][server] Running on http://localhost:3000');
127
+ app.listen(port, () => {
128
+ console.log(`[@ossy/app][server] Running on http://localhost:${port}`);
77
129
  });
78
130
 
79
131
  async function renderToString(App, config) {
80
132
 
133
+ const devReloadScript = isDevReloadEnabled
134
+ ? `(function(){try{var es=new EventSource('/__ossy_reload');es.addEventListener('reload',function(){location.reload();});}catch(e){}})();`
135
+ : ``
136
+
81
137
  const { prelude } = await prerenderToNodeStream(createElement(App, config), {
82
- bootstrapScriptContent: `window.__INITIAL_APP_CONFIG__ = ${JSON.stringify(config)};`,
138
+ bootstrapScriptContent: `window.__INITIAL_APP_CONFIG__ = ${JSON.stringify(config)};${devReloadScript}`,
83
139
  bootstrapModules: ['/static/main.js']
84
140
  });
85
141
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/app",
3
- "version": "0.7.15",
3
+ "version": "0.8.0",
4
4
  "description": "",
5
5
  "source": "./src/index.js",
6
6
  "main": "./src/index.js",
@@ -58,5 +58,5 @@
58
58
  "/cli",
59
59
  "README.md"
60
60
  ],
61
- "gitHead": "21c4dab2e75921f8fc6a8bed03b85f73833ec817"
61
+ "gitHead": "4aa6bd438e39babbb136f388aa7b5df9ba701144"
62
62
  }