TypeScript Tutorial: Building a Simple Web-Based Code Editor with Code Highlighting and Execution

In the world of web development, the ability to write, test, and run code directly within a browser is incredibly valuable. Whether you’re a beginner learning the ropes or a seasoned developer experimenting with new ideas, having an in-browser code editor can streamline your workflow and boost your productivity. This tutorial will guide you through building a simple, yet functional, web-based code editor using TypeScript, complete with code highlighting and execution capabilities.

Why Build a Web-Based Code Editor?

There are several compelling reasons to create your own web-based code editor:

  • Accessibility: Access your code from anywhere with an internet connection.
  • Learning: Understand the inner workings of code editors and how they function.
  • Customization: Tailor the editor to your specific needs and preferences.
  • Experimentation: Test and debug code snippets quickly without setting up a local environment.

This tutorial focuses on creating a simplified version, but the principles learned can be applied to more complex projects.

Prerequisites

Before we begin, ensure you have the following:

  • Node.js and npm (or yarn): Used for managing project dependencies and running the development server.
  • A basic understanding of HTML, CSS, and JavaScript: Familiarity with these web technologies is essential.
  • A code editor: Any code editor of your choice (VS Code, Sublime Text, etc.) will work.

Setting Up the Project

Let’s start by setting up our project. Open your terminal and create a new directory for your project, then navigate into it:

mkdir web-code-editor
cd web-code-editor

Initialize a new Node.js project:

npm init -y

Next, install TypeScript and a few other necessary packages. We’ll use Parcel as a bundler to simplify the build process. We’ll also install a code highlighting library like Prism.js, and a module for executing JavaScript code in the browser:

npm install typescript parcel-bundler prismjs @babel/standalone --save-dev

Create a tsconfig.json file in the root of your project. This file configures the TypeScript compiler. Use the following configuration:

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    "target": "es5",
    "sourceMap": true,
    "strict": true,
    "jsx": "preserve",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

This configuration specifies that we’re targeting ES5 for compatibility, enables source maps for easier debugging, and turns on strict type checking.

Project Structure

Create the following file structure in your project directory:

web-code-editor/
├── src/
│   ├── index.html
│   ├── index.ts
│   └── styles.css
├── tsconfig.json
├── package.json
└── .gitignore

Let’s add a basic .gitignore file to avoid committing unnecessary files:

node_modules/
dist/

Building the HTML Structure (src/index.html)

Create the basic HTML structure for our code editor:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Code Editor</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <textarea id="code-editor"></textarea>
        <button id="run-button">Run</button>
        <pre id="output"><code class="language-javascript"></code></pre>
    </div>
    <script src="index.ts"></script>
</body>
</html>

This HTML includes a textarea for the code editor, a button to run the code, and a pre element to display the output. It also links to our CSS and JavaScript files.

Styling the Editor (src/styles.css)

Add some basic styles to make the editor visually appealing:

body {
    font-family: monospace;
    margin: 0;
    padding: 0;
    background-color: #f0f0f0;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

.container {
    background-color: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    width: 80%;
    max-width: 800px;
}

#code-editor {
    width: 100%;
    height: 200px;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-family: monospace;
    resize: vertical;
}

#run-button {
    margin-top: 10px;
    padding: 10px 20px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

#output {
    margin-top: 10px;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
    background-color: #f9f9f9;
    overflow: auto;
}

code {
    display: block;
    padding: 10px;
    overflow-x: auto;
}

Writing the TypeScript Code (src/index.ts)

This is where the core logic of our code editor resides. Let’s break it down step by step.

import Prism from 'prismjs';
import 'prismjs/themes/prism.css'; // Import a Prism theme
import { transform } from '@babel/standalone';

const codeEditor = document.getElementById('code-editor') as HTMLTextAreaElement;
const runButton = document.getElementById('run-button') as HTMLButtonElement;
const output = document.getElementById('output') as HTMLPreElement;
const outputCode = output.querySelector('code') as HTMLElement;

function highlightCode() {
  if (codeEditor.value) {
    outputCode.innerHTML = Prism.highlight(codeEditor.value, Prism.languages.javascript, 'javascript');
  } else {
    outputCode.innerHTML = '';
  }
}

function executeCode() {
    try {
        const transformedCode = transform(codeEditor.value, { presets: ['env'] }).code;
        // Execute the code within the browser's context
        const result = eval(transformedCode);
        outputCode.textContent = result !== undefined ? String(result) : '';
    } catch (error: any) {
        outputCode.textContent = `Error: ${error.message}`;
    }
}

// Event listeners
codeEditor.addEventListener('input', () => {
    highlightCode();
});

