TypeScript Tutorial: Building a Simple Interactive Code Snippet Runner

In the world of web development, sharing and experimenting with code snippets is a common practice. Whether you’re a seasoned developer or just starting out, the ability to quickly test and share small code examples can significantly speed up your learning and debugging process. This tutorial will guide you through building a simple, yet functional, interactive code snippet runner using TypeScript, HTML, and JavaScript. We’ll focus on creating a user-friendly interface where users can input code, execute it, and see the results instantly.

Why Build a Code Snippet Runner?

There are several compelling reasons to build your own code snippet runner:

  • Learning and Experimentation: It provides a sandbox environment to test out new code, learn TypeScript syntax, and understand how different code snippets behave.
  • Rapid Prototyping: Quickly test ideas without setting up a full development environment.
  • Sharing and Collaboration: Easily share code snippets with others and allow them to run and experiment with them directly.
  • Educational Tool: A valuable tool for teaching and learning programming concepts.

This project is perfect for beginners because it introduces fundamental web development concepts in a practical, hands-on way. You’ll learn about HTML structure, JavaScript execution, and the benefits of using TypeScript for type safety and code organization.

Setting Up Your Development Environment

Before we dive into the code, let’s set up our development environment. You’ll need the following:

  • A Text Editor: VS Code, Sublime Text, or any editor of your choice.
  • Node.js and npm (Node Package Manager): Used for managing project dependencies and running the TypeScript compiler. Download from nodejs.org.
  • TypeScript Compiler: We’ll install this globally using npm.

Let’s install the TypeScript compiler globally by running the following command in your terminal:

npm install -g typescript

Verify the installation by checking the TypeScript version:

tsc -v

Project Structure

Create a new project directory and navigate into it using your terminal. We’ll structure our project as follows:

code-snippet-runner/
├── index.html
├── src/
│   └── app.ts
├── tsconfig.json
└── package.json
  • index.html: The main HTML file, which will contain the structure of our code snippet runner.
  • src/app.ts: The TypeScript file where we’ll write our application logic.
  • tsconfig.json: The TypeScript configuration file.
  • package.json: For managing dependencies (though we won’t have any in this simple project).

Creating the HTML Structure (index.html)

Let’s create the basic HTML structure for our code snippet runner. Open index.html and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Code Snippet Runner</title>
    <style>
        body {
            font-family: sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
        }

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

        textarea, #output {
            width: 100%;
            padding: 10px;
            margin-bottom: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-family: monospace;
            resize: vertical;
        }

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

        button:hover {
            background-color: #3e8e41;
        }
    </style>
</head>
<body>
    <div class="container">
        <h2>Code Snippet Runner</h2>
        <textarea id="code" rows="10" placeholder="Enter your code here..."></textarea>
        <button id="runButton">Run Code</button>
        <div id="output"></div>
    </div>
    <script src="./dist/app.js"></script>
</body>
</html>

This HTML provides the basic structure: a text area for the code input, a button to run the code, and a div to display the output. We’ve also included some basic CSS for styling.

Configuring TypeScript (tsconfig.json)

The tsconfig.json file tells the TypeScript compiler how to compile your TypeScript code. Create this file in the root of your project and add the following configuration:

