TypeScript Tutorial: Building a Simple Web-Based Code Runner

In the world of web development, the ability to quickly test and execute code snippets without leaving your browser is invaluable. Whether you’re a beginner learning the ropes or an experienced developer experimenting with new libraries, a web-based code runner can significantly boost your productivity. This tutorial will guide you through building a simple, yet functional, code runner using TypeScript, a language that adds static typing to JavaScript, making your code more robust and easier to maintain. We’ll cover everything from setting up your development environment to handling user input and displaying output.

Why Build a Code Runner?

Imagine you’re trying to understand a new JavaScript function or library. Instead of constantly switching between your code editor, a terminal, and a browser console, a code runner allows you to write, execute, and see the results all in one place. This immediate feedback loop is crucial for learning and experimenting. Furthermore, a web-based code runner provides accessibility. You can access it from any device with a browser, making it a convenient tool for coding on the go or collaborating with others.

Prerequisites

Before we dive in, let’s make sure you have the necessary tools installed:

  • Node.js and npm (Node Package Manager): These are essential for managing project dependencies and running TypeScript code. You can download them from nodejs.org.
  • A Code Editor: Choose your favorite code editor, such as Visual Studio Code, Sublime Text, or Atom.
  • Basic HTML, CSS, and JavaScript knowledge: While this tutorial focuses on TypeScript, some familiarity with these web technologies is helpful.

Setting Up Your Project

Let’s start by creating a new project directory and initializing it with npm:

mkdir typescript-code-runner
cd typescript-code-runner
npm init -y

This will create a package.json file, which will store information about your project and its dependencies.

Installing TypeScript

Next, install TypeScript as a development dependency:

npm install typescript --save-dev

This command downloads and installs the TypeScript compiler (tsc) and saves it as a development dependency in your package.json file.

Creating the TypeScript Configuration File

To configure the TypeScript compiler, create a tsconfig.json file in your project root. You can do this by running the following command:

npx tsc --init

This command generates a tsconfig.json file with default settings. You can customize these settings to suit your project’s needs. For our code runner, we’ll make a few adjustments. Open tsconfig.json in your code editor and modify the following settings:

{
  "compilerOptions": {
    "target": "es5",  // or "es6", "esnext" depending on your needs
    "module": "commonjs", // or "esnext" if you are using modules
    "outDir": "./dist", // Specifies where to output the compiled JavaScript files
    "strict": true, // Enables strict type checking
    "esModuleInterop": true, // Enables interoperability between CommonJS and ES Modules
    "skipLibCheck": true, // Skips type checking of declaration files
    "forceConsistentCasingInFileNames": true // Enforces consistent casing
  },
  "include": ["src/**/*"]
}

Here’s a breakdown of what these options do:

  • target: Specifies the JavaScript version to compile to. es5 is widely supported, but you can choose a newer version if you prefer.
  • module: Specifies the module system to use. commonjs is suitable for Node.js, while esnext is often used with modern JavaScript bundlers.
  • outDir: Defines the output directory for the compiled JavaScript files.
  • strict: Enables strict type checking, which helps catch errors early.
  • esModuleInterop: Enables interoperability between CommonJS and ES Modules.
  • skipLibCheck: Skips type checking of declaration files.
  • forceConsistentCasingInFileNames: Enforces consistent casing in file names.
  • include: Specifies the files and directories to include in the compilation.

Project Structure

Let’s create the basic project structure:

mkdir src dist
touch src/index.ts
touch index.html

Your project directory should now look something like this:

typescript-code-runner/
├── dist/
├── src/
│   └── index.ts
├── index.html
├── package.json
└── tsconfig.json

Building the HTML Structure

Now, let’s create the HTML structure for our code runner in index.html. This will include a text area for the user to enter code, a button to run the code, and an area to display the output.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TypeScript Code Runner</title>
    <style>
        body {
            font-family: sans-serif;
            margin: 20px;
        }
        textarea {
            width: 100%;
            height: 200px;
            margin-bottom: 10px;
        }
        button {
            padding: 10px 20px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        #output {
            margin-top: 10px;
            padding: 10px;
            border: 1px solid #ccc;
            white-space: pre-wrap;
        }
    </style>
</head>
<body>
    <textarea id="code" placeholder="Enter your TypeScript code here..."></textarea>
    <button id="runButton">Run Code</button>
    <div id="output"></div>
    <script src="dist/index.js"></script>
</body>
</html>

This HTML provides a basic layout with a text area for the code, a button to trigger execution, and a div to display the output. It also includes some basic CSS for styling.

Writing the TypeScript Code

Now, let’s write the TypeScript code that will handle user input, compile the code, and display the output in src/index.ts.


