Skip to content

Instantly share code, notes, and snippets.

@nicobytes
Created November 11, 2023 10:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicobytes/725f9d2f83093eaebaa19a81ff1626f2 to your computer and use it in GitHub Desktop.
Save nicobytes/725f9d2f83093eaebaa19a81ff1626f2 to your computer and use it in GitHub Desktop.
WebContianers
import { Component, ElementRef, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { ReactiveFormsModule } from '@angular/forms';
import { WebContainer } from '@webcontainer/api';
import { FormControl } from '@angular/forms';

export const files = {
  'index.js': {
    file: {
      contents: `
import express from 'express';
const app = express();
const port = 3111;

app.get('/', (req, res) => {
  res.send('Welcome to a WebContainers app! 🥳');
});

app.listen(port, () => {
  console.log(\`App is live at http://localhost:\${port}\`);
});`,
    },
  },
  'package.json': {
    file: {
      contents: `
{
  "name": "example-app",
  "type": "module",
  "dependencies": {
    "express": "latest",
    "nodemon": "latest"
  },
  "scripts": {
    "start": "nodemon --watch './' index.js"
  }
}`,
    },
  },
};

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet, ReactiveFormsModule],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  webcontainerInstance!: WebContainer;
  @ViewChild('iframeEl') iframeEl!: ElementRef;
  textareaCtrl = new FormControl<string>('', {nonNullable: true});

  async ngOnInit() {
    this.webcontainerInstance = await WebContainer.boot({
      coep: 'none'
    });
    await this.webcontainerInstance.mount(files);
    const contentFile = await this.webcontainerInstance.fs.readFile('index.js', 'utf-8');
    console.log(contentFile);
    this.textareaCtrl.setValue(contentFile);
    const exitCode = await this.installDependencies();
    if (exitCode !== 0) {
      throw new Error('Installation failed');
    };
    await this.startDevServer();
    this.textareaCtrl.valueChanges.subscribe(async (value) => {
      await this.writeIndexJS(value);
    });
  }

  async installDependencies() {
    const installProcess = await this.webcontainerInstance.spawn('npm', ['install']);
    installProcess.output.pipeTo(new WritableStream({
      write(data) {
        console.log(data);
      }
    }));
    return installProcess.exit;
  }

  async startDevServer() {
    await this.webcontainerInstance.spawn('npm', ['run', 'start']);
    const iframeEl = this.iframeEl.nativeElement;
    this.webcontainerInstance.on('server-ready', (port, url) => {
      iframeEl.src = url;
    });
  }

  async writeIndexJS(content: string) {
    await this.webcontainerInstance.fs.writeFile('/index.js', content);
  };
}
<div class="container">
  <div class="editor">
    <textarea [formControl]="textareaCtrl">I am a textarea</textarea>
  </div>
  <div class="preview">
    <iframe #iframeEl src="/assets/loading.html"></iframe>
  </div>
</div>
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  height: 100vh;
}
.container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  height: 100%;
  width: 100%;
}

textarea {
  width: 100%;
  height: 100%;
  resize: none;
  border-radius: 0.5rem;
  background: black;
  color: white;
  padding: 0.5rem 1rem;
}

iframe {
  height: 100%;
  width: 100%;
  border-radius: 0.5rem;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment