Devenv: Improve webpack readiness check
This commit is contained in:
@@ -10,16 +10,16 @@ local_resource(
|
||||
|
||||
local_resource(
|
||||
'yarn start',
|
||||
cmd='rm -rf public/build/assets-manifest.json',
|
||||
serve_cmd='yarn start:noLint',
|
||||
serve_cmd='yarn start:noLint --env httpReadinessPort=9080',
|
||||
resource_deps=['yarn install'],
|
||||
|
||||
# Note: this doesn't seem to work as expected - the assets-manifest is somehow created before
|
||||
# the webpack build is complete?
|
||||
readiness_probe=probe(
|
||||
initial_delay_secs=10,
|
||||
period_secs=1,
|
||||
exec=exec_action(["bash", "-c", "stat public/build/assets-manifest.json"]),
|
||||
failure_threshold=1,
|
||||
http_get=http_get_action(
|
||||
host='localhost',
|
||||
port=9080,
|
||||
),
|
||||
),
|
||||
allow_parallel=True,
|
||||
labels=["local"]
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
const http = require('http');
|
||||
|
||||
/**
|
||||
* Starts a HTTP server that provides a readiness endpoint indicating the build status.
|
||||
* The server responds with:
|
||||
* - 200 when the build is complete and successful
|
||||
* - 503 when the build is in progress, for both the initial build and subsequent rebuilds when in watch mode
|
||||
* - 500 when the build has failed
|
||||
*
|
||||
* This is used in the devenv/frontend-service Tiltfile to determine when the frontend build is ready.
|
||||
*/
|
||||
class HttpReadinessPlugin {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {number} options.port
|
||||
* @param {string} [options.host]
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this.port = options.port;
|
||||
this.host = options.host || 'localhost';
|
||||
this.isReady = false;
|
||||
this.server = null;
|
||||
this.hasError = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('webpack').Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
// Start HTTP server on first compilation
|
||||
compiler.hooks.beforeCompile.tapAsync('HttpReadinessPlugin', (params, callback) => {
|
||||
if (!this.server) {
|
||||
this.startServer();
|
||||
}
|
||||
callback();
|
||||
});
|
||||
|
||||
// Mark as building when compilation starts
|
||||
compiler.hooks.compile.tap('HttpReadinessPlugin', () => {
|
||||
this.isReady = false;
|
||||
this.hasError = false;
|
||||
console.log('[HttpReadinessPlugin] Build started - readiness endpoint returning 503');
|
||||
});
|
||||
|
||||
// Mark as ready when compilation completes successfully
|
||||
compiler.hooks.done.tap('HttpReadinessPlugin', (stats) => {
|
||||
if (stats.hasErrors()) {
|
||||
this.hasError = true;
|
||||
this.isReady = false;
|
||||
console.log('[HttpReadinessPlugin] Build completed with errors - readiness endpoint returning 500');
|
||||
} else {
|
||||
this.isReady = true;
|
||||
this.hasError = false;
|
||||
console.log('[HttpReadinessPlugin] Build completed successfully - readiness endpoint returning 200');
|
||||
}
|
||||
});
|
||||
|
||||
// Mark as error on compilation failure
|
||||
compiler.hooks.failed.tap('HttpReadinessPlugin', () => {
|
||||
this.hasError = true;
|
||||
this.isReady = false;
|
||||
console.log('[HttpReadinessPlugin] Build failed - readiness endpoint returning 500');
|
||||
});
|
||||
|
||||
// Close server on shutdown
|
||||
compiler.hooks.shutdown.tap('HttpReadinessPlugin', () => {
|
||||
this.stopServer();
|
||||
});
|
||||
}
|
||||
|
||||
startServer() {
|
||||
this.server = http.createServer((req, res) => {
|
||||
// Only respond to GET requests
|
||||
if (req.method !== 'GET') {
|
||||
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
||||
res.end('Method Not Allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Respond based on build state
|
||||
if (this.isReady) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('OK - Build ready');
|
||||
} else if (this.hasError) {
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
res.end('Error - Build failed');
|
||||
} else {
|
||||
res.writeHead(503, { 'Content-Type': 'text/plain' });
|
||||
res.end('Service Unavailable - Build in progress');
|
||||
}
|
||||
});
|
||||
|
||||
this.server.listen(this.port, this.host, () => {
|
||||
console.log(`[HttpReadinessPlugin] Readiness server listening on http://${this.host}:${this.port}`);
|
||||
});
|
||||
|
||||
this.server.on('error', (error) => {
|
||||
console.error(`[HttpReadinessPlugin] Server error:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
stopServer() {
|
||||
if (this.server) {
|
||||
this.server.close(() => {
|
||||
console.log('[HttpReadinessPlugin] Readiness server stopped');
|
||||
});
|
||||
this.server = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = HttpReadinessPlugin;
|
||||
@@ -14,6 +14,7 @@ const { merge } = require('webpack-merge');
|
||||
const WebpackBar = require('webpackbar');
|
||||
|
||||
const getEnvConfig = require('./env-util.js');
|
||||
const HttpReadinessPlugin = require('./plugins/HttpReadinessPlugin.js');
|
||||
const common = require('./webpack.common.js');
|
||||
const esbuildTargets = resolveToEsbuildTarget(browserslist(), { printUnknownTargets: false });
|
||||
// esbuild-loader 3.0.0+ requires format to be set to prevent it
|
||||
@@ -48,6 +49,8 @@ function scenesModule() {
|
||||
const envConfig = getEnvConfig();
|
||||
|
||||
module.exports = (env = {}) => {
|
||||
console.log('env', env);
|
||||
const httpReadinessPort = parseInt(env.httpReadinessPort, 10);
|
||||
return merge(common, {
|
||||
devtool: 'source-map',
|
||||
mode: 'development',
|
||||
@@ -170,6 +173,12 @@ module.exports = (env = {}) => {
|
||||
name: 'Grafana',
|
||||
}),
|
||||
new EnvironmentPlugin(envConfig),
|
||||
httpReadinessPort
|
||||
? new HttpReadinessPlugin({
|
||||
port: httpReadinessPort,
|
||||
host: 'localhost',
|
||||
})
|
||||
: new DefinePlugin({}), // bogus plugin to satisfy webpack API
|
||||
],
|
||||
|
||||
stats: 'minimal',
|
||||
|
||||
Reference in New Issue
Block a user