├── 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 |
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 |
--------------------------------------------------------------------------------