{
  "compilerOptions": {
    "target": "es5",
    "module": "amd",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

Let’s break down the key options:

  • target: "es5": Specifies the JavaScript version to compile to. ES5 is widely supported by browsers.
  • module: "amd": Specifies the module system to use. AMD is suitable for use in the browser without a module bundler.
  • outDir: "./dist": Specifies the output directory for the compiled JavaScript files.
  • strict: true: Enables strict type checking, which is highly recommended for catching errors early.
  • esModuleInterop: true: Enables interoperability between CommonJS and ES modules.
  • skipLibCheck: true: Skips type checking of declaration files (e.g., those from node_modules) to speed up compilation.
  • forceConsistentCasingInFileNames: true: Enforces consistent casing for file names.
  • include: ["src/**/*"]: Specifies which files to include in the compilation (all files in the src directory).

Writing the TypeScript Code (app.ts)

Now, let’s write the TypeScript code that will handle the code execution. Open src/app.ts and add the following code:


// 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 execute the code
function runCode() {
    if (!codeTextArea || !outputDiv) {
        console.error('Code area or output div not found.');
        return;
    }

    try {
        // Clear previous output
        outputDiv.innerHTML = '';

        // Get the code from the textarea
        const code = codeTextArea.value;

        // Evaluate the code.  Be very careful with eval in real-world applications.
        // Consider using a safer sandboxing approach for production.
        const result = eval(code);

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

// Add event listener to the button
if (runButton) {
    runButton.addEventListener('click', runCode);
}

Let’s break down the code:

  • Element Selection: We get references to the HTML elements using document.getElementById() and cast them to their respective types (HTMLTextAreaElement, HTMLButtonElement, and HTMLDivElement). This is important for type safety.
  • runCode Function: This is the core function that executes the code.
    • It first checks if the necessary elements exist.
    • It clears any previous output in the outputDiv.
    • It retrieves the code from the codeTextArea.
    • Important Security Note: It uses eval() to execute the code. Be extremely cautious when using eval(). It can be a security risk if you’re allowing users to input arbitrary code, as it can potentially execute malicious code. For production environments, consider using a safer sandboxing approach, such as a web worker or a secure code execution environment. For this tutorial, we’re using it for simplicity, but always prioritize security in real-world applications.
    • It displays the result of the code execution in the outputDiv.
    • It includes error handling to catch and display any errors that occur during execution.
  • Event Listener: We add an event listener to the runButton, so when the button is clicked, the runCode function is executed.

Compiling the TypeScript Code

Now, we need to compile the TypeScript code into JavaScript. Open your terminal, navigate to your project directory, and run the following command:

tsc

This command will use the tsconfig.json file to compile the src/app.ts file and create a dist/app.js file.

Running the Code Snippet Runner

Open index.html in your web browser. You should see the code snippet runner interface. Type some JavaScript code into the text area (e.g., console.log('Hello, world!'); or 2 + 2;), and click the “Run Code” button. The output should appear below the button.

Example Usage and Testing

Let’s test our code snippet runner with a few examples:

Example 1: Basic Output

Enter the following code into the text area:

console.log('Hello, Code Runner!');

Click “Run Code”. You should see nothing in the output, because console.log outputs to the browser’s console, not the output div. But the code *did* execute.

Now enter the following code:

"Hello, Code Runner!"

Click “Run Code”. You should see “Hello, Code Runner!” in the output.

Example 2: Simple Calculation

Enter the following code:

2 + 2;

Click “Run Code”. The output should be 4.

Example 3: Variable Assignment and Output

Enter the following code:

const message = "TypeScript is cool!";
message;

Click “Run Code”. The output should be TypeScript is cool!.

Example 4: Error Handling

Enter the following code:

console.log(undefinedVariable);

Click “Run Code”. You should see an error message like “Error: undefinedVariable is not defined” in the output.

Adding TypeScript Features

While our current code runner works, we can enhance it by leveraging TypeScript’s features. Let’s make a few improvements:

1. Type Checking the Input Code

Currently, the code is executed directly without any type checking. We can add a simple type checking mechanism using the TypeScript compiler API. This will help catch errors *before* the code is even executed.

First, we need to install the TypeScript compiler as a project dependency. In your terminal, run:

npm install typescript --save-dev

Next, modify src/app.ts to include a basic type checking mechanism. We’ll use the TypeScript compiler API to check the code before evaluating it. Replace the `runCode` function with the following (or add the new code inside the original function):


import * as ts from 'typescript';

function runCode() {
    if (!codeTextArea || !outputDiv) {
        console.error('Code area or output div not found.');
        return;
    }

    try {
        outputDiv.innerHTML = '';
        const code = codeTextArea.value;

        // Type Checking
        const compilerOptions: ts.CompilerOptions = {
            target: ts.ScriptTarget.ES5,
            module: ts.ModuleKind.CommonJS,
            noEmitOnError: true, // Prevents emitting the JavaScript if there are errors
        };
        const program = ts.createProgram(['./temp.ts'], compilerOptions);
        const emitResult = program.emit(
            undefined,
            (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
                // This function is called for each file written during emit.
                // We won't actually write any files in this example.
            },
            undefined,
            false,
            undefined,
            ['./temp.ts']
        );

        if (emitResult.diagnostics.length > 0) {
            // Display type errors
            const errors = emitResult.diagnostics.map(diagnostic => {
                if (diagnostic.file) {
                    const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
                    const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, 'n');
                    return `Line ${line + 1}, Column ${character + 1}: ${message}`;
                } else {
                    return ts.flattenDiagnosticMessageText(diagnostic.messageText, 'n');
                }
            });
            outputDiv.textContent = "Type Errors:n" + errors.join('n');
            return; // Stop execution if there are type errors
        }

        // If no type errors, execute the code.
        const result = eval(code);

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

Explanation of the type checking code:

  • Import TypeScript API: import * as ts from 'typescript'; imports the TypeScript compiler API.
  • Compiler Options: Defines the compiler options for type checking. We set `noEmitOnError: true` to prevent JavaScript output if there are type errors.
  • Create Program: Creates a TypeScript program from the input code. We are creating a temporary file ‘./temp.ts’ to hold the code for the type checking.
  • Emit: Calls the `emit` function, which performs the type checking.
  • Diagnostics: Checks for diagnostics (errors) from the emit result.
  • Error Display: If there are type errors, it formats and displays them in the output div.
  • Conditional Execution: Only executes the code if no type errors are found.

To make the type checking work, we need a dummy file that the compiler will use. Create a file named `temp.ts` in your project root, and add the following content:

// This file is used for type checking the code entered in the textarea.
// It will be replaced by the user's code during compilation.

Now, when the user enters code and clicks “Run Code”, the code will first be type-checked. If there are any type errors, they will be displayed in the output area, and the code will not be executed. If there are no type errors, the code will be executed.

2. Adding Syntax Highlighting

Syntax highlighting can greatly improve the readability of the code. We can easily integrate a library like Prism.js or highlight.js to achieve this. Let’s use Prism.js for simplicity. First, install Prism.js:

npm install prismjs --save

Now, include the Prism.js CSS and JavaScript files in your index.html file. Add the following lines within the <head> tag:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" integrity="sha512-tN7Ec6zAFaVqW94503ZyFXOy1GDOIar0Uv0JVj2z9vG0L1H6zO59gACW6gOP/5aWKU5z92M5c3/t9e1C/tN+lQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />

And add the following lines *before* the closing </body> tag:

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js" integrity="sha512-7UdF/c13h1P7Vj3q+Kqj6x6V7t8N9b1v3i5I/i2I9zG/k3/c60+h1i5f9R2j6h/QkX+yWp0zN+mSj5vR+N6w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-jD96+Wj4U5B9X9bJ9u+6p5a0V5C0j/j6U5W+1p1s6j53Wj9g07J5mQ6qHjI9D4p1H+8O1/v68c9p/w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Now, modify the runCode function in src/app.ts to apply syntax highlighting to the code in the output div. Modify the section where the result is displayed, to add the `language-javascript` class to the output:


        // Display the result
        if (result !== undefined) {
            outputDiv.innerHTML = `<pre><code class="language-javascript">${String(result)}

`;
Prism.highlightAll();
}

This code does the following:

  • Wraps the output in a <pre> and <code> tag with the class language-javascript. This tells Prism.js to highlight the code as JavaScript.
  • Calls Prism.highlightAll() to apply syntax highlighting to the entire output.

After these changes, recompile your TypeScript code (tsc) and refresh your browser. The output in the output div should now be syntax-highlighted.

3. Adding Code Completion (Optional)

Adding code completion can significantly improve the user experience. Libraries like CodeMirror or Monaco Editor (used by VS Code) can be integrated for this purpose. However, this is a more advanced topic and beyond the scope of this beginner tutorial. If you’re interested, research those libraries and how to integrate them.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to fix them when building a code snippet runner:

  • Error: `eval` is not defined.
  • Cause: This usually happens if you’re trying to use `eval` in an environment where it’s not available (e.g., inside a web worker if you try to use it directly).

    Fix: Make sure you are running the code in a standard browser environment. Ensure your JavaScript file is correctly linked in your HTML.

  • Error: Incorrect HTML element selection.
  • Cause: Typographical errors in the element IDs, or the elements not being present in the HTML.

    Fix: Double-check the element IDs in your TypeScript code and ensure they match the IDs in your HTML. Use the browser’s developer tools (usually accessed by pressing F12) to inspect the HTML and verify that the elements exist.

  • Error: Type errors during compilation.
  • Cause: Incorrect TypeScript syntax, type mismatches, or missing type definitions.

    Fix: Read the error messages from the TypeScript compiler carefully. They usually provide helpful information about the location and cause of the error. Use your text editor’s features (such as auto-complete and type hints) to catch errors early. Make sure that you have installed the necessary type definition files for any third-party libraries you’re using. If you have an error in a library, you can install the type definition with `npm install @types/[library-name]`

  • Error: Security vulnerabilities with eval.
  • Cause: Using eval to execute untrusted code can expose your application to security risks. Malicious users can inject code that can harm your system.

    Fix: For production environments, avoid using eval. Consider using a safer sandboxing approach, such as a web worker or a secure code execution environment. If you must use eval, carefully sanitize and validate the code before execution.

  • Error: Syntax highlighting not working.
  • Cause: Incorrect inclusion of Prism.js files, CSS issues, or incorrect use of Prism.js classes.

    Fix: Verify that you have correctly included the Prism.js CSS and JavaScript files in your HTML. Check the browser’s developer console for any errors related to Prism.js. Ensure that you’re using the correct language class (e.g., language-javascript) for the code you want to highlight. Make sure you are calling Prism.highlightAll() after the code is added to the output div.

Summary and Key Takeaways

In this tutorial, we’ve built a simple, interactive code snippet runner using TypeScript, HTML, and JavaScript. We’ve covered the following key concepts:

  • Project Setup: How to set up a basic TypeScript project.
  • HTML Structure: Creating the HTML elements for the code input, output, and button.
  • TypeScript Code: Writing TypeScript code to handle user input, execute the code using eval() (with security considerations), and display the output.
  • Type Checking: Adding a basic type checking mechanism using the TypeScript compiler API.
  • Syntax Highlighting: Integrating Prism.js to enhance code readability.
  • Error Handling: Handling potential errors during code execution.

This project provides a solid foundation for understanding how to build interactive web applications with TypeScript. You can expand upon this by adding features such as code completion, saving and loading snippets, and supporting different programming languages. Remember to always prioritize security and consider the implications of using eval() in your code.

FAQ

  1. Why use TypeScript instead of JavaScript? TypeScript offers several benefits, including static typing, which helps catch errors early, improved code organization, and better code maintainability. TypeScript code compiles to JavaScript, so it can run in any browser.
  2. Is eval() safe to use? eval() can be a security risk if used with untrusted code. It is recommended to avoid using eval() in production environments. If you must use it, carefully sanitize and validate the code before execution. Consider using safer alternatives such as web workers or sandboxed environments.
  3. How can I add code completion? You can integrate a code editor like CodeMirror or Monaco Editor to provide code completion, syntax highlighting, and other advanced features. These libraries offer APIs to interact with the code editor and provide a better coding experience.
  4. Can I support multiple programming languages? Yes, you can extend the code snippet runner to support multiple languages by using different syntax highlighting libraries (like Prism.js) and by modifying the eval() function (or a safer execution environment) to handle different language interpreters or compilers.
  5. How can I deploy this application? You can deploy this application by hosting the HTML, JavaScript, and CSS files on a web server, such as Netlify, Vercel, or GitHub Pages.

This simple code snippet runner is more than just a tool; it’s a gateway to understanding the core principles of web development and the power of TypeScript. By experimenting with different code snippets, you’ll not only enhance your coding skills but also gain a deeper appreciation for how web applications work. As you continue to build and refine this project, remember that the most valuable lesson is the journey of learning and discovery. Embrace the challenges, celebrate the successes, and keep exploring the endless possibilities of coding!