frosty 0.0.148 → 0.0.150
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/package.json
CHANGED
|
@@ -43,6 +43,8 @@ print_usage() {
|
|
|
43
43
|
echo " -b, --build-only Only build, do not run the server"
|
|
44
44
|
echo " -B, --no-build Skip build step"
|
|
45
45
|
echo " -p, --port <port> Specify port for the server (default: 8080)"
|
|
46
|
+
echo " -n, --num-workers <count> Specify number of worker processes"
|
|
47
|
+
echo " -i, --instance-var <name> Specify the env variable name for worker instance index (default: NODE_APP_INSTANCE)"
|
|
46
48
|
echo " -c, --configuration <file> Specify configuration file to use (default: server.config.js)"
|
|
47
49
|
echo " -s, --src <dir> Specify source root directory"
|
|
48
50
|
echo " -o, --output <dir> Specify output directory for build artifacts"
|
|
@@ -53,6 +55,7 @@ print_usage() {
|
|
|
53
55
|
echo ""
|
|
54
56
|
echo "Examples:"
|
|
55
57
|
echo " frosty run app.js"
|
|
58
|
+
echo " frosty run -n 4 app.js"
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
POSITIONAL_ARGS=()
|
|
@@ -63,6 +66,8 @@ while [ $# -gt 0 ]; do
|
|
|
63
66
|
-b|--build-only) BUILD_ONLY=true; shift ;;
|
|
64
67
|
-B|--no-build) NO_BUILD=true; shift ;;
|
|
65
68
|
-p|--port) PORT="$2"; shift 2 ;;
|
|
69
|
+
-n|--num-workers) NUM_WORKERS="$2"; shift 2 ;;
|
|
70
|
+
-i|--instance-var) INSTANCE_VAR="$2"; shift 2 ;;
|
|
66
71
|
-c|--configuration) CONFIG_FILE="$2"; shift 2 ;;
|
|
67
72
|
-s|--src) SRCROOT="$2"; shift 2 ;;
|
|
68
73
|
-o|--output) OUTPUT_DIR="$2"; shift 2 ;;
|
|
@@ -110,6 +115,8 @@ if [ "$NO_BUILD" != "true" ]; then
|
|
|
110
115
|
fi
|
|
111
116
|
[ -n "$INPUT_FILE" ] && BUILD_OPTS="$BUILD_OPTS --env INPUT_FILE="$INPUT_FILE""
|
|
112
117
|
[ -n "$SRCROOT" ] && BUILD_OPTS="$BUILD_OPTS --env SRCROOT="$SRCROOT""
|
|
118
|
+
[ -n "$NUM_WORKERS" ] && BUILD_OPTS="$BUILD_OPTS --env NUM_WORKERS="$NUM_WORKERS""
|
|
119
|
+
[ -n "$INSTANCE_VAR" ] && BUILD_OPTS="$BUILD_OPTS --env INSTANCE_VAR="$INSTANCE_VAR""
|
|
113
120
|
[ -n "$PORT" ] && BUILD_OPTS="$BUILD_OPTS --env PORT="$PORT""
|
|
114
121
|
if [ "$WATCH_MODE" = "true" ]; then
|
|
115
122
|
node "$FROSTY_CLI_ROOT/node_modules/webpack-cli/bin/cli.js" $BUILD_OPTS --watch &
|
|
@@ -24,5 +24,8 @@
|
|
|
24
24
|
//
|
|
25
25
|
|
|
26
26
|
import _ from 'lodash';
|
|
27
|
+
import { availableParallelism } from 'os';
|
|
27
28
|
|
|
28
29
|
export const PORT = process.env.PORT ? parseInt(process.env.PORT) : 8080;
|
|
30
|
+
export const NUM_WORKERS = process.env.NUM_WORKERS ? parseInt(process.env.NUM_WORKERS) : availableParallelism();
|
|
31
|
+
export const INSTANCE_VAR = process.env.INSTANCE_VAR || 'NODE_APP_INSTANCE';
|
|
@@ -26,41 +26,83 @@
|
|
|
26
26
|
import _ from 'lodash';
|
|
27
27
|
import fs from 'fs';
|
|
28
28
|
import path from 'path';
|
|
29
|
+
import cluster from 'cluster';
|
|
29
30
|
import { Server } from '@o2ter/server-js';
|
|
30
31
|
import { FrostyRoute } from './route';
|
|
31
32
|
import * as __SERVER__ from '__SERVER__';
|
|
32
33
|
import * as __APPLICATIONS__ from '__APPLICATIONS__';
|
|
33
|
-
import { PORT } from './env';
|
|
34
|
+
import { PORT, NUM_WORKERS, INSTANCE_VAR } from './env';
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
if (cluster.isPrimary && NUM_WORKERS > 1) {
|
|
37
|
+
console.info(`Primary ${process.pid} is running`);
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
// Fork workers one by one, waiting for each to come online before starting the next.
|
|
40
|
+
let forked = 0;
|
|
41
|
+
const workerInstances = new Map(); // worker.id -> instance index
|
|
42
|
+
const startedWorkers = new Set(); // worker IDs that have successfully started
|
|
38
43
|
|
|
39
|
-
const
|
|
40
|
-
|
|
44
|
+
const spawnWorker = (instance) => {
|
|
45
|
+
const worker = cluster.fork({ [INSTANCE_VAR]: instance });
|
|
46
|
+
workerInstances.set(worker.id, instance);
|
|
47
|
+
return worker;
|
|
48
|
+
};
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const forkNext = () => {
|
|
51
|
+
if (forked >= NUM_WORKERS) return;
|
|
52
|
+
const instance = forked++;
|
|
53
|
+
const worker = spawnWorker(instance);
|
|
54
|
+
worker.once('listening', () => {
|
|
55
|
+
startedWorkers.add(worker.id);
|
|
56
|
+
forkNext();
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
forkNext();
|
|
60
|
+
|
|
61
|
+
cluster.on('exit', (worker, code, signal) => {
|
|
62
|
+
console.info(`Process ${worker.process.pid} exited with code ${code} (${signal})`);
|
|
63
|
+
const instance = workerInstances.get(worker.id);
|
|
64
|
+
workerInstances.delete(worker.id);
|
|
65
|
+
if (startedWorkers.delete(worker.id)) {
|
|
66
|
+
// Worker had started successfully; restart it
|
|
67
|
+
const newWorker = spawnWorker(instance);
|
|
68
|
+
newWorker.once('listening', () => {
|
|
69
|
+
startedWorkers.add(newWorker.id);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
54
72
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
|
|
74
|
+
} else {
|
|
75
|
+
|
|
76
|
+
const app = 'serverOptions' in __SERVER__ ? new Server(__SERVER__.serverOptions) : new Server;
|
|
77
|
+
|
|
78
|
+
app.use(Server.static(path.join(__dirname, 'public'), { cacheControl: true }));
|
|
79
|
+
|
|
80
|
+
const server_env = {};
|
|
81
|
+
if ('default' in __SERVER__) await __SERVER__.default(app, server_env);
|
|
82
|
+
|
|
83
|
+
for (const [name, { path: pathname }] of _.toPairs(__applications__)) {
|
|
84
|
+
const { default: App } = __APPLICATIONS__[name];
|
|
85
|
+
if (!_.isFunction(App)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Failed to load client app "${name}": default export is not a function.\n` +
|
|
88
|
+
`Please ensure "${name}" exports a valid Frosty component as its default export.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const cssExists = fs.existsSync(path.join(__dirname, `public/css/${name}_bundle.css`));
|
|
92
|
+
const route = FrostyRoute(App, {
|
|
93
|
+
jsSrc: `/${name}_bundle.js`,
|
|
94
|
+
cssSrc: cssExists ? `/css/${name}_bundle.css` : undefined,
|
|
95
|
+
});
|
|
96
|
+
if (_.isEmpty(pathname) || pathname === '/') {
|
|
97
|
+
app.use(route);
|
|
98
|
+
} else {
|
|
99
|
+
app.use(pathname, route);
|
|
100
|
+
}
|
|
59
101
|
}
|
|
60
|
-
}
|
|
61
102
|
|
|
62
|
-
app.use((err, req, res, next) => {
|
|
63
|
-
|
|
64
|
-
});
|
|
103
|
+
app.use((err, req, res, next) => {
|
|
104
|
+
res.status(500).json(err instanceof Error ? { message: err.message } : err);
|
|
105
|
+
});
|
|
65
106
|
|
|
66
|
-
app.listen(PORT, () => console.info(`listening on port ${PORT}`));
|
|
107
|
+
app.listen(PORT, () => console.info(`Process ${process.pid} listening on port ${PORT}`));
|
|
108
|
+
}
|
|
@@ -42,6 +42,8 @@ export default async (env, argv) => {
|
|
|
42
42
|
CONFIG_FILE = 'server.config.js',
|
|
43
43
|
INPUT_FILE,
|
|
44
44
|
PORT = 8080,
|
|
45
|
+
NUM_WORKERS,
|
|
46
|
+
INSTANCE_VAR,
|
|
45
47
|
} = env;
|
|
46
48
|
|
|
47
49
|
const serverConfig = await (async () => {
|
|
@@ -253,7 +255,7 @@ export default async (env, argv) => {
|
|
|
253
255
|
optimization: webpackOptimization({ server: true }),
|
|
254
256
|
plugins: _.compact([
|
|
255
257
|
...webpackPlugins,
|
|
256
|
-
new webpack.EnvironmentPlugin({ PORT }),
|
|
258
|
+
new webpack.EnvironmentPlugin({ PORT, NUM_WORKERS, INSTANCE_VAR }),
|
|
257
259
|
new webpack.DefinePlugin({
|
|
258
260
|
__applications__: JSON.stringify(_.mapValues(inputs, x => ({
|
|
259
261
|
path: x.basepath,
|