// Get references to HTML elements
const codeTextArea = document.getElementById('code') as HTMLTextAreaElement;
const runButton = document.getElementById('runButton') as HTMLButtonElement;
const outputDiv = document.getElementById('output') as HTMLDivElement;

// Function to run the code
const runCode = () => {
  if (!codeTextArea || !outputDiv) {
    console.error('Code area or output div not found.');
    return;
  }

  const code = codeTextArea.value;
  outputDiv.textContent = ''; // Clear previous output

  try {
    // Use eval() to execute the code.  This is generally not recommended in production, but
    // is acceptable in a controlled, isolated environment like our code runner.
    const result = eval(code);

    // Display the result in the output div
    if (result !== undefined) {
      outputDiv.textContent = String(result);
    }
  } catch (error: any) {
    // Handle errors and display them in the output div
    outputDiv.textContent = `Error: ${error.message}`;
    console.error(error);
  }
}

// Add an event listener to the run button
runButton?.addEventListener('click', runCode);

console.log('Code runner initialized.');

Let’s break down this code:

  • Getting HTML Elements: We start by getting references to the text area, run button, and output div using their respective IDs. The as HTMLTextAreaElement, as HTMLButtonElement, and as HTMLDivElement are type assertions, telling TypeScript the expected type of the elements.
  • runCode Function: This function is the core of our code runner. It does the following:
    • Gets the code from the text area.
    • Clears the previous output.
    • Uses the eval() function to execute the code. This is a powerful function that can execute JavaScript code represented as a string. Important: Using eval() can be dangerous if you’re not careful. In a production environment, you should avoid it and consider safer alternatives like a sandboxed environment. However, for a simple code runner like this, where the user inputs the code, it’s acceptable.
    • Displays the result of the execution in the output div. If the code returns a value (e.g., a number, a string, an object), it will be displayed.
    • Catches any errors that occur during execution and displays the error message in the output div.
  • Event Listener: An event listener is added to the run button. When the button is clicked, the runCode function is executed.

Compiling and Running the Application

Now that we have the TypeScript code and the HTML, let’s compile the TypeScript code and run the application.

Compiling the TypeScript Code

Open your terminal and navigate to your project directory. Run the following command to compile the TypeScript code:

tsc

This command uses the TypeScript compiler (tsc) to transpile the src/index.ts file into JavaScript and places the output in the dist/index.js file, as specified in your tsconfig.json.

Running the Application

Open index.html in your web browser. You should see the code runner interface with the text area, run button, and output area. Type some TypeScript code into the text area, such as console.log('Hello, world!');, and click the “Run Code” button. You should see “Hello, world!” printed in the output area.

Enhancements and Features

Our basic code runner is functional, but we can enhance it with more features to make it more useful. Here are some ideas:

Syntax Highlighting

Adding syntax highlighting can significantly improve the readability of the code. You can use a library like Prism.js or highlight.js to achieve this. These libraries analyze the code and apply different styles to different parts of the code (keywords, comments, strings, etc.)

  1. Include the Library: Add the CSS and JavaScript files for your chosen syntax highlighting library in the <head> of your index.html file.
  2. Apply Highlighting: After the user enters the code, use the library’s functions to highlight the code in the text area or a separate code display element.

Example using Prism.js:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TypeScript Code Runner</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" integrity="sha512-tN7Ec6zAFaVqW9swEg6WYu2ix9qPdyTz1mOgHep9o/qn5PEfs6TzRUxSkv5LsPJVMVjO19Z2FMlbb/vL79+gvA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <style>
        /* ... (existing styles) ... */
        .prism {
            padding: 10px;
            border: 1px solid #ccc;
            overflow: auto; /* Enable scrolling if code is too long */
        }
    </style>
</head>
<body>
    <textarea id="code" placeholder="Enter your TypeScript code here..."></textarea>
    <button id="runButton">Run Code</button>
    <div id="output"></div>
    <pre class="prism language-typescript" id="highlightedCode"></pre>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js" integrity="sha512-7UDW6p6qW05d76D98Qn+q/U5689Q2b/pBf1Vq8+6qW13/N6d00W0r9Xf9a/5w6pY0r/5fW+z9A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="dist/index.js"></script>
</body>
</html>

// ... (existing code) ...
const highlightedCode = document.getElementById('highlightedCode') as HTMLElement;

const runCode = () => {
  if (!codeTextArea || !outputDiv || !highlightedCode) {
    console.error('Code area, output div, or highlighted code element not found.');
    return;
  }

  const code = codeTextArea.value;
  outputDiv.textContent = '';
  highlightedCode.textContent = code;

  Prism.highlightElement(highlightedCode);

  try {
    const result = eval(code);

    if (result !== undefined) {
      outputDiv.textContent = String(result);
    }
  } catch (error: any) {
    outputDiv.textContent = `Error: ${error.message}`;
    console.error(error);
  }
}

