paint-brush
How to Create a Personal WebDev IDE in Next.jsby@cigargalaxy82
4,819 reads
4,819 reads

How to Create a Personal WebDev IDE in Next.js

by dexterJuly 30th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this tutorial, we'll explore the process of building a functional web development Integrated Development Environment (IDE) using React within a Next.js application. Initially, we'll focus on creating an IDE that offers support for HTML, CSS, and JavaScript. Subsequently, we'll enhance the IDE by adding the capability to execute our code.
featured image - How to Create a Personal WebDev IDE in Next.js
dexter HackerNoon profile picture


Having a suitable code editor is crucial during the coding process, as it plays a significant role in one's coding journey. Finding the right editor that suits your needs can greatly benefit your programming experience. Let’s create one ourselves now.


Content Overview

  • Introduction
  • Setting up the application
  • Creating the editor
  • Steps
  • Conclusion

Introduction

In this tutorial, we'll explore the process of building a functional web development Integrated Development Environment (IDE) using React within a Next.js application. Initially, we'll focus on creating an IDE that offers support for HTML, CSS, and JavaScript. Subsequently, we'll enhance the IDE by adding the capability to execute our code.


Setting up the application

To begin, let's generate a fresh Next.js project. We'll create the project within a directory named "web-editor" and make sure to enable JavaScript and ESLint for it.


npx create-next-app contact-form --js --eslint


This will create the folder and installs all the dependencies. The main file where we are going to be working is page.js inside src/app.


Now enter the folder (cd contact-form) and start the development server:


npm run dev


Visit http://localhost:3000 to check the running application. If everything is working, you should see a working web page.


Creating the Editor

For building the editor in Next.js, we'll utilize monaco-react a library that enables us to integrate the monaco-editor seamlessly into our React application. The advantage of using this library is that we can avoid the complexities of configuring webpack, rollup, parcel, or similar tools that are typically required for incorporating the monaco-editor into our project.


To start using this we first need to install it:


npm install @monaco-editor/react


After installing the Monaco-react package, you would have to go to the docs and then you'll see how to include it in your project.


Copy and paste the below code in Page.js file:


'use client'

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

export default function Home() {
  return (
    <Editor height="100vh" defaultLanguage="javascript" defaultValue="// some comment" />
  )
}


You should see some like this.


We now have an Integrated Development Environment (IDE) integrated into our web page. This IDE allows users to write and edit code directly on the webpage. By default, the editor is set to support JavaScript, but users have the flexibility to change the language according to their preference. Now, let's take some time to explore and understand the code implementation of this IDE.


Numerous props are available for us to customize the editor according to our preferences. These props include options like height, defaultLanguage, defaultValue, Theme, and many others. By leveraging these props, we can easily make various changes and adjustments to the appearance and behavior of our editor to suit our specific needs and requirements.


To ensure that our IDE supports HTML, CSS, and JavaScript, we need to add support for all three languages. For this purpose, we will use a prop called path which enables us to manage the current file we are working on. This path prop plays a crucial role in maintaining three distinct states, allowing us to seamlessly switch between the different files corresponding to HTML, CSS, and JavaScript code, making it possible to work on each of them effectively.


I am also going to install fontAwesome as it will allow us to use different icons in our editor that we need.


Copy and paste the below cmd:


npm i --save @fortawesome/react-fontawesome@latest
npm i --save @fortawesome/free-brands-svg-icons@latest


Copy and paste the below code in Page.js:


'use client'

import { useState } from 'react';
import Editor from '@monaco-editor/react';
import styles from './page.module.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faHtml5, faCss3, faJs } from '@fortawesome/free-brands-svg-icons'

const files = {
  'script.js': {
    name: 'script.js',
    language: 'javascript',
    value: "someJSCodeExample",
  },
  'style.css': {
    name: 'style.css',
    language: 'css',
    value: "someCSSCodeExample",
  },
  'index.html': {
    name: 'index.html',
    language: 'html',
    value: "someHTMLCodeExample",
  },
};