runButton.addEventListener('click', () => {
    executeCode();
});

// Initial highlight
highlightCode();

Let’s break down the code:

  • Import Statements: We import the necessary modules: Prism.js for code highlighting and @babel/standalone for transpilation.
  • Get DOM Elements: We get references to the textarea, the run button, and the output pre element using their respective IDs.
  • highlightCode() function: This function uses Prism.js to highlight the code in the editor. It takes the text from the code editor, and applies syntax highlighting using the `Prism.highlight()` function. The highlighted code is then inserted into the `output` element.
  • executeCode() function: This function executes the code entered in the editor. It uses Babel to transpile the code for browser compatibility. It then uses the `eval()` function to execute the transformed JavaScript code. The output is displayed in the output area. We’ve also included a `try…catch` block to handle any errors that might occur during execution.
  • Event Listeners: We attach event listeners to the code editor and the run button. The ‘input’ event on the editor triggers the `highlightCode()` function, updating the highlighted code dynamically as the user types. The ‘click’ event on the run button triggers the `executeCode()` function, which executes the code and displays the result.
  • Initial Highlight: We call `highlightCode()` initially to highlight the code, even when the page first loads.

Running the Application

To run the application, use the following command in your terminal:

npx parcel src/index.html

Parcel will start a development server and automatically open your code editor in your browser. You should see the code editor, and you can start typing JavaScript code. As you type, the code will be highlighted. When you click the “Run” button, the code will execute, and the output will be displayed below.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to fix them:

  • Typing Errors: Ensure you are typing the correct JavaScript syntax. Syntax errors will prevent the code from running or highlighting correctly. Carefully check for missing semicolons, incorrect brackets, or misspelled keywords.
  • Incorrect Imports: Double-check the import statements for the libraries you are using. Make sure you have installed the necessary packages and that the import paths are correct.
  • JavaScript Errors: If your JavaScript code has runtime errors, they will be displayed in the output area. Carefully read the error messages to identify and fix the issues. Use the browser’s developer console (usually opened by pressing F12) for more detailed error information.
  • Incorrect Syntax Highlighting: Ensure you’ve correctly included the Prism.js stylesheet and that the language is correctly specified in the `Prism.highlight()` function. Also, ensure you have chosen a Prism theme that suits your preferences.
  • Security Concerns with eval(): The use of eval() can be a security risk if you’re not careful. In this simple example, we’re only executing code that the user enters, so the risk is relatively low. However, in a production environment, you should carefully consider the security implications and potentially use a safer alternative, such as a sandboxed environment or a code execution service that mitigates these risks.

Enhancements and Next Steps

This is a basic implementation, but there’s a lot you can do to enhance it:

  • Code Autocompletion: Implement code autocompletion using a library like CodeMirror or Monaco Editor.
  • Error Highlighting: Highlight errors directly in the code editor.
  • Multiple Language Support: Extend the editor to support multiple programming languages.
  • Code Formatting: Add a code formatting feature to automatically format the code.
  • Save/Load Functionality: Allow users to save and load their code.
  • Themes: Allow users to select different themes for the editor.
  • Debugging: Integrate a debugger to step through code and inspect variables.

Key Takeaways

  • Building a web-based code editor is a practical way to learn about web development and code execution.
  • TypeScript enhances code maintainability and readability.
  • Libraries like Prism.js and Babel simplify code highlighting and execution.
  • Understanding the basics opens doors to more advanced projects.

FAQ

Here are some frequently asked questions:

  1. Can I use this code editor for production? While this editor provides a good starting point, it’s not production-ready. You would need to add security features, more robust error handling, and more advanced features.
  2. How can I add different language support? You’ll need to update the Prism.js configuration to include the language you want to support and adjust the `Prism.highlight()` function accordingly. You may also need to modify the Babel configuration.
  3. Why is my code not executing? Check the browser’s console for any error messages. Make sure your code is syntactically correct and that you have installed all the necessary dependencies.
  4. How can I improve the performance? You can optimize the performance by using techniques like code splitting, lazy loading, and memoization.

The journey of building a web-based code editor is a rewarding one. You’ve now created a foundation upon which you can build a powerful and versatile tool. By combining your knowledge of TypeScript, HTML, CSS, and JavaScript, you have the power to create a customized code editor that caters to your unique needs. As you continue to experiment and refine your code, you’ll gain a deeper understanding of web development principles and the intricacies of code editing. The ability to write, test, and run code directly in your browser provides a dynamic and accessible way to learn, experiment, and streamline your workflow. Embrace the possibilities, and continue to explore the endless opportunities that web development offers.