@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 +22 -0
- package/cli/dev.js +75 -36
- package/cli/server.js +59 -3
- package/package.json +2 -2
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 {
|
|
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][
|
|
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/
|
|
36
|
-
let middlewareSourcePath = path.resolve(options['--middleware-source'] || 'src/
|
|
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, '
|
|
53
|
+
apiSourcePath = path.resolve(scriptDir, 'api.js')
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
if (!fs.existsSync(middlewareSourcePath)) {
|
|
56
|
-
|
|
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':
|
|
82
|
+
'@ossy/middleware/source-file': middlewareSourcePath,
|
|
82
83
|
}),
|
|
83
84
|
replace({
|
|
84
85
|
preventAssignment: true,
|
|
85
|
-
'process.env.NODE_ENV': JSON.stringify('
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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(
|
|
76
|
-
console.log(
|
|
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.
|
|
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": "
|
|
61
|
+
"gitHead": "4aa6bd438e39babbb136f388aa7b5df9ba701144"
|
|
62
62
|
}
|