Karagöz

Getting Started

Initialising Karagöz Sandbox involves a few steps. We will go through the basic steps in this guide.

Create File System Tree

Start by creating the file structure you want to mount to the WebContainer.

In this example we will create a simple express server that provides an endpoint under api/message, as well as, serves the static files under the public directory at the server root.

The contents of each file will be created as a separate string for better readability.

This will be a relatively long chunk of code, so bear with me ^^

/**
 * package.json
 * Defines the dependencies to be installed and script to start the server.
 * The server options indicate that it should restart when JS, HTML or CSS files change.
 */
const packageJson = `
{
  "name": "example-app",
  "type": "module",
  "dependencies": {
    "express": "latest",
    "nodemon": "latest"
  },
  "scripts": {
    "start": "nodemon --watch './' -e js,html,css server.js"
  }
}
`

/**
 * server.js
 * Configures a web server on port 3111.
 * Defines the endpoint /api/message that returns a string message.
 * Serves the files in /public under the root path /. 
 */
const serverJs = `
import express from 'express'

const app = express()
const port = 3111

app.get('/api/message', (req, res) => {
  setTimeout(() => res.send('Welcome to a WebContainers app! 🥳'), 1000)
})
app.use('/', express.static('public'))

app.listen(port, () => {
  console.log('App is live at http://localhost:' + port)
})
`

/**
 * public/index.html
 * The home page of the server.
 * Links style.css for styling.
 * Embeds script.js and calls the doSomething() function defined within it.
 * IMPORTANT: we're escpaing the script closing tags, otherwise we will get errors. 
 */
const indexHtml = `
<html>
<head>
    <link rel="stylesheet" href="./style.css"/>
</head>
<body>
<h1>Home Page</h1>
<p class="response">Fetching message...</p>
<script src="./script.js"><${'/script'}>
<script>doSomething()<${'/script'}>
</body>
</html>
`

/**
 * public/script.js
 * Defines the function doSomething() that calls the API endpoint /api/messsage
 * and injects the response into the div element with the class "response".
 */
const scriptJs = `
function doSomething() {
  fetch('/api/message')
    .then((response) => response.text())
    .then((data) => (document.querySelector('.response').innerHTML = data))
}
`

/**
 * public/style.css
 * Basic styles.
 */
const styleCss = `
body {
  font-family: Arial, Helvetica, sans-serif;
}

h1 {
  color: #99cc33;
}
`

We will now assemble all that into a FileSystemTree instance.

import type { FileSystemTree } from '@webcontainer/api'

/**
 * Notice how we define files and directories.
 */
const fileTree: FileSystemTree = {
  'server.js': { file: { contents: serverJs } },
  'package.json': { file: { contents: packageJson } },
  public: {
    directory: {
      'index.html': { file: { contents: indexHtml } },
      'script.js': { file: { contents: scriptJs } },
      'style.css': { file: { contents: styleCss } },
    },
  },
}

Create and Provide a WebContainer Instance

Now, import and call useSandboxBoot(). It is a wrapper around WebContainer.boot() and optionally takes a similar BootOptions instance as an argument.

After that, provide the resulting promise through provideWebContainer().

Then create the sandbox with useSandbox().

// `boot` is the promise returned by WebContainer.boot().
// `isBooting` is a convenience boolean flag that will be used later to indicate that the sandbox is still booting.
const { boot, isBooting } = useSandboxBoot()

// Sub-components and underlying code will await `boot` and use the resulting WebContainer instance. 
provideWebContainer(boot)

// Must come after provideWebContainer() to able to use the provided boot promise.
const sandbox = useSandbox()

Mount File System Tree

Use onMounted() to await the boot, then mount the fileTree we created earlier.

onMounted(async () => {
  // Ensure injected promise has been resolved
  const container = await boot
  
  // Continue initialisation
  
  // Mount files
  await container.mount(fileTree)
  // Finish bootstrapping
  await sandbox.bootstrap()
  // Optionally, open one of the files for editing.
  sandbox.editorTabs.open('./public/index.html')
})

Cleanup

Make sure to tear down the WebContainer once it is no longer needed.

onBeforeUnmount(() => sandbox.container.value?.teardown())

Template

Now simply add the KrgzSandbox component to your template.

<template>
  <!-- Hiding the solve button for now. Another story for another guide :) -->
  <KrgzSandbox :booting="isBooting" hide-solve-button></KrgzSandbox>
</template>

Bringing all of that together will get you this nice sandbox :)

Full example code