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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frosty",
3
- "version": "0.0.148",
3
+ "version": "0.0.150",
4
4
  "main": "dist/index",
5
5
  "module": "dist/index",
6
6
  "types": "dist/index",
@@ -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
- const app = 'serverOptions' in __SERVER__ ? new Server(__SERVER__.serverOptions) : new Server;
36
+ if (cluster.isPrimary && NUM_WORKERS > 1) {
37
+ console.info(`Primary ${process.pid} is running`);
36
38
 
37
- app.use(Server.static(path.join(__dirname, 'public'), { cacheControl: true }));
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 server_env = {};
40
- if ('default' in __SERVER__) await __SERVER__.default(app, server_env);
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
- for (const [name, { path: pathname }] of _.toPairs(__applications__)) {
43
- const { default: App } = __APPLICATIONS__[name];
44
- if (!_.isFunction(App)) {
45
- throw new Error(
46
- `Failed to load client app "${name}": default export is not a function.\n` +
47
- `Please ensure "${name}" exports a valid Frosty component as its default export.`
48
- );
49
- }
50
- const cssExists = fs.existsSync(path.join(__dirname, `public/css/${name}_bundle.css`));
51
- const route = FrostyRoute(App, {
52
- jsSrc: `/${name}_bundle.js`,
53
- cssSrc: cssExists ? `/css/${name}_bundle.css` : undefined,
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
- if (_.isEmpty(pathname) || pathname === '/') {
56
- app.use(route);
57
- } else {
58
- app.use(pathname, route);
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
- res.status(500).json(err instanceof Error ? { message: err.message } : err);
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,