Process Tabs
The processTabs
property of the sandbox is responsible for managing processes and terminals.
Automatic Bootstrapping
For simple use cases, directly managing processes won't be necessary.
Karagöz Sandbox offers the bootstrap
function which does the following:
- Kills the dependency installation process and the dev server if either of them is running.
- On first run: listen to URL change events in the preview iframe to emit the latest URL to the parent window. This is needed to show the current URL in address bar of the preview.
- Open a terminal window if allowed by the sandbox options.
- Install dependencies (using the package manager configured in the options).
- Start the dev server (also using the package manager configured in the options).
- On first run: watch the current working directory to re-bootstrap when a file changes that should trigger
re-install (e.g.
package.json
).
Manual Process Management
You can opt out of automatic bootstrapping simply by not calling sandbox.bootstrap()
, then you will have full
control over which processes to run and in what order.
Spawning a Process
Whether you opt in or out of automatic bootstrapping, you can manually spawn processes by calling editorTabs.open()
.
sandbox.processTabsopen(
// Unique id. As a convention, you can use the string presentation of the command to run.
'npm install',
// Optional label to show for the process tab.
// If not provided, the process tab ID is shown instead.
'Install',
// Optional context.
{
// The only mandatory property. The command to spawn the process with.
command: 'npm',
// Additional arguemtns.
arguments: ['install', '--frozen-lockfile'],
// Flags to enable/disable certain UI features (all false by default).
canRestart: false,
canStop: false,
isHidden: false,
suppressClose: false,
suppressInput: false,
// When true, the process is treated as a terminal:
// some flags are ignored and the tab will be shown in the
// terminals panel and not in the processes panel.
isTerminal: false,
},
)
The context object has some additional properties that are used internally. You can learn more about it in the API Reference.
If you provide an ID that already exists, then the respective process tab will be focused without spawning a new process.
Stopping a Process
You can stop a process either
- by calling
sandbox.processTabs.close(id)
which kills the process and closes its respective tab, - or by calling
sandbox.processTabs.kill(id)
which kills the process, but keeps its respective tab visible so the user still has access to its output.
Restarting a Process
A process can be restarted by calling sandbox.processTabs.restart(id)
.
Also, processes that have canRestart: true
in their context show a restart button to respawn the process through
the UI.
Example: Manual Bootstrapping
Let's bring everything together and manually start processes.
In the demo below, now process is started automatically, rather we have buttons to:
- Trigger dependency installation.
- Start the development server.
- Spawn a terminal.
- Close the terminal, if open.
Here is the code to handle these processes:
const installDeps = async () => {
await sandbox.processTabs.open('npm install', 'Install Dependencies', {
command: 'npm',
args: ['install'],
suppressClose: true,
suppressInput: true,
})
// we await to correctly set installationCompleted flag
await sandbox.processTabs.findTab('npm install')?.context?.process?.exit
installationCompleted.value = true
}
const startDevServer = () => {
sandbox.processTabs.close('npm install')
// No need to await here because nothing depends on finishing this process
sandbox.processTabs.open('npm start', 'Dev Server', {
command: 'npm',
args: ['start'],
canRestart: true,
canStop: true,
suppressInput: true,
})
}
const openTerminal = () => {
sandbox.processTabs.open('terminal', 'Terminal', {
command: 'jsh',
isTerminal: true,
})
}
const closeTerminal = () => {
sandbox.processTabs.close('terminal')
}
Example: Using Generators
A good use case for manual bootstrapping (or running some processes before automatic bootstrapping) is when using
generators (e.g. npm create
, create-react-app
...etc.).
In the example below we will generate a Vite app using the React template by running the following command:
npm create vite@latest my-react-app -- --template react
There are a few things to consider here:
- Karagöz Sandbox expect the files to be in the current working directory, while generators (usually) create a new directory to store the generated files. Because of that, we will have to move all the files up to the CWD after generation and (optionally) remove the now empty directory created by the generator.
- We should aim to eliminate the need for user input by providing the command options directly. It might not work in all cases, but it's recommended.
- Some generators might perform unwanted steps that cannot be prevented (e.g. the Analog generator initialises a Git repository, which won't work in a WebContainer).
So our workflow for the example will be as follows:
- Spawn a process to run the
npm create
command shown above. - Move all the generated files from
./my-react-app
to.
. - Remove the
./my-react-app
directory. - Run the automatic bootstrapping to install dependencies and start the development server.
With that our onMounted
hook would look something like this:
onMounted(async () => {
// Ensure injected promise has been resolved
const container = await boot
// Mount shell script to perform extra steps before bootstrapping
await container.mount({
'prepare.sh': {
file: {
contents: [
'mv ./my-react-app/* ./my-react-app/.* ./',
'rmdir ./my-react-app',
'rm prepare.sh',
].join('\n'),
},
},
})
// Set start script
sandbox.setOption('process', (old) => ({
...old,
commands: {
...old.commands,
devServer: 'npm run dev',
},
}))
// Generate react app
await sandbox.processTabs.open('npm create', 'Generating React App', {
command: 'npm',
args: 'create vite@latest my-react-app -y -- --template react'.split(' '),
suppressClose: true,
suppressInput: true,
})
await sandbox.processTabs.findTab('npm create')?.context?.process?.exit
sandbox.processTabs.close('npm create')
// Perform remaining steps before bootstrapping
await sandbox.processTabs.open('prepare', 'Prepare', {
command: 'jsh',
args: ['prepare.sh'],
isHidden: true,
})
// Hand over to bootstrap()
await sandbox.bootstrap()
sandbox.editorTabs.open('./src/App.jsx')
})