Running Processes
This page covers executing commands inside WebContainers.
Mental model
In WebContainers, processes are executed as follows:
webcontainerInstance.spawn('npm', ['install']);
webcontainerInstance.spawn('npm', ['install']);
The method spawn
takes three arguments:
- a string representing the command,
- an array with the command options,
- and, not required, spawn options (for example, specifying environment variables, disabling output, or specifying the size of the attached terminal).
In this case, we are running npm install
. If our command had multiple arguments, like for example ls src -l
, all the arguments would be added to the array like so:
webcontainerInstance.spawn('ls', ['src', '-l']);
webcontainerInstance.spawn('ls', ['src', '-l']);
Installing dependencies
Let's take a look at the following function that, when invoked, will install dependencies in a WebContainer instance:
async function installDependencies() {
// Install dependencies
const installProcess = await webcontainerInstance.spawn('npm', ['install']);
// Wait for install command to exit
return installProcess.exit;
}
async function installDependencies() {
// Install dependencies
const installProcess = await webcontainerInstance.spawn('npm', ['install']);
// Wait for install command to exit
return installProcess.exit;
}
Having installed the dependencies, this function will return the exit code (0
for a successful exit).
Reading process output
Calling spawn
returns a WebContainerProcess
. Every process has an output
property of type ReadableStream<string>
that receives all terminal output, including the stdout
and stderr
emitted by the spawned process and its descendants (for example, child processes).
Deep dive: ReadableStream
The output property is a ReadableStream
. That’s because it is, in fact, a stream that can emit strings numerous times, just like the actual stdout
or stderr
from a process in Node.js. The advantage of streams is that they allow composition, meaning we can pipe data from one stream into another stream, for example source.pipeTo(destination)
. Furthermore, streams can be transferred via postMessage
from one context to a different context, for example a web worker. A ReadableStream
also keeps a buffer of the data which is only flushed once you start reading.
If you want to read data from output you can pipe it into a WriteableStream
just like in the example below.
Output can be disabled by passing optional options to spawn
and setting output: false
. When set to false
, the output
stream will never receive any data. An example of a usage could be the following:
const installProcess = await webcontainerInstance.spawn('npm', ['install']);
installProcess.output.pipeTo(new WritableStream({
write(data) {
console.log(data);
}
}));
const installProcess = await webcontainerInstance.spawn('npm', ['install']);
installProcess.output.pipeTo(new WritableStream({
write(data) {
console.log(data);
}
}));
Running a dev server
WebContainers expose three events through the method on
:
server-ready
, which is emitted when the server is ready to accept requests,port
, which is emitted when a port is open or closed by a process,error
, emitted when an internal error is triggered.
The server-ready
event can be used to, for example, run a dev server via npm run start
:
async function startDevServer() {
// Run `npm run start` to start the Express app
await webcontainerInstance.spawn('npm', ['run', 'start']);
// Wait for `server-ready` event
webcontainerInstance.on('server-ready', (port, url) => {
// ...
});
}
async function startDevServer() {
// Run `npm run start` to start the Express app
await webcontainerInstance.spawn('npm', ['run', 'start']);
// Wait for `server-ready` event
webcontainerInstance.on('server-ready', (port, url) => {
// ...
});
}
Next steps
To further explore the WebContainer API in our WebContainer API starter, check the API Reference or follow our tutorial and build your first WebContainer app.