TypeScript Tutorial: Building a Simple Interactive Markdown Editor with Syntax Highlighting

Markdown is a lightweight markup language that allows you to format text using a simple syntax. It’s widely used for writing documentation, blog posts, and more. Wouldn’t it be great to have a simple, interactive Markdown editor right in your browser? This tutorial will guide you through building one using TypeScript, a superset of JavaScript that adds static typing. We’ll cover everything from setting up your project to implementing features like live preview and syntax highlighting. This project is ideal for beginners to intermediate developers looking to deepen their understanding of TypeScript and front-end development principles.

Why Build a Markdown Editor?

There are several reasons why building a Markdown editor is a valuable learning experience:

  • Practical Application: Markdown editors are useful tools. You can immediately put what you learn into practice.
  • TypeScript Fundamentals: You’ll reinforce your understanding of TypeScript’s core concepts, like types, interfaces, and classes.
  • Front-End Development: You’ll gain experience with HTML, CSS, and JavaScript (through TypeScript).
  • Problem Solving: You’ll learn how to break down a complex task (creating an editor) into smaller, manageable steps.

Setting Up Your Project

Let’s get started! First, create a new project directory and navigate into it using your terminal:

mkdir markdown-editor
cd markdown-editor

Next, initialize a new Node.js project. This will create a package.json file, which manages your project’s dependencies.

npm init -y

Now, install TypeScript and a few other necessary packages. We’ll use Parcel for bundling (making it easier to deploy), and highlight.js for syntax highlighting.

npm install typescript parcel-bundler highlight.js --save-dev

Create a tsconfig.json file to configure TypeScript. You can generate a basic one using the TypeScript compiler:

npx tsc --init

Open tsconfig.json and make the following adjustments. These settings are recommended for this project:

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

This configuration specifies that TypeScript should compile to ES5 JavaScript, use CommonJS modules, output the compiled files to a dist directory, and enable strict type checking. The include array specifies which files to include in the compilation (we’ll create a src directory soon). The exclude array prevents the compilation of node_modules.

Project Structure

Create the following directory structure in your project:


markdown-editor/
├── src/
│   ├── index.html
│   ├── index.ts
│   └── style.css
├── package.json
├── tsconfig.json
└── .gitignore

In your root directory, create a .gitignore file and add node_modules and dist to it. This will prevent these directories from being committed to your version control system.

Writing the HTML

Open src/index.html and add the basic HTML structure:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown Editor</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <div class="editor-container">
            <textarea id="editor" placeholder="Write your Markdown here..."></textarea>
        </div>
        <div class="preview-container">
            <div id="preview"></div>
        </div>
    </div>
    <script src="index.ts"></script>
</body>
</html>

This HTML sets up two main areas: an editor (a textarea) where the user will write Markdown, and a preview area (a div) where the formatted output will be displayed. It also links to a CSS file (style.css) and a JavaScript file (index.ts).

Styling with CSS

Open src/style.css and add some basic styles to make the editor visually appealing:

body {
    font-family: sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f4f4f4;
}

.container {
    display: flex;
    height: 100vh;
}

.editor-container {
    flex: 1;
    padding: 20px;
}

.preview-container {
    flex: 1;
    padding: 20px;
    border-left: 1px solid #ccc;
    background-color: #fff;
    overflow-y: scroll;
}

textarea {
    width: 100%;
    height: 90%;
    padding: 10px;
    font-size: 16px;
    border: 1px solid #ccc;
    resize: vertical;
}

#preview {
    padding: 10px;
    font-size: 16px;
    line-height: 1.6;
}

/* Syntax highlighting styles (will be added dynamically) */
.hljs {
  padding: 1em;
  border-radius: 5px;
}

This CSS provides a basic layout and styling for the editor and preview areas. It also includes a placeholder for syntax highlighting styles (.hljs), which we’ll populate later.

Writing the TypeScript Code

Now, let’s write the core TypeScript code in src/index.ts. This will handle the Markdown processing, live preview, and syntax highlighting.

import { marked } from 'marked';
import hljs from 'highlight.js';

// Get references to the editor and preview elements
const editor = document.getElementById('editor') as HTMLTextAreaElement;
const preview = document.getElementById('preview') as HTMLDivElement;

// Function to update the preview
const updatePreview = () => {
  const markdownText = editor.value;
  // Use marked to convert Markdown to HTML
  const html = marked(markdownText, {
    // Enable GFM (GitHub Flavored Markdown) features
    gfm: true,
    // Use highlight.js for syntax highlighting
    highlight: (code, lang) => {
      if (lang && hljs.getLanguage(lang)) {
        try {
          return hljs.highlight(code, { language: lang }).value;
        } catch (error) {
          console.error(error);
        }
      }
      return code;
    },
  });

  // Set the HTML of the preview element
  preview.innerHTML = html;
};

// Add event listener to the editor to update the preview on input
editor.addEventListener('input', updatePreview);

// Initialize the preview with any existing content in the editor
updatePreview();

