Collaborative Coding in Monaco Editor

Code-Pair/Collaborative Coding is a trending topic in the landscape of remote-working/remote-hiring. In this post, We will discuss how we can build such a system on our own in less than 20 minutes.

What is Code-Pairing?

In Code-Pairing there are more than one users sharing the same code and editing it in real-time. Code-Pairing has lots of use-cases like helping a teammate, instructing interns/students, real time interviews etc. Possibilities are endless.

System Architecture

Lets take a look how the system architecture of such simplified system might look.
collaborative coding system archetecture

Tech Stack

Today we will talk about how we can build such a system, using some of the most popular tools in the current tech world. We are going to build this project in ReactJs but keep in mind end product can be achieved through any framework.

We will be using Monaco-Editor as our code editor. If you don't already know, Monaco-Editor is the same Editor which powers VSCode. Developed by Microsoft, is currently one of the most powerful opensource editor in market.

We will also be using Firebase's Realtime Database as backend.

We will be using an amazing library @hackerrank/firepad, This library will automatically take care of managing editor-state in Firebase automatically for us. Not only that but it also automatically highlight the cursor of each user and where exactly other users are typing.

Code it Out

Lets get started with the setup.

1. Create a new React Project.

npx create-react-app collaboratory

2. Install Dependencies

yarn add @hackerrank/firepad [email protected] @monaco-editor/react monaco-editor

3. Add Editor

Lets add editor by modifying our App.js and lets start the developement server by npm start.

import Editor from "@monaco-editor/react";
import {useRef,useState} from 'react';


function App() {

  const editorRef = useRef(null);
  const [editorLoaded,setEditorLoaded] = useState(false);

  function handleEditorDidMount(editor, monaco) {
    editorRef.current = editor;
    setEditorLoaded(true);
  }

  return (
    <div>
      <Editor
       height="90vh"
       defaultLanguage="javascript"
       theme="vs-dark"
       defaultValue="// Welcome to My Editor"
       onMount={handleEditorDidMount}
     />
    </div>
  );
}

export default App;

4. Setup Firebase.

If you already have firebase set up skip to next step.
Go to https://console.firebase.google.com/u/2/.

i. Create Firebase App

ii. Create Realtime Database

iii. Select Region 'United States',

Other regions can be choose as per your targeted region.
Select Realtime Database Region

iv. Select 'Start in Test Mode'

v. Go to Project Settings

vi. Create an app.

Create an app from the bottom of General project settings.
Create a Firebase app.

vii. Register Firebase App Name

viii. Copy Firebase Configuration

Copy object next to firebaseConfig
Copy Firebase Configuration

5. Create a file firebaseConfig.js and Paste copied Firebase config.

Create firebaseConfig.js file in src folder.

const firebaseConfig =  {
    apiKey: "#####################################", // important
    authDomain: "############.firebaseapp.com", // important
    databaseURL: "https://########.firebaseio.com", // important
    projectId: "###########",
    storageBucket: "#########.appspot.com",
    messagingSenderId: "############3",
    appId: "#############",
    measurementId: "G-########"
  };

export default firebaseConfig;

6. Initialize Firebase SDK

Import firebase sdk

import firebase from "firebase";
  import firebaseConfig from './firebaseConfig';

Inside App.js, We do a initialization of firebase sdk

useEffect(() => {
    if(!firebase.apps.length){
      // Make sure initialization happens only once
      firebase.initializeApp(firebaseConfig); 
    } 
    else{
      firebase.app();
    }
  }, []);

7. Instantiate Firepad

Import Firepad Monaco Adapter

import {fromMonaco} from '@hackerrank/firepad';

We initialize firepad adapter for monaco

useEffect(() => {
    if(!editorLoaded){
      // If editor is not loaded return
      return;
    }

    const dbRef = firebase.database().ref().child(`pair001`); // Can be anything in param, use unique string for unique code session

    const firepad = fromMonaco(dbRef,editorRef.current);

    const name = prompt("Enter your Name :"); // Name to highlight who is editing where in the code
    if(name){
        firepad.setUserName(name);
    }

  },[editorLoaded]);

That's It. We should be good to go. Open project localhost:3000 in two separate tabs/window and start typing in one. Other should update as well.

Final Code should look like the following.

import React {useRef,useEffect,useState} from 'react';

import Editor from "@monaco-editor/react";

import firebaseConfig from './firebaseConfig';

import firebase from "firebase";
import {fromMonaco} from '@hackerrank/firepad';

function App() {

  const editorRef = useRef(null);
  const [editorLoaded, setEditorLoaded] = useState(false);

  function handleEditorDidMount(editor, monaco) {
    editorRef.current = editor; 
    setEditorLoaded(true);
  }

  useEffect(() => {
    if(!firebase.app.length){
      firebase.initializeApp(firebaseConfig);
    } 
    else{
      firebase.app();
    }
  }, []);

  useEffect(() => {
    if(!editorLoaded){
      return;
    }

    const dbRef = firebase.database().ref().child(`pair001`);

    const firepad = fromMonaco(dbRef,editorRef.current);
    const name = prompt("Enter your Name :");
    firepad.setUserName(name);


  },[editorLoaded]);

  return (
    <div>
      <Editor
       height="90vh"
       defaultLanguage="javascript"
       theme="vs-dark"
       defaultValue="// Welcome to My Editor"
       onMount={handleEditorDidMount}
     />
    </div>
  );
}

export default App;

Thank You have a nice day

About me

My name is Shubham Shekhar, I am a Software Developer, I have experience in ReactJs, Redux, NextJs, Material Ui, Bootstrap, Testing-Library, Jest, NodeJs, etc. I am highly focused towards learning new things and improving my way up. Feel free to contact me.

19