Autocompletion

Autocompletion can significantly speed up coding. Libraries like CodeMirror or Monaco Editor can provide this functionality. These libraries offer features like code completion, syntax highlighting, and more advanced editing capabilities.

  1. Include the Library: Add the library’s CSS and JavaScript files to your HTML.
  2. Replace the Text Area: Replace the standard <textarea> with the code editor component provided by the library.
  3. Configure the Editor: Configure the editor with TypeScript syntax highlighting and autocompletion features.

Error Highlighting

When errors occur, it’s helpful to highlight the lines of code where the errors are located. This often involves parsing the error messages and matching line numbers to the code in the text area. The code editor libraries mentioned above often have built-in capabilities for this.

Input/Output Redirection

Instead of just displaying the output in a simple text area, you could redirect standard output (console.log) and standard error (console.error) to the output area. This gives you more control over the output and allows you to display formatted messages.

Code Formatting

Implement a code formatter to automatically format the code entered by the user. This improves readability and consistency. Libraries like Prettier can be used for this purpose.

Saving and Loading Code

Allow users to save their code snippets and load them later. You can use local storage or a backend database for this functionality.

Common Mistakes and How to Fix Them

Incorrect HTML Element References

One common mistake is incorrectly referencing HTML elements. Make sure the IDs you use in your TypeScript code match the IDs in your HTML. Use the browser’s developer tools (right-click, “Inspect”) to verify that the elements exist and that you’re referencing them correctly.

Fix: Double-check the IDs in your HTML and TypeScript code. Use type assertions (as HTMLTextAreaElement, etc.) to help the compiler catch potential type errors.

Syntax Errors in TypeScript

TypeScript, being a typed language, can be strict. Syntax errors can prevent your code from compiling. Make sure your code follows TypeScript syntax rules. Use your code editor’s features (e.g., syntax highlighting, linting) to catch errors early. Also, ensure you have the correct imports.

Fix: Carefully review the TypeScript compiler’s error messages. These messages often point directly to the line and the nature of the error. Fix the syntax errors and recompile.

Incorrect JavaScript Execution

The eval() function can sometimes behave unexpectedly. Make sure the code you’re executing is valid JavaScript and that you understand the scope and context of the execution. If you are using external libraries, make sure they are imported correctly and are available in the execution environment.

Fix: Thoroughly test your code snippets and handle potential errors in the try...catch block. Consider using a safer alternative to eval() if possible, especially in production environments.

Incorrect Module Imports

When working with modules, incorrect imports can lead to runtime errors. Ensure that you are importing modules correctly and that the paths are accurate. If you are using a bundler (like Webpack or Parcel), make sure your module configuration is correct.

Fix: Review your import statements and ensure that the module paths and names are correct. Check your bundler’s configuration if you are using one.

Key Takeaways

  • TypeScript for Robustness: Using TypeScript adds static typing to your code, making it more reliable and easier to maintain.
  • HTML Structure: The HTML provides the basic layout for the code runner, including the text area, button, and output area.
  • JavaScript Execution: The eval() function allows you to execute JavaScript code dynamically. However, use it with caution and in a controlled environment.
  • Error Handling: Implement error handling to gracefully handle any issues that arise during code execution.
  • Extensibility: The basic code runner can be extended with features like syntax highlighting, autocompletion, and more.

FAQ

1. Can I use this code runner in a production environment?

While the code runner is great for learning and experimentation, using eval() directly in a production environment is generally not recommended due to security risks. Consider using a sandboxed environment or a more controlled execution method.

2. How can I add syntax highlighting?

You can integrate syntax highlighting libraries like Prism.js or highlight.js. Include the library’s CSS and JavaScript files in your HTML and use the library’s functions to highlight the code in the text area or a separate code display element.

3. How do I handle errors in the code runner?

Wrap the code execution in a try...catch block to catch any errors. Display the error message in the output area so the user can see what went wrong.

4. Can I save and load code snippets?

Yes, you can add functionality to save and load code snippets using local storage or a backend database. This allows users to store and retrieve their code.

5. What are the alternatives to eval()?

Alternatives to eval() include using a sandboxed environment like a web worker or a code execution service that provides a more secure way to run code. Consider using a library that parses and interprets the code instead of directly executing it.

Building a web-based code runner in TypeScript is a great way to learn about web development and TypeScript. By following this tutorial, you’ve learned the fundamentals of building such an application. You can now experiment with different features, such as syntax highlighting and autocompletion, to make your code runner even more powerful and user-friendly. Remember to always prioritize code security and user experience when developing your application. With a solid understanding of the basics, you can build a versatile tool that enhances your coding workflow and helps you learn and experiment with code more effectively.