fix: dynamic web ports

This commit is contained in:
cha0s 2024-02-15 04:55:47 -06:00
parent 31fd7b46a4
commit 1b13374b10
14 changed files with 468 additions and 360 deletions

View File

@ -37,8 +37,7 @@ export const hooks = {
await createWindow(flecks);
},
'@flecks/electron/server.window': async (win, flecks) => {
const {public: $$public} = flecks.get('@flecks/web');
const {installExtensions, url = `http://${$$public}`} = flecks.get('@flecks/electron');
const {installExtensions, url} = flecks.get('@flecks/electron');
if (installExtensions) {
const installer = __non_webpack_require__('electron-devtools-installer');
const {default: installExtension} = installer;
@ -48,7 +47,17 @@ export const hooks = {
.flat(),
]);
}
await win.loadURL(url);
let realUrl = url;
if (!realUrl) {
realUrl = `http://${flecks.web.public}`;
while (!flecks.web.server) {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 50);
});
}
}
await win.loadURL(realUrl);
},
'@flecks/repl.context': (flecks) => ({
electron: {

View File

@ -5,7 +5,17 @@ import {createBrowser, connectPage} from './connect';
export function withWeb(task, options) {
return async function withWeb() {
const optionsWithTask = {...options, task: this};
const server = await startServer(optionsWithTask);
const server = await startServer({
...optionsWithTask,
opts: {
...optionsWithTask.opts,
env: {
...optionsWithTask.opts?.env,
FLECKS_ENV__flecks_web__port: '0',
FLECKS_ENV__flecks_web__devPort: '0',
},
},
});
const socket = await server.waitForSocket(optionsWithTask);
if (options.beforeConnect) {
await options.beforeConnect({server, socket});
@ -13,7 +23,7 @@ export function withWeb(task, options) {
const start = Date.now();
const previousTimeout = this.timeout();
this.timeout(0);
const {payload: config} = await socket.send({type: 'config.get', payload: '@flecks/web'});
const {payload} = await socket.send({type: 'web.public'});
this.timeout(previousTimeout + (Date.now() - start));
const {browser, page} = await createBrowser(optionsWithTask);
if (options.beforePage) {
@ -24,7 +34,7 @@ export function withWeb(task, options) {
socket,
});
}
const response = await connectPage(page, `http://${config.public}`, optionsWithTask);
const response = await connectPage(page, `http://${payload}`, optionsWithTask);
let taskError;
try {
await task({

View File

@ -1,314 +1,3 @@
const {stat, unlink} = require('fs/promises');
const {join} = require('path');
const {Flecks} = require('@flecks/core/build/flecks');
const Build = require('@flecks/build/build/build');
const {regexFromExtensions} = require('@flecks/build/src/server');
const {binaryPath, spawnWith} = require('@flecks/core/src/server');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
exports.hooks = {
'@flecks/build.config': async (target, config, env, argv, flecks) => {
const isProduction = 'production' === argv.mode;
let finalLoader;
switch (target) {
case 'fleck': {
finalLoader = {loader: MiniCssExtractPlugin.loader};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
break;
}
case 'server': {
finalLoader = {loader: MiniCssExtractPlugin.loader, options: {emit: false}};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
break;
}
case 'test': {
finalLoader = {loader: MiniCssExtractPlugin.loader};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
break;
}
case 'web': {
if (isProduction) {
finalLoader = {loader: MiniCssExtractPlugin.loader};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
}
else {
finalLoader = {loader: 'style-loader'};
}
break;
}
default: break;
}
const buildOneOf = (test, loaders, cssOptions = {}) => ({
test,
use: [
finalLoader,
{
loader: 'css-loader',
options: {
...cssOptions,
importLoaders: loaders.length,
},
},
...loaders,
'source-map-loader',
],
});
const stylesWithModulesRule = (extensions, loaders) => ({
oneOf: [
// `.module.*` must match first.
buildOneOf(
regexFromExtensions(extensions.map((ext) => `module${ext}`)),
loaders,
{
modules: {
localIdentName: isProduction
? '[hash:base64:5]'
: '[path][name]__[local]',
},
},
),
buildOneOf(
regexFromExtensions(extensions),
loaders,
),
],
});
const postcss = {
loader: 'postcss-loader',
options: {
postcssOptions: {
config: await flecks.resolveBuildConfig('postcss.config.js'),
},
},
};
// Originally separated because Sass can't handle incoming source maps, but probably more
// performant with 3rd-party CSS anyway.
config.module.rules.push(stylesWithModulesRule(['.css'], [postcss]));
config.module.rules.push(stylesWithModulesRule(['.sass', '.scss'], [postcss, 'sass-loader']));
// Fonts.
if (isProduction) {
config.module.rules.push({
generator: {
filename: 'assets/[hash][ext][query]',
},
test: /\.(eot|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset',
});
}
else {
config.module.rules.push({
test: /\.(eot|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset/inline',
});
}
// Images.
config.module.rules.push({
generator: {
filename: 'assets/[hash][ext][query]',
},
test: /\.(ico|png|jpg|jpeg|gif|svg|webp)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset',
});
},
'@flecks/build.config.alter': async (configs, env, argv, flecks) => {
const isProduction = 'production' === argv.mode;
// Only build vendor in dev.
if (configs['web-vendor']) {
if (isProduction) {
delete configs['web-vendor'];
}
// Only build if something actually changed.
const dll = flecks.get('@flecks/web.dll');
if (dll.length > 0) {
const manifest = join(
FLECKS_CORE_ROOT,
'node_modules',
'.cache',
'@flecks',
'web',
'vendor',
'manifest.json',
);
let timestamp = 0;
try {
const stats = await stat(manifest);
timestamp = stats.mtime;
}
// eslint-disable-next-line no-empty
catch (error) {}
let latest = 0;
for (let i = 0; i < dll.length; ++i) {
const path = dll[i];
try {
// eslint-disable-next-line no-await-in-loop
const stats = await stat(join(FLECKS_CORE_ROOT, 'node_modules', path));
if (stats.mtime > latest) {
latest = stats.mtime;
}
}
// eslint-disable-next-line no-empty
catch (error) {}
}
if (timestamp > latest) {
delete configs['web-vendor'];
}
else if (timestamp > 0) {
await unlink(manifest);
}
}
}
// Bail if there's no web build.
if (!configs.web) {
return;
}
// Bail if the build isn't watching.
if (!process.argv.find((arg) => 'watch' === arg)) {
return;
}
// Otherwise, spawn `webpack-dev-server` (WDS).
const cmd = [
await binaryPath('webpack', '@flecks/build'),
'serve',
'--mode', 'development',
'--hot',
'--config', await flecks.resolveBuildConfig('fleckspack.config.js'),
];
spawnWith(
cmd,
{
env: {
FLECKS_CORE_BUILD_LIST: 'web',
},
},
);
// Remove the build config since we're handing off to WDS.
delete configs.web;
},
'@flecks/build.files': () => [
/**
* Template file used to generate the client HTML.
*
* See: https://github.com/jantimon/html-webpack-plugin/blob/main/docs/template-option.md
*/
'template.ejs',
/**
* PostCSS config file.
*
* See: https://github.com/postcss/postcss#usage
*/
'postcss.config.js',
/**
* Web client build configuration. See: https://webpack.js.org/configuration/
*/
'web.webpack.config.js',
/**
* Web vendor DLL build configuration. See: https://webpack.js.org/configuration/
*/
'web-vendor.webpack.config.js',
],
'@flecks/core.config': () => ({
/**
* The ID of the root element on the page.
*/
appMountId: 'root',
/**
* Base tag path.
*/
base: '/',
/**
* (webpack-dev-server) Disable the host check.
*
* See: https://github.com/webpack/webpack-dev-server/issues/887
*/
devDisableHostCheck: false,
/**
* (webpack-dev-server) Host to bind.
*/
devHost: 'localhost',
/**
* (webpack-dev-server) Port to bind.
*/
devPort: undefined,
/**
* (webpack-dev-server) Public path to serve.
*
* Defaults to `flecks.get('@flecks/web.public')`.
*/
devPublic: undefined,
/**
* (webpack-dev-server) Webpack stats output.
*/
devStats: {
preset: 'minimal',
},
/**
* Modules to externalize using `webpack.DllPlugin`.
*/
dll: [],
/**
* Host to bind.
*/
host: '0.0.0.0',
/**
* Path to icon.
*/
icon: '',
/**
* Port to bind.
*/
port: 32340,
/**
* Meta tags.
*/
meta: {
charset: 'utf-8',
viewport: 'width=device-width, user-scalable=no',
},
/**
* Public path to server.
*/
public: 'localhost:32340',
/**
* Webpack stats configuration.
*/
stats: {
preset: 'minimal',
},
/**
* HTML title.
*/
title: '[@flecks/core.id]',
/**
* Proxies to trust.
*
* See: https://www.npmjs.com/package/proxy-addr
*/
trust: false,
}),
'@flecks/build.targets': (flecks) => [
'web',
...(flecks.get('@flecks/web.dll').length > 0 ? ['web-vendor'] : []),
],
'@flecks/build.targets.alter': (targets) => {
// Don't build if there's a fleck target.
if (targets.has('fleck')) {
targets.delete('web');
}
},
'@flecks/fleck.packageJson': (json, compilation) => {
if (Object.keys(compilation.assets).some((filename) => filename.match(/^assets\//))) {
json.files.push('assets');
}
},
'@flecks/server.runtime': async (flecks) => {
const {config} = await Build.from({
config: flecks.config,
platforms: ['client', '!server'],
});
return JSON.stringify(config);
},
};
exports.hooks = Flecks.hooks(require.context('./hooks'));

View File

@ -0,0 +1,73 @@
const {stat, unlink} = require('fs/promises');
const {join} = require('path');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
exports.hook = async (configs, env, argv, flecks) => {
const isProduction = 'production' === argv.mode;
// Only build vendor in dev.
if (configs['web-vendor']) {
if (isProduction) {
delete configs['web-vendor'];
}
// Only build if something actually changed.
const dll = flecks.get('@flecks/web.dll');
if (dll.length > 0) {
const manifest = join(
FLECKS_CORE_ROOT,
'node_modules',
'.cache',
'@flecks',
'web',
'vendor',
'manifest.json',
);
let timestamp = 0;
try {
const stats = await stat(manifest);
timestamp = stats.mtime;
}
// eslint-disable-next-line no-empty
catch (error) {}
let latest = 0;
for (let i = 0; i < dll.length; ++i) {
const path = dll[i];
try {
// eslint-disable-next-line no-await-in-loop
const stats = await stat(join(FLECKS_CORE_ROOT, 'node_modules', path));
if (stats.mtime > latest) {
latest = stats.mtime;
}
}
// eslint-disable-next-line no-empty
catch (error) {}
}
if (timestamp > latest) {
delete configs['web-vendor'];
}
else if (timestamp > 0) {
await unlink(manifest);
}
}
}
// Bail if there's no web server build.
const {server} = configs;
if (!configs.web || !server) {
return;
}
// Bail if the build isn't watching.
if (!process.argv.find((arg) => 'watch' === arg)) {
return;
}
// Send the build details to the server node process so it can spawn the dev server.
const plugin = server.plugins.find(({pluginName}) => pluginName === 'StartServerPlugin');
if (plugin) {
plugin.options.env.FLECKS_WEB_DEV_SERVER = (
await flecks.resolveBuildConfig('fleckspack.config.js')
);
// Remove the build config since we're handing off to the server.
delete configs.web;
}
};

View File

@ -0,0 +1,106 @@
const {regexFromExtensions} = require('@flecks/build/src/server');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
exports.hook = async (target, config, env, argv, flecks) => {
const isProduction = 'production' === argv.mode;
let finalLoader;
switch (target) {
case 'fleck': {
finalLoader = {loader: MiniCssExtractPlugin.loader};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
break;
}
case 'server': {
finalLoader = {loader: MiniCssExtractPlugin.loader, options: {emit: false}};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
break;
}
case 'test': {
finalLoader = {loader: MiniCssExtractPlugin.loader};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
break;
}
case 'web': {
if (isProduction) {
finalLoader = {loader: MiniCssExtractPlugin.loader};
config.plugins.push(new MiniCssExtractPlugin({filename: 'assets/[name].css'}));
}
else {
finalLoader = {loader: 'style-loader'};
}
break;
}
default: break;
}
const buildOneOf = (test, loaders, cssOptions = {}) => ({
test,
use: [
finalLoader,
{
loader: 'css-loader',
options: {
...cssOptions,
importLoaders: loaders.length,
},
},
...loaders,
'source-map-loader',
],
});
const stylesWithModulesRule = (extensions, loaders) => ({
oneOf: [
// `.module.*` must match first.
buildOneOf(
regexFromExtensions(extensions.map((ext) => `module${ext}`)),
loaders,
{
modules: {
localIdentName: isProduction
? '[hash:base64:5]'
: '[path][name]__[local]',
},
},
),
buildOneOf(
regexFromExtensions(extensions),
loaders,
),
],
});
const postcss = {
loader: 'postcss-loader',
options: {
postcssOptions: {
config: await flecks.resolveBuildConfig('postcss.config.js'),
},
},
};
// Originally separated because Sass can't handle incoming source maps, but probably more
// performant with 3rd-party CSS anyway.
config.module.rules.push(stylesWithModulesRule(['.css'], [postcss]));
config.module.rules.push(stylesWithModulesRule(['.sass', '.scss'], [postcss, 'sass-loader']));
// Fonts.
if (isProduction) {
config.module.rules.push({
generator: {
filename: 'assets/[hash][ext][query]',
},
test: /\.(eot|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset',
});
}
else {
config.module.rules.push({
test: /\.(eot|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset/inline',
});
}
// Images.
config.module.rules.push({
generator: {
filename: 'assets/[hash][ext][query]',
},
test: /\.(ico|png|jpg|jpeg|gif|svg|webp)(\?v=\d+\.\d+\.\d+)?$/,
type: 'asset',
});
};

View File

@ -0,0 +1,22 @@
exports.hook = () => [
/**
* Template file used to generate the client HTML.
*
* See: https://github.com/jantimon/html-webpack-plugin/blob/main/docs/template-option.md
*/
'template.ejs',
/**
* PostCSS config file.
*
* See: https://github.com/postcss/postcss#usage
*/
'postcss.config.js',
/**
* Web client build configuration. See: https://webpack.js.org/configuration/
*/
'web.webpack.config.js',
/**
* Web vendor DLL build configuration. See: https://webpack.js.org/configuration/
*/
'web-vendor.webpack.config.js',
];

View File

@ -0,0 +1,6 @@
exports.hook = (targets) => {
// Don't build if there's a fleck target.
if (targets.has('fleck')) {
targets.delete('web');
}
};

View File

@ -0,0 +1,4 @@
exports.hook = (flecks) => [
'web',
...(flecks.get('@flecks/web.dll').length > 0 ? ['web-vendor'] : []),
];

View File

@ -0,0 +1,69 @@
exports.hook = () => ({
/**
* The ID of the root element on the page.
*/
appMountId: 'root',
/**
* Base tag path.
*/
base: '/',
/**
* (webpack-dev-server) Host to bind. Defaults to `flecks.web.host`
*/
devHost: undefined,
/**
* (webpack-dev-server) Port to bind. Defaults to random port.
*/
devPort: 0,
/**
* (webpack-dev-server) Webpack stats output.
*/
devStats: {
preset: 'minimal',
},
/**
* Modules to externalize using `webpack.DllPlugin`.
*/
dll: [],
/**
* Host to bind.
*
* Defaults to 'localhost' in development and '0.0.0.0' in production.
*/
host: undefined,
/**
* Path to icon.
*/
icon: '',
/**
* Port to bind.
*/
port: 3000,
/**
* Meta tags.
*/
meta: {
charset: 'utf-8',
viewport: 'width=device-width, user-scalable=no',
},
/**
* Public path to server. Defaults to `[@flecks/web.host]:[@flecks/web.port]`.
*/
public: undefined,
/**
* Webpack stats configuration.
*/
stats: {
preset: 'minimal',
},
/**
* HTML title.
*/
title: '[@flecks/core.id]',
/**
* Proxies to trust.
*
* See: https://www.npmjs.com/package/proxy-addr
*/
trust: false,
});

View File

@ -0,0 +1,5 @@
exports.hook = (json, compilation) => {
if (Object.keys(compilation.assets).some((filename) => filename.match(/^assets\//))) {
json.files.push('assets');
}
};

View File

@ -0,0 +1,9 @@
const Build = require('@flecks/build/build/build');
exports.hook = async (flecks) => {
const {config} = await Build.from({
config: flecks.config,
platforms: ['client', '!server'],
});
return JSON.stringify(config);
};

View File

@ -22,19 +22,16 @@ module.exports = async (env, argv, flecks) => {
const {
appMountId,
base,
devHost,
devPort,
devStats,
meta,
icon,
port,
title,
} = flecks.get('@flecks/web');
const isProduction = 'production' === argv.mode;
const plugins = [
// Environment.
new webpack.EnvironmentPlugin({
FLECKS_CORE_BUILD_TARGET: 'web',
new webpack.DefinePlugin({
'process.env.FLECKS_CORE_BUILD_TARGET': "'web'",
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
@ -168,8 +165,6 @@ module.exports = async (env, argv, flecks) => {
disableDotRule: true,
},
hot: false,
host: devHost,
port: devPort || (port + 1),
},
devtool: 'source-map',
entry,

View File

@ -1,8 +1,10 @@
import {createReadStream} from 'fs';
import {createServer, ServerResponse} from 'http';
import {join} from 'path';
import {PassThrough, Transform} from 'stream';
import {D} from '@flecks/core';
import {binaryPath, spawnWith} from '@flecks/core/server';
import bodyParser from 'body-parser';
import compression from 'compression';
import express from 'express';
@ -10,7 +12,9 @@ import httpProxy from 'http-proxy';
const {
FLECKS_CORE_ROOT = process.cwd(),
FLECKS_WEB_DEV_SERVER,
NODE_ENV,
TERM,
} = process.env;
const debug = D('@flecks/web/server/http');
@ -20,18 +24,15 @@ const deliverHtmlStream = async (stream, flecks, req, res) => {
};
export const createHttpServer = async (flecks) => {
const {trust} = flecks.get('@flecks/web');
const {
devHost,
devPort,
host,
public: publicConfig,
port,
trust,
} = flecks.get('@flecks/web');
const app = express();
app.set('trust proxy', trust);
const httpServer = createServer(app);
httpServer.app = app;
flecks.web.server = httpServer;
// Body parser.
app.use(bodyParser.json());
// Compression. heheh
@ -43,25 +44,121 @@ export const createHttpServer = async (flecks) => {
const routes = (await Promise.all(flecks.invokeFlat('@flecks/web.routes'))).flat();
debug('routes: %O', routes);
routes.forEach(({method, path, middleware}) => app[method](path, routeMiddleware, middleware));
const {host} = flecks.web;
await new Promise((resolve, reject) => {
const args = [port];
if (host) {
args.push(host);
}
args.push(async (error) => {
if (error) {
reject(error);
return;
}
await flecks.invokeSequentialAsync('@flecks/web/server.up', httpServer);
const actualPort = 0 === port ? httpServer.address().port : port;
debug(
'HTTP server up @ %s!',
[host, actualPort].filter((e) => !!e).join(':'),
);
if ('undefined' === typeof publicConfig) {
flecks.web.public = [host, actualPort].join(':');
}
resolve();
});
debug('httpServer.listen(...%O)', args.slice(0, -1));
httpServer.listen(...args);
});
// In development mode, create a proxy to the webpack-dev-server.
if ('production' !== NODE_ENV) {
const {
devHost,
devPort,
} = flecks.get('@flecks/web');
const wdsHost = devHost || host;
let wdsPort;
if (0 === devPort) {
wdsPort = 0;
}
else if (devPort) {
wdsPort = devPort;
}
else {
wdsPort = httpServer.address().port + 1;
}
if (FLECKS_WEB_DEV_SERVER) {
// Otherwise, spawn `webpack-dev-server` (WDS).
const cmd = [
await binaryPath('webpack', '@flecks/build'),
'serve',
'--mode', 'development',
'--hot',
'--host', wdsHost,
'--port', wdsPort,
'--config', FLECKS_WEB_DEV_SERVER,
];
const wds = spawnWith(
cmd,
{
env: {
DEBUG_COLORS: 'dumb' !== TERM,
FLECKS_CORE_BUILD_LIST: 'web',
FORCE_COLOR: 'dumb' !== TERM,
},
stdio: 0 === wdsPort ? 'pipe' : 'inherit',
},
);
if (0 === wdsPort) {
class ParsePort extends Transform {
constructor() {
super();
let resolve;
let reject;
this.port = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
this.resolve = resolve;
this.on('error', reject);
}
// eslint-disable-next-line no-underscore-dangle, class-methods-use-this
_transform(chunk, encoding, done) {
this.push(chunk);
const matches = chunk.toString().match(/http:\/\/.*\//g);
if (!matches) {
done();
return;
}
const [port] = matches
.slice(0, 1)
.map((match) => new URL(match))
.map(({port}) => port);
done();
this.resolve(port);
}
}
const parsePort = new ParsePort();
const stderr = new PassThrough();
wds.stderr.pipe(parsePort).pipe(stderr);
stderr.pipe(process.stderr);
wds.stdout.pipe(process.stdout);
wdsPort = await parsePort.port;
parsePort.unpipe(stderr);
stderr.unpipe(process.stderr);
wds.stderr.pipe(process.stderr);
}
}
const proxy = httpProxy.createProxyServer({
secure: false,
target: `http://${devHost}:${devPort || (port + 1)}`,
target: `http://${wdsHost}:${wdsPort}`,
});
proxy.on('proxyRes', async (proxyRes, req, res) => {
res.statusCode = proxyRes.statusCode;
// HTML.
if (proxyRes.headers['content-type']?.match('text/html')) {
// Tests bypass middleware and stream processing.
const {pathname} = new URL(req.url, 'https://example.org/');
if ('/tests.html' === pathname) {
if (!res.headersSent) {
res.setHeader('Content-Type', proxyRes.headers['content-type']);
}
proxyRes.pipe(res);
return;
}
routeMiddleware(req, res, (error) => {
if (error) {
// eslint-disable-next-line no-console
@ -127,23 +224,7 @@ export const createHttpServer = async (flecks) => {
}
});
}
return new Promise((resolve, reject) => {
const args = [port];
if (host) {
args.push(host);
}
args.push(async (error) => {
if (error) {
reject(error);
return;
}
await flecks.invokeSequentialAsync('@flecks/web/server.up', httpServer);
debug('HTTP server up @ %s!', [host, port].filter((e) => !!e).join(':'));
resolve();
});
debug('httpServer.listen(...%O)', args.slice(0, -1));
httpServer.listen(...args);
});
flecks.web.server = httpServer;
};
export const destroyHttpServer = (httpServer) => {

View File

@ -32,7 +32,27 @@ export const hooks = {
return routes;
},
'@flecks/web/server.stream.html': inlineConfig,
'@flecks/server.up': (flecks) => createHttpServer(flecks),
'@flecks/server.test.socket': async (action, socket, flecks) => {
while (!flecks.web.server) {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 50);
});
}
const {meta, type} = action;
switch (type) {
case 'web.public':
socket.write(JSON.stringify({
meta,
payload: flecks.web.public,
}));
break;
default:
}
},
'@flecks/server.up': async (flecks) => {
await createHttpServer(flecks);
},
};
export const mixin = (Flecks) => class FlecksWithWeb extends Flecks {
@ -40,7 +60,17 @@ export const mixin = (Flecks) => class FlecksWithWeb extends Flecks {
constructor(runtime) {
super(runtime);
if (!this.web) {
this.web = {config: runtime['@flecks/web'], server: undefined};
const {
host = 'production' === NODE_ENV ? '0.0.0.0' : 'localhost',
port,
public: httpPublic,
} = runtime.config['@flecks/web'];
this.web = {
config: runtime['@flecks/web'],
host,
public: httpPublic || [host, port].join(':'),
server: undefined,
};
}
}