Let’s break down this code:

  • Imports: We import the marked library (for Markdown processing) and highlight.js (for syntax highlighting). You’ll need to install the marked library: npm install marked.
  • Element References: We get references to the textarea (editor) and the div (preview) elements from the HTML using document.getElementById(). The as HTMLTextAreaElement and as HTMLDivElement are type assertions, telling TypeScript the expected type of the elements.
  • updatePreview Function: This function is the heart of the editor. It does the following:
    • Gets the Markdown text from the editor’s value.
    • Uses the marked library to convert the Markdown to HTML. The options object passed to marked enables GitHub Flavored Markdown (GFM) features and configures syntax highlighting using highlight.js.
    • Sets the HTML of the preview element to the generated HTML.
  • Event Listener: We add an event listener to the editor element. The input event fires whenever the content of the textarea changes. When this event occurs, the updatePreview function is called.
  • Initial Preview: We call updatePreview() once when the page loads to display any existing content in the editor.

Integrating Syntax Highlighting

The code above includes the syntax highlighting functionality, but we need to import a stylesheet for highlight.js and apply the necessary styles. First, install a theme for highlight.js. You can choose from a variety of themes. Let’s use the ‘github-dark’ theme as an example:

npm install highlight.js --save

Then, import the theme in your index.ts file. Add this line at the top of your index.ts file:

import 'highlight.js/styles/github-dark.css';

This imports the CSS for the github-dark theme. You can change this to any other theme you prefer. You can find a list of available themes in the node_modules/highlight.js/styles directory. Ensure the CSS file is correctly referenced and that the highlight option is correctly configured in your marked options.

Running the Application

Now, build and run your application using Parcel:

npx parcel src/index.html

Parcel will bundle your code and start a development server. Open your browser and go to the address provided by Parcel (usually http://localhost:1234). You should see your Markdown editor! As you type Markdown in the left-hand editor, the formatted output should appear in the right-hand preview area, with syntax highlighting.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid them:

  • Incorrect File Paths: Double-check your file paths in the HTML (e.g., the <script src="index.ts"> tag) and CSS (e.g., the <link rel="stylesheet" href="style.css"> tag) to ensure they are correct. A common mistake is forgetting to specify the src directory.
  • Missing Dependencies: Make sure you have installed all the necessary dependencies using npm (marked, highlight.js, and the theme). If you’re missing a dependency, the code will not work.
  • TypeScript Errors: Carefully review any TypeScript errors in your terminal. These errors are helpful and will guide you in fixing type-related problems. Use the TypeScript compiler (tsc) to check for errors before running Parcel.
  • Incorrect Import Paths: Ensure your import paths in index.ts are correct. The path to the css theme for highlight.js is crucial.
  • Incorrect Marked Options: Double-check the configuration options passed to the marked function, especially the highlight callback function. Syntax highlighting might not work if this is not set up correctly.
  • Browser Caching: Sometimes, your browser may cache an older version of your code. If you make changes and they are not reflected in the browser, try clearing your browser’s cache or hard-refreshing the page (usually Ctrl+Shift+R or Cmd+Shift+R).

Adding More Features

This is a basic Markdown editor, but you can extend it with many more features:

  • Toolbar: Add a toolbar with buttons for common Markdown formatting options (bold, italic, headings, etc.).
  • Real-time Preview: Implement a more sophisticated real-time preview that updates as the user types, including math and diagrams.
  • Save/Load: Add functionality to save and load Markdown files from local storage or a server.
  • Themes: Allow users to select different themes for the editor and preview areas.
  • Error Handling: Implement more robust error handling and user feedback.

Key Takeaways

In this tutorial, you’ve learned how to build a basic Markdown editor with TypeScript. You’ve covered the following key concepts:

  • Setting up a TypeScript project with Parcel.
  • Writing HTML and CSS for the editor’s user interface.
  • Using the marked library to convert Markdown to HTML.
  • Integrating highlight.js for syntax highlighting.
  • Handling user input and updating the preview in real-time.
  • Understanding and fixing common mistakes.

FAQ

  1. Why use TypeScript instead of JavaScript? TypeScript adds static typing to JavaScript, which helps catch errors early, improves code readability, and makes it easier to maintain larger projects.
  2. What is Parcel? Parcel is a zero-configuration web application bundler. It simplifies the build process by automatically handling dependencies and bundling your code for deployment.
  3. How do I deploy this editor? You can deploy your editor by building it with Parcel (npx parcel build src/index.html) and then deploying the contents of the dist directory to a web server or hosting platform.
  4. Can I use a different Markdown library? Yes, you can. There are other Markdown libraries available, such as Showdown. The core principles of the editor would remain the same, but you would need to adjust the code to use the new library’s API.
  5. How can I improve the performance of the editor? For very large Markdown documents, consider techniques like debouncing the updatePreview function (to reduce the frequency of updates) and optimizing the syntax highlighting process.

Building a Markdown editor is a fantastic way to learn and apply your TypeScript skills. By experimenting with the code, adding new features, and refining the user interface, you can create a powerful and personalized tool. Remember that the journey of learning is continuous, so keep exploring, experimenting, and building!