├── public └── stackblitz-favicon-editor.png ├── loading.html ├── vite.config.js ├── .gitignore ├── package.json ├── index.html ├── files.js ├── javascript.svg ├── style.css ├── main.js └── README.md /public/stackblitz-favicon-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stackblitz/webcontainer-api-starter/main/public/stackblitz-favicon-editor.png -------------------------------------------------------------------------------- /loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Installing dependencies... 8 | 9 | 10 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | server: { 5 | headers: { 6 | 'Cross-Origin-Embedder-Policy': 'require-corp', 7 | 'Cross-Origin-Opener-Policy': 'same-origin', 8 | }, 9 | }, 10 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webcontainers-api-starter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^4.1.0" 13 | }, 14 | "dependencies": { 15 | "@webcontainer/api": "^1.1.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My first WebContainers app 8 | 9 | 10 |
11 |

Hello, WebContainers API!

12 |

13 | 14 | Read the docs 📖 15 | 16 |

17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /files.js: -------------------------------------------------------------------------------- 1 | /** @satisfies {import('@webcontainer/api').FileSystemTree} */ 2 | 3 | export const files = { 4 | 'index.js': { 5 | file: { 6 | contents: ` 7 | import express from 'express'; 8 | const app = express(); 9 | const port = 3111; 10 | 11 | app.get('/', (req, res) => { 12 | res.send('Welcome to a WebContainers app! 🥳'); 13 | }); 14 | 15 | app.listen(port, () => { 16 | console.log(\`App is live at http://localhost:\${port}\`); 17 | });`, 18 | }, 19 | }, 20 | 'package.json': { 21 | file: { 22 | contents: ` 23 | { 24 | "name": "example-app", 25 | "type": "module", 26 | "dependencies": { 27 | "express": "latest", 28 | "nodemon": "latest" 29 | }, 30 | "scripts": { 31 | "start": "nodemon index.js" 32 | } 33 | }`, 34 | }, 35 | }, 36 | }; -------------------------------------------------------------------------------- /javascript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | :root { 6 | font-family: Inter, system-ui, Segoe UI, Roboto, Helvetica Neue, sans-serif; 7 | color: black; 8 | background: white; 9 | } 10 | 11 | body { 12 | margin: 0.5rem 1rem; 13 | } 14 | 15 | header { 16 | text-align: center; 17 | } 18 | 19 | header h1 { 20 | margin-bottom: 0; 21 | } 22 | 23 | header p { 24 | font-size: 1.5rem; 25 | margin-top: 0; 26 | } 27 | 28 | iframe, 29 | textarea { 30 | border-radius: 3px; 31 | } 32 | 33 | iframe { 34 | height: 20rem; 35 | width: 100%; 36 | border: solid 2px #ccc; 37 | } 38 | 39 | textarea { 40 | width: 100%; 41 | height: 20rem; 42 | resize: none; 43 | background: black; 44 | color: white; 45 | padding: 0.5rem 1rem; 46 | margin-bottom: 10px; 47 | font-size: 0.9rem; 48 | line-height: 1.2rem; 49 | font-family: Menlo, Cascadia Code, Consolas, Liberation Mono, monospace; 50 | } 51 | 52 | .container { 53 | display: grid; 54 | grid-template-columns: 1fr 1fr; 55 | gap: 1rem; 56 | height: 100%; 57 | width: 100%; 58 | } 59 | 60 | .wc { 61 | -webkit-text-fill-color: #0000; 62 | background-clip: text; 63 | -webkit-background-clip: text; 64 | background-image: linear-gradient(to right,#761fac 0,#8a19a9 20%,#d900a5 70%,#d917a3 100%); 65 | filter: drop-shadow(0 1px 0 #fff); 66 | font-weight: 800; 67 | color:#69f5ff; 68 | text-decoration: underline; 69 | } 70 | 71 | .docs { 72 | margin-left: 5px; 73 | text-decoration: none; 74 | font-weight:600; 75 | color:#333; 76 | font-size: 80%; 77 | text-decoration: underline; 78 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import { WebContainer } from '@webcontainer/api'; 3 | import { files } from './files'; 4 | 5 | /** @type {import('@webcontainer/api').WebContainer} */ 6 | let webcontainerInstance; 7 | 8 | window.addEventListener('load', async () => { 9 | textareaEl.value = files['index.js'].file.contents; 10 | textareaEl.addEventListener('input', (e) => { 11 | writeIndexJS(e.currentTarget.value); 12 | }); 13 | 14 | // Call only once 15 | webcontainerInstance = await WebContainer.boot(); 16 | await webcontainerInstance.mount(files); 17 | 18 | const exitCode = await installDependencies(); 19 | if (exitCode !== 0) { 20 | throw new Error('Installation failed'); 21 | }; 22 | 23 | startDevServer(); 24 | }); 25 | 26 | async function installDependencies() { 27 | // Install dependencies 28 | const installProcess = await webcontainerInstance.spawn('npm', ['install']); 29 | installProcess.output.pipeTo(new WritableStream({ 30 | write(data) { 31 | console.log(data); 32 | } 33 | })) 34 | // Wait for install command to exit 35 | return installProcess.exit; 36 | } 37 | 38 | async function startDevServer() { 39 | // Run `npm run start` to start the Express app 40 | await webcontainerInstance.spawn('npm', ['run', 'start']); 41 | 42 | // Wait for `server-ready` event 43 | webcontainerInstance.on('server-ready', (port, url) => { 44 | iframeEl.src = url; 45 | }); 46 | } 47 | 48 | /** 49 | * @param {string} content 50 | */ 51 | 52 | async function writeIndexJS(content) { 53 | await webcontainerInstance.fs.writeFile('/index.js', content); 54 | } 55 | 56 | document.querySelector('#app').innerHTML = ` 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | ` 66 | 67 | /** @type {HTMLIFrameElement | null} */ 68 | const iframeEl = document.querySelector('iframe'); 69 | 70 | /** @type {HTMLTextAreaElement | null} */ 71 | const textareaEl = document.querySelector('textarea'); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebContainer API Starter 2 | 3 | WebContainer API is a browser-based runtime for executing Node.js applications and operating system commands. It enables you to build applications that previously required a server running. 4 | 5 | WebContainer API is perfect for building interactive coding experiences. Among its most common use cases are production-grade IDEs, programming tutorials, or employee onboarding platforms. 6 | 7 | ## How To 8 | 9 | For an up-to-date documentation, please refer to [our documentation](https://webcontainers.io). 10 | 11 | ## Cross-Origin Isolation 12 | 13 | WebContainer _requires_ [SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) to function. In turn, this requires your website to be [cross-origin isolated](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer#security_requirements). Among other things, the root document must be served with: 14 | 15 | ``` 16 | Cross-Origin-Embedder-Policy: require-corp 17 | Cross-Origin-Opener-Policy: same-origin 18 | ``` 19 | 20 | You can check [our article](https://blog.stackblitz.com/posts/cross-browser-with-coop-coep/) on the subject and our [docs on browser support](https://developer.stackblitz.com/docs/platform/browser-support) for more details. 21 | 22 | ## Serve over HTTPS 23 | 24 | Please note that your deployed page must be served over HTTPS. This is not necessary when developing locally, as `localhost` is exempt from some browser restrictions, but there is no way around it once you deploy to production. 25 | 26 | ## Demo 27 | 28 | Check [the WebContainer API demo app](https://webcontainer.new). 29 | 30 | Here's an example `main.ts` file: 31 | 32 | ```ts 33 | import { WebContainer } from "@webcontainer/api"; 34 | 35 | const files: FileSystemTree = { 36 | "index.js": { 37 | file: { 38 | contents: "", 39 | }, 40 | }, 41 | }; 42 | 43 | let webcontainer: WebContainer; 44 | 45 | // add a textarea (the editor) and an iframe (a preview window) to the document 46 | document.querySelector("#app").innerHTML = ` 47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 | `; 56 | 57 | // the editor 58 | const textarea = document.querySelector("textarea"); 59 | 60 | // the preview window 61 | const iframe = document.querySelector("iframe"); 62 | 63 | window.addEventListener("load", async () => { 64 | textarea.value = files["index.js"].file.contents; 65 | 66 | textarea.addEventListener("input", (event) => { 67 | const content = event.currentTarget.value; 68 | webcontainer.fs.writeFile("/index.js", content); 69 | }); 70 | 71 | // call only once 72 | webcontainer = await WebContainer.boot(); 73 | 74 | await webcontainer.mount(files); 75 | 76 | const exitCode = await installDependencies(); 77 | 78 | if (exitCode !== 0) { 79 | throw new Error("Installation failed"); 80 | } 81 | 82 | startDevServer(); 83 | }); 84 | 85 | async function installDependencies() { 86 | // install dependencies 87 | const installProcess = await webcontainer.spawn("npm", ["install"]); 88 | 89 | installProcess.output.pipeTo( 90 | new WritableStream({ 91 | write(data) { 92 | console.log(data); 93 | }, 94 | }) 95 | ); 96 | 97 | // wait for install command to exit 98 | return installProcess.exit; 99 | } 100 | 101 | async function startDevServer() { 102 | // run `npm run start` to start the express app 103 | await webcontainer.spawn("npm", ["run", "start"]); 104 | 105 | // wait for `server-ready` event 106 | webcontainer.on("server-ready", (port, url) => { 107 | iframe.src = url; 108 | }); 109 | } 110 | ``` 111 | 112 | ## Troubleshooting 113 | 114 | Cookie blockers, either from third-party addons or built-in into the browser, can prevent WebContainer from running correctly. Check the `on('error')` event and our [docs](https://developer.stackblitz.com/docs/platform/third-party-blocker). 115 | 116 | To troubleshoot other problems, check the [Troubleshooting page](https://webcontainers.io/guides/troubleshooting) in our docs. 117 | 118 | # License 119 | 120 | Copyright 2023 StackBlitz, Inc. 121 | --------------------------------------------------------------------------------