Wait Until Webpack Dev Server Is Ready - javascript

Requirements
I need to run the webpack-dev-server and wait until the server is ready to serve pages.
Solution
// Start a webpack-dev-server
new WebpackDevServer(webpack(myConfig), {
publicPath: myConfig.output.publicPath,
hot: true,
historyApiFallback: true,
// It suppress error shown in console, so it has to be set to false.
quiet: false,
// It suppress everything except error, so it has to be set to false as well
// to see success build.
noInfo: false,
stats: {
// Config for minimal console.log mess.
assets: false,
colors: true,
version: false,
hash: false,
timings: false,
chunks: false,
chunkModules: false
}
}).listen(3000, '0.0.0.0', function(err) {
if(err) throw new gutil.PluginError('webpack-dev-server', err);
gutil.log('[webpack-dev-server]', 'http://0.0.0.0:3000/webpack-dev-server/index.html');
//this is to ensure that end to end test wouldn't start running until the server is ready
http.get({
hostname: '0.0.0.0',
port: 3000,
path: '/',
agent: false // create a new agent just for this one request
}, (/*res*/) => {
// Do stuff with response
done();
});
});
Problem
On Linux a have a wait until the server is ready. On Windows I get an exception because there is no wait and the server is not ready
C:\development\ucms-react>gulp webpack-dev-server [11:03:01] Requiring
external module babel-register [11:03:07] Using gulpfile
C:\development\ucms-react\gulpfile.babel.js [11:03:07] Starting
'webpack-dev-server'... [11:03:07] [webpack-dev-server]
http://0.0.0.0:3000/webpack-dev-server/index.html error events.js:141
throw er; // Unhandled 'error' event
^
Error: connect EADDRNOTAVAIL 0.0.0.0:3000
at Object.exports._errnoException (util.js:907:11)
at exports._exceptionWithHostPort (util.js:930:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1077:14)
C:\development\ucms-react>
How can I solve this issue?

The other answer here by Brett DeWoody is partially correct. It does properly wait until the server is started. However this does not wait for the bundle to be created. So if you ask webpack for a page at this point, it will return something akin to "still compiling".
To wait for the bundle to be fully compiled (and that the server is listening) before trying to access a page, you'll need something like this:
function makeServer(config) {
return new Promise((resolve, reject) => {
const compiler = webpack(config);
let compiled = false;
let listening = false;
compiler.hooks.done.tap('IDoNotUnderstandWhatThisStringIsForButItCannotBeEmpty', () => {
// console.log('compiled');
if (listening) resolve(server);
else compiled = true;
});
const server = new WebpackDevServer(compiler, config.devServer);
server.listen(port, '0.0.0.0', err => {
if (err) return reject(err);
// console.log('listening');
if (compiled) resolve(server);
else listening = true;
});
});
}
Then, just like Brett's answer, await the result:
var server = await makeServer(config);
Or without async/await:
makeServer(config).then(server => {
// Do something with server
});

Here's another solution (inspired by #CameronTacklind's answer). The setImmediate makes the log output show up below the initial compiler log output.
const PORT = process.env.PORT || 3000
const compiler = webpack(config)
new WebpackDevServer(compiler).listen(PORT, '0.0.0.0', err => {
if (err) {
console.log(err)
}
})
compiler.hooks.done.tap('done', () => {
setImmediate(() => {
console.log()
console.log(`Running at http://localhost:${PORT}`)
})
})
UPDATE
Here's an alternative version that doesn't require a custom server script—you can just add this to your webpack config options. It also preserves the built-in feature of automatically using a different port if the default one isn't available...of course, if you'd rather specify your port, you don't have to use that part.
const chalk = require('chalk')
...
module.exports = {
....
devServer: {
onListening: server => {
const { port } = server.listeningApp.address()
server.compiler.hooks.done.tap('done', () => {
setImmediate(() => {
console.log()
console.log(
chalk.cyan.bold(`Running at http://localhost:${port}`)
)
})
})
},
}
}

