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.
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.
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
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.
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.onChange
event, which will allow us to detect any modifications made to the code and save those changes accordingly.
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.
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
You can check out the code here - code on github
Also published here.