export default function Home() {
  const [fileName, setFileName] = useState('script.js');

  const file = files[fileName];

  return (
    <>
    <div className={styles.topBar}>
    <button className={styles.htmlButton} disabled={fileName === 'index.html'} onClick={() => setFileName('index.html')}>
      <div><FontAwesomeIcon icon={faHtml5} /></div>index.html
      </button>
      <button className={styles.cssButton} disabled={fileName === 'style.css'} onClick={() => setFileName('style.css')}>
      <div><FontAwesomeIcon icon={faCss3} /></div>style.css
      </button>
      <button className={styles.jsButton} disabled={fileName === 'script.js'} onClick={() => setFileName('script.js')}>
      <div><FontAwesomeIcon  icon={faJs} /></div> script.js
      </button>
    </div>
    <Editor
      height="100vh"
      theme="vs-dark"
      path={file.name}
      defaultLanguage={file.language}
      defaultValue={file.value}
    />
  </>
  )
}


Our project should look something like this.


The IDE doesn’t look the best as of now so let’s start working on that. I am also going to add a button to the code.


Copy and paste the below code in page.js


"use client";
import { useState } from "react";
import Editor from "@monaco-editor/react";
import styles from "./page.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHtml5, faCss3, faJs } from "@fortawesome/free-brands-svg-icons";
import { faPlay } from "@fortawesome/free-solid-svg-icons";

const files = {
  "index.html": {
    name: "index.html",
    language: "html",
    value: "",
  },
  "style.css": {
    name: "style.css",
    language: "css",
    value: "",
  },
  "script.js": {
    name: "script.js",
    language: "javascript",
    value: "",
  },
};

export default function Home() {
  const [fileName, setFileName] = useState("index.html");
  const file = files[fileName];

  return (
    <>
      <div>
        <div className={styles.topBar}>
          <button
            className={styles.htmlButton}
            disabled={fileName === "index.html"}
            onClick={() => setFileName("index.html")}
          >
            <div>
              <FontAwesomeIcon icon={faHtml5} />
            </div>
            index.html
          </button>
          <button
            className={styles.cssButton}
            disabled={fileName === "style.css"}
            onClick={() => setFileName("style.css")}
          >
            <div>
              <FontAwesomeIcon icon={faCss3} />
            </div>
            style.css
          </button>
          <button
            className={styles.jsButton}
            disabled={fileName === "script.js"}
            onClick={() => setFileName("script.js")}
          >
            <div>
              <FontAwesomeIcon icon={faJs} />
            </div>{" "}
            script.js
          </button>
          <button className={styles.playButton} id="runCode">
            <div>
              <FontAwesomeIcon icon={faPlay} />
            </div>{" "}
            Run
          </button>
        </div>
        <Editor
          height="100vh"
          theme="vs-dark"
          saveViewState={true}
          path={file.name}
          defaultLanguage={file.language}
          defaultValue={file.value}
        />
      </div>
    </>
  );
}


This is all the CSS we will need in our project Copy and paste the below code in page.module.css file.




.topBar{
  background-color: #242424;
  display: flex;

}

.jsButton,.cssButton, .htmlButton, .playButton, .closeButton {
  padding: 10px;
  font-size: 0.9rem;
  background-color: #242424;
  color: white;
  display: flex;
  flex-direction: row;
}

.closeButton{
  font-size: 1.2rem;
  box-shadow: 0 4px 10px 0 rgba(0,0,0,.2);
  font-weight: 700;
}

.htmlButton svg{
  color: rgb(248, 48, 48);
}

.cssButton svg{
  color: rgb(21, 130, 255);
}

.jsButton svg{
  color: rgb(243, 243, 16);
}

.playButton svg {
  color: rgb(46, 192, 46);
}

.closeButton svg {
  color: rgb(248, 48, 48);
}

.jsButton div,.htmlButton div, .cssButton div, .playButton div, .closeButton div {
  padding-right: 2px;
  margin: 1px;
}

.split{
  display: flex;
  flex-direction: row;
}


.editor {
  position: absolute;
}

.outputiframewindow {
  width: 50vw;
  height: 45vh;
  background-color: white;
}

.websiteWindow{
  position: absolute;
  z-index: 1;
  top: 30vh;
  left: 25vw;
}

.buttonBlock {
  background-color: #F0F4F4;
  box-shadow: 0 4px 10px 0 rgba(0,0,0,.2);
}


This is how our IDE should look like now:


We are now entering the exciting phase of the project where we focus on implementing the functionality to run the code within our IDE. To make it easier to understand, we will break down this process into several steps, ensuring a clear and systematic approach to this crucial part of our project. Let's dive into each step one by one and bring the code execution feature to life in our IDE.