One way to handle this is to wrap the server setup in a function that returns a Promise. The Promise is resolved when the server connects, or is rejected if there's an error.
Here's a simplified example:
function startServer() {
return new Promise((resolve, reject) => {
new WebpackDevServer(webpack(myConfig), {
// Do stuff
}).listen(3000, '0.0.0.0', function(err) {
resolve();
}).on('error', (error) => {
reject(error);
});
});
}
and then provide a callback for when the server is started:
var server = startServer();
server.then(function() {
// Run tests
});

Related

checking on url status before fulfilling a promise

I have an atypical use case for the cypress test runner, where I need to start the server from within the cypress.
I'm doing that by defining the before:spechook in cypress plugins/index.jslike so:
module.exports = (on, config) => {
on('before:spec', async(spec) => {
// the promise will be awaited before the runner continues with the spec
return new Promise((resolve, reject) => {
startServer();
// keep checking that the url accessible, when it is: resolve(null)
while (true) {
getStatus(function(statusCode) {
if (statusCode === 200)
break
})
};
resolve(null)
I'm struggling to implement this while loop that is supposed to keep checking if the url is accessible before fulfilling the before:spec promise.
I have the following function for checking the url:
function getStatus (callback) {
const options = {
hostname: 'localhost',
port: 8080,
path: '/',
method: 'GET'
}
const req = http.request(options, res => {
console.log(`statusCode: ${res.statusCode}`)
callback(res.statusCode}
})
req.on('error', error => {
console.error("ERROR",error)
})
req.end()
};
Any help implementing that loop or other suggestions how to achieve the task of checking the url before fulfilling the before:specpromise appreciated.
Ideally your startServer function should return a promise and in the before:spec hook you simply await startServer();. Or at least should accept a callback which called when the server initialisation is complete. But lets assume that is not possible, so here is another solution for the given code:
function getStatus() {
return new Promise((resolve, reject) => {
const options = {
hostname: 'localhost',
port: 8080,
path: '/',
method: 'GET'
}
const req = http.request(options, res => {
console.log(`statusCode: ${res.statusCode}`)
resolve(res.statusCode);
})
req.on('error', error => {
console.error("ERROR", error);
reject(error);
})
req.end()
});
};
module.exports = (on, config) => {
on('before:spec', async (spec) => {
// the promise will be awaited before the runner continues with the spec
startServer();
// keep checking that the url accessible, when it is: resolve(null)
while (await getStatus() !== 200) {
await (new Promise(resolve => setTimeout(resolve, 50)));
}
});
}
Your original try with while loop had serious flaws as you can't break like that and you flooded your server with requests.
There is only one strange part in the current one, await (new Promise(resolve => setTimeout(resolve, 50))); . This is simply to prevent flooding, lets give 50ms if the service was not yet ready. If you know your service is really slower to start feel free to adjust this, but much lower values doesn't make much sense. Actually it is not even strictly necessary, as the condition in while loop ensures that only one request will be running at a time. But I felt a bit safer this way, pointless to try to server too often if it is still warming up.
Also please note that you may want to resolve(500) or omit resolve/reject in req.on('error') as I don't know if your server is immediately ready to return proper status code, it depends on the implementation of startServer.

Promisify function that does not exit

I am attempting to util.promisify the bonjour npm package. This is the original use case described in the docs:
var bonjour = require('bonjour')()
// advertise an HTTP server on port 3000
bonjour.publish({ name: 'My Web Server', type: 'http', port: 3000 })
// browse for all http services
bonjour.find({ type: 'http' }, function (service) {
console.log('Found an HTTP server:', service)
})
And this will not exit - bonjour.find() stays open looking for http servers.
I want to promisify this and successfully resolve the promise after scanning for servers. Something like:
var bonjour = require('bonjour')
const util = require('util');
// advertise an HTTP server on port 3000
bonjour().publish({ name: 'My Web Server', type: 'http', port: 3000 })
// promisify the 'find' function
const find = util.promisify(bonjour().find.bind(bonjour()));
(async () => {
try {
const content = await find({ type: 'http' });
console.log(content)
} catch (err) {
// It will always error, although, the value of 'err' is the correct content.
console.error('There was an error');
}
})();
As noted in the comments, it will always throw an error, even though the value of err is the desired output.
The promisify'd process expects an exit code 0, which I suspect isn't happening since the process doesn't return. Is this the right assumption? Does anyone have any other insights or solutions to get my promise to not throw an error but return the value that is currently being thrown?
Thanks!
That's because the bonjour callback does not comply with the Node.js callback signature (err, data) => {}. Indeed, bonjour.find's callback has the success value as first parameter. You could wrap it in a proxy function like this:
function findWrapper(options, func) {
function callback(data, err) {
func(err, data);
}
return bonjour().find(options, callback);
}
const find = util.promisify(findWrapper);

How to create api routes with Fastify and Nuxt?

Fastify changed a lot since the last time I played with it. Also, I read that we should use Nuxt serverMiddleware to add api routes but I couldn't find any example on how to proceed.
Below there is my index file to start Fastify. I tried adding a route in it before nuxt with this line:
fastify.register(require('./routesIndex'), { prefix: '/api' })
routesIndex.js as per the new fastify documentation:
async function routes(fastify, options) {
fastify.register(require('./fetchRemote'))
fastify.register(require('./domains'))
}
module.exports = routes
To be thorough, the domains.js file:
async function routes(fastify, options) {
const database = fastify.mongo.db('db')
const collection = database.collection('test')
fastify.get('/domains/list', async (request, reply) => {
return { hello: 'world' }
})
fastify.get('/domains/:id', async (request, reply) => {
const result = await collection.findOne({ id: request.params.id })
if (result.value === null) {
throw new Error('Invalid value')
}
return result.value
})
}
module.exports = routes
server/index.js:
const { Nuxt, Builder } = require('nuxt')
const fastify = require('fastify')({
logger: true
})
fastify.register(require('./db'), {
url: 'mongodb://localhost:27017'
})
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
async function start() {
// Instantiate nuxt.js
const nuxt = new Nuxt(config)
const {
host = process.env.HOST || '127.0.0.1',
port = process.env.PORT || 3000
} = nuxt.options.server
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
} else {
await nuxt.ready()
}
fastify.use(nuxt.render)
fastify.listen(port, host, (err, address) => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
})
}
start()
The /api route is not found in the browser. But then I read I should use serverMiddleware inside nuxt.config.js:
serverMiddleware: [
{ path: '/api', handler: '~/server/routesIndex.js' },
],
Doing so results in an error from fastify.register:
(node:10441) UnhandledPromiseRejectionWarning: TypeError: fastify.register is not a function
at routes (/media/srv/testingNuxt/server/routesIndex.js:4:13)
at call (/media/srv/testingNuxt/node_modules/connect/index.js:239:7)
at next (/media/srv/testingNuxt/node_modules/connect/index.js:183:5)
at next (/media/srv/testingNuxt/node_modules/connect/index.js:161:14)
at WebpackBundler.middleware (/media/srv/testingNuxt/node_modules/#nuxt/webpack/dist/webpack.js:5430:5)
(node:10441) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:10441) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
How can I add api routes using Fastify and Nuxt?
I want to avoid creating a second server on port 3001 just for the api.

Node.js "net.createConnection()" error callback not emitting

I want to connect to a Unix Domain Socket server in a Node application. If the connection succeeds and was opened, a loop (that may take some time) shall be executed. If an error occurs during the execution of this loop, it should receive some kind of notification. If a connection to the client is not possible at all, the loop should not be executed in the first place (that seems to work with the Promise). To me this sounds like the most simple thing in the world, but I just can't get it to work... This is what I have until now:
new Promise(function(resolve, reject) {
let connection = net.createConnection('/tmp/socket.s', () => {resolve(connection);})
.on('data', function(data) {
// Do something (during loop execution)
})
.on('error', reject); // If this callback is executed, the while loop should terminate (by receiving some kind of signal within the loop)
}).then(function(connection) {
for(...) {
// Do stuff that takes some time, executes other callbacks, sends data to the socket
}
connection.end();
}, function(error) {
// Error handling
});
What am I missing?
Try to listen to the data event in the resolve section of the promise. The following code should do it:
const net = require('net');
/**
* Client
* --------------------------------------------
*/
new Promise((resolve, reject) => {
let client = net.createConnection({ path: '/tmp/socket.s'}, () => {
console.log('Client: connected ')
resolve(client);
});
// Reject on error
client.on('error', err => reject(err) );
client.on('end', () => {
console.log('Client: disconnected from server #1');
});
}).then( connection => {
connection.on('data', data => {
// Do stuff with the data
console.log(`Client: the server says: ${data.toString()}\n`);
if(data != 'Data recieved'){
// Just to ensure that the following loop runs only once
for (let i = 0; i <= 10; i++) {
setTimeout(() => {
// Send data to the server
connection.write(`Client Data ${i}`);
if (i == 10) {
// Close the connection after everything is done
connection.end();
}
}, i*2000);
}
}
});
}, error => {
console.log('Client: promise rejection error', error );
});
My test server looks like this
const net = require('net');
/**
* Server
* --------------------------------------------
*/
const server = net.createServer( connectionListener => {
console.log(`#${process.pid} Server: client connected`);
connectionListener.on('end', () => {
console.log(`#${process.pid} Server: client disconnected`);
});
connectionListener.write('Hello\r\n');
connectionListener.on('data', data => {
console.log(`#${process.pid} Server: client sends: ${data.toString()}`);
connectionListener.write('Data recieved');
});
});
server.on('error', (err) => {
console.log(err);
server.close();
});
server.listen('/tmp/socket.s', () => {
console.log(`#${process.pid} Server: server bound`);
});
process.on('exit', code => {
console.log(code);
server.close();
});
process.on('SIGTERM', () => server.close() );
process.on('SIGINT', () => server.close() );
In this example the client sends data to server and the server replies each time. The client then closes the connection after having sent data 10 times.
P.S. There is no need to use a Promise unless you do need to return a promise at some point in your code.

How do I emit an event into gulp-tap's parent stream

I'm setting up a gulpfile for bundling multiple JavaScript files into multiple bundles during development.
Since I want to watch this process and not have it exit if there is an error (syntax or something) in one of the files I would need to properly emit an end event to the parent stream.
For handling the multiple files I am using the approach described in the gulp recipes.
But using this and reading the docs on gulp-tap I am unsure how to have it emit the error into the parent stream.
What I am trying to do is the following:
gulp.task('bundle', () => (
gulp.src(['whatevs/*.js'], { read: false })
.pipe($.tap((file) => {
file.contents = browserify( // eslint-disable-line no-param-reassign
file.path,
{ debug: true, fullPaths: true }
).bundle();
}))
.on('error', function handleBrowserifyError(err) {
this.emit('end');
})
.pipe(gulp.dest('bundles/'))
));
I can see the error if I would pass a callback to the bundle() call, but I do not have the slightest clue how to get it back into the parent stream.
I managed to do it like this:
gulp.task('bundle', () => {
const scriptStream = gulp.src(['whatevs/*.js'], { read: false })
.pipe($.tap((file) => {
file.contents = browserify( // eslint-disable-line no-param-reassign
file.path,
{ debug: true, fullPaths: true }
)
.bundle()
.on('error', () => scriptStream.emit('end'));
}))
.on('error', Function.prototype) // prevents crashing
.pipe(gulp.dest('bundles/'));
return scriptStream
});

Categories