Steps

  1. The first step is to ensure that we can preserve the current state of the IDE, which involves saving the HTML, CSS, and JavaScript code entered by the user so that we can execute it later.
  2. To achieve this, we will utilize the useState hook, a powerful feature in React, which enables us to create and manage state variables. These state variables will store the code entered by the user, and we'll update them whenever the user runs the code.
  3. To keep track of the changes made in the IDE, we'll employ the onChange event, which will allow us to detect any modifications made to the code and save those changes accordingly.
  4. Now that we have the user's code stored, the next step is to figure out a way to execute it. For this purpose, we will leverage an iframe, a popular HTML element that will facilitate code execution within a sandboxed environment, ensuring a safe and separate execution space for the code.


Copy and paste the below code in page.js file.


"use client";
import { useState, useEffect, useRef } from "react";
import Editor from "@monaco-editor/react";
import styles from "./page.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHtml5, faCss3, faJs } from "@fortawesome/free-brands-svg-icons";
import { faPlay, faCircleXmark } from "@fortawesome/free-solid-svg-icons";

const files = {
  "index.html": {
    name: "index.html",
    language: "html",
    value: "",
  },
  "style.css": {
    name: "style.css",
    language: "css",
    value: "",
  },
  "script.js": {
    name: "script.js",
    language: "javascript",
    value: "",
  },
};

export default function Home() {
  const [fileName, setFileName] = useState("index.html");
  const [htmlCode, setHtmlCode] = useState("");
  const [cssCode, setCssCode] = useState("");
  const [jsCode, setJsCode] = useState("");

  function handleEditorChange(value) {
    file.value = value;
  }

  const file = files[fileName];

  useEffect(() => {
    const runBtn = document.getElementById("runCode");
    const clsBtn = document.getElementById("closeWindow");
    runBtn?.addEventListener("click", () => { 
      setHtmlCode(files["index.html"].value);
      setCssCode(files["style.css"].value);
      setJsCode(files["script.js"].value);
      document.getElementById("outputWindow").style.display = "block";
    });

    clsBtn?.addEventListener("click", () => {
      document.getElementById("outputWindow").style.display = "none";
    });
  }, []);

  return (
    <>
      <div>
        <div className={styles.topBar}>
          <button
            className={styles.htmlButton}
            disabled={fileName === "index.html"}
            onClick={() => setFileName("index.html")}
          >
            <div>
              <FontAwesomeIcon icon={faHtml5} />
            </div>
            index.html
          </button>
          <button
            className={styles.cssButton}
            disabled={fileName === "style.css"}
            onClick={() => setFileName("style.css")}
          >
            <div>
              <FontAwesomeIcon icon={faCss3} />
            </div>
            style.css
          </button>
          <button
            className={styles.jsButton}
            disabled={fileName === "script.js"}
            onClick={() => setFileName("script.js")}
          >
            <div>
              <FontAwesomeIcon icon={faJs} />
            </div>{" "}
            script.js
          </button>
          <button className={styles.playButton} id="runCode">
            <div>
              <FontAwesomeIcon icon={faPlay} />
            </div>{" "}
            Run
          </button>
        </div>
        <Editor
          height="100vh"
          theme="vs-dark"
          saveViewState={true}
          path={file.name}
          defaultLanguage={file.language}
          defaultValue={file.value}
          onChange={handleEditorChange}
          value={file.value}
        />
      </div>
      <div className={styles.websiteWindow} id="outputWindow">
        <div className={styles.buttonBlock}>
          <button className={styles.closeButton} id="closeWindow">
            <div>
              <FontAwesomeIcon icon={faCircleXmark} />
            </div>
          </button>
        </div>
        <iframe
          title="output"
          srcDoc={`
  <html>
    <body>${htmlCode}</body>
    <style>${cssCode}</style>
    <script>${jsCode}</script>
  </html>
`}
          className={styles.outputiframewindow}
        />
      </div>
    </>
  );
}


This is how our final project looks.

Conclusion

We have learned how to create a WebDev IDE on Next.js, we can see that we can run HTML, CSS and JavaScript in our IDE.


Further possible improvements include adding better UI/UX, adding saving code feature, uploading files, creating different themes and a lot more.


I also have a similar project with a lot of different features added you can check it out here https://ide.lamtell.com/.


You can check out the code here - code on github


Also published here.