TypeScript Tutorial: Building a Simple Web Application for a Markdown Previewer

Markdown is a lightweight markup language with plain text formatting syntax. It’s widely used for creating formatted text documents, websites, and more, because of its simplicity and readability. But what if you could see the formatted output of your Markdown text instantly as you type? That’s where a Markdown previewer comes in handy. In this tutorial, we’ll build a simple web application using TypeScript that allows you to type Markdown and see its rendered HTML output in real time.

Why Build a Markdown Previewer?

A Markdown previewer is a valuable tool for anyone who works with Markdown. It offers several benefits:

  • Instant Feedback: You see the formatted output immediately, helping you catch errors and refine your formatting.
  • Efficiency: It saves time by eliminating the need to repeatedly save, preview, and edit your Markdown.
  • Learning Tool: It provides a visual representation of how Markdown syntax translates into HTML, which is helpful for learning and mastering Markdown.

Prerequisites

Before we begin, make sure you have the following installed:

  • Node.js and npm: We’ll use npm (Node Package Manager) to manage our project dependencies. You can download Node.js from nodejs.org.
  • A Code Editor: Such as Visual Studio Code, Sublime Text, or Atom.
  • Basic Understanding of HTML, CSS, and JavaScript: Familiarity with these technologies is helpful, but not strictly required.

Setting Up the Project

Let’s get started by setting up our project. Open your terminal or command prompt and follow these steps:

  1. Create a Project Directory: Create a new directory for your project, for example, `markdown-previewer`.
  2. Initialize npm: Navigate into your project directory and run `npm init -y`. This will create a `package.json` file.
  3. Install TypeScript: Install TypeScript globally or locally. For a local installation, run `npm install –save-dev typescript`.
  4. Create `tsconfig.json`: Run `npx tsc –init` to generate a `tsconfig.json` file. This file configures the TypeScript compiler.
  5. Create Project Files: Create an `index.html` file, an `index.ts` file, and a `style.css` file in your project directory.

Project Structure

Your project directory should now look like this:

markdown-previewer/
├── index.html
├── index.ts
├── style.css
├── package.json
├── tsconfig.json
└── node_modules/ (if you installed TypeScript locally)

Writing the HTML (index.html)

Open `index.html` and add the following 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 Previewer</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <div class="input-container">
      <textarea id="markdown-input" placeholder="Enter Markdown here..."></textarea>
    </div>
    <div class="preview-container">
      <div id="preview"></div>
    </div>
  </div>
  <script src="index.js"></script>
</body>
</html>

This HTML sets up the basic layout: a textarea for Markdown input and a div to display the rendered HTML preview. It also links to your CSS and JavaScript files.

Styling with CSS (style.css)

Next, let’s add some basic styles to `style.css` to make the previewer look presentable:

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

.container {
  display: flex;
  width: 80%;
  max-width: 900px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  border-radius: 8px;
  overflow: hidden;
}

.input-container, .preview-container {
  flex: 1;
  padding: 20px;
  background-color: #fff;
}

.input-container {
  border-right: 1px solid #ddd;
}

textarea {
  width: 100%;
  height: 80vh;
  padding: 10px;
  font-size: 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
  resize: none;
}

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

/* Add styles for Markdown elements (e.g., headings, bold, italics) */
#preview h1, #preview h2, #preview h3, #preview h4, #preview h5, #preview h6 {
  margin-bottom: 0.5em;
  margin-top: 1em;
}

#preview p {
  margin-bottom: 1em;
}

#preview strong {
  font-weight: bold;
}

#preview em {
  font-style: italic;
}

#preview a {
  color: #007bff;
  text-decoration: none;
}

#preview a:hover {
  text-decoration: underline;
}

#preview img {
  max-width: 100%;
  height: auto;
  display: block;
  margin: 10px 0;
}

#preview ul, #preview ol {
  margin-bottom: 1em;
  padding-left: 20px;
}

#preview li {
  margin-bottom: 0.5em;
}

#preview code {
  background-color: #eee;
  padding: 2px 4px;
  border-radius: 3px;
  font-family: monospace;
}

#preview pre {
  background-color: #eee;
  padding: 10px;
  border-radius: 3px;
  overflow-x: auto;
}

#preview pre code {
  background-color: transparent;
  padding: 0;
  border-radius: 0;
  font-family: monospace;
}

This CSS provides a basic layout and styling for the input area, preview area, and some Markdown elements. Feel free to customize the styles to your liking.

Writing the TypeScript (index.ts)

Now, let’s write the TypeScript code that will handle the Markdown conversion and preview.

First, install the `marked` library, which we’ll use to convert Markdown to HTML:

npm install marked

Open `index.ts` and add the following code:

import { marked } from 'marked';

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

// Function to update the preview
const updatePreview = () => {
  if (markdownInput && preview) {
    const markdownText = markdownInput.value;
    const html = marked.parse(markdownText);
    preview.innerHTML = html;
  }
};

// Add an event listener to the textarea to update the preview on input
if (markdownInput) {
  markdownInput.addEventListener('input', updatePreview);
}

// Initial preview update (optional)
updatePreview();

Let’s break down this code:

  • Import `marked`: We import the `marked` library to handle the Markdown conversion.
  • Get Element References: We get references to the `textarea` (where the user types Markdown) and the `div` (where the HTML preview will be displayed) using their IDs. The `as HTMLTextAreaElement` and `as HTMLDivElement` are type assertions, telling TypeScript the expected type of the elements.
  • `updatePreview` Function: This function does the following:
    • Gets the Markdown text from the `textarea`.
    • Uses `marked.parse()` to convert the Markdown to HTML.
    • Sets the `innerHTML` of the `preview` div to the generated HTML.
  • Event Listener: We add an `input` event listener to the `textarea`. This means that every time the user types something in the textarea, the `updatePreview` function is called, updating the preview.
  • Initial Update (Optional): We call `updatePreview()` once when the page loads to initialize the preview with any existing text in the textarea.

Compiling the TypeScript

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

tsc

This command will use the `tsconfig.json` file to compile `index.ts` into `index.js` in the same directory. If you encounter any errors, carefully review your code and the error messages.

Running the Application

Open `index.html` in your web browser. You should see a two-pane layout: a textarea on the left and a preview area on the right. Start typing Markdown in the textarea, and the rendered HTML should appear in the preview area in real time. For example, try typing:

# Hello, Markdown!

This is a paragraph.

*   Item 1
*   Item 2

**Bold text** and _italic text_.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to fix them:

  • Typo in Element IDs: Make sure the IDs in your `index.html` (e.g., `markdown-input`, `preview`) match the IDs you’re using in your `index.ts` code. A simple typo can prevent your JavaScript from finding the correct elements.
  • Incorrect File Paths: Double-check that the file paths in your `index.html` (e.g., `style.css`, `index.js`) are correct relative to the HTML file.
  • Missing `marked` Import: If you forget to import `marked` at the top of your `index.ts` file, you’ll get an error. Ensure you have `import { marked } from ‘marked’;`.
  • Compilation Errors: Carefully review any error messages from the TypeScript compiler (`tsc`). These messages often pinpoint the exact line of code where the error occurred. Common errors include type mismatches, syntax errors, and missing semicolons.
  • CORS Issues: If you are serving your HTML file from a different domain than your JavaScript file, you might encounter CORS (Cross-Origin Resource Sharing) issues. This is less likely when developing locally, but it can be a problem if you deploy your application to a server. To fix this, configure CORS on your server to allow requests from your domain.

Enhancements and Features

Here are some ways you can enhance your Markdown previewer:

  • Live Preview of Code Blocks: Add syntax highlighting to code blocks using a library like Prism.js or highlight.js.
  • Image Preview: Display images directly in the preview.
  • Toolbar: Add a toolbar with buttons for common Markdown formatting options (bold, italics, headings, etc.).
  • Save/Load Functionality: Allow users to save their Markdown text to a file and load it back into the editor.
  • Themes: Implement different themes for the editor and preview areas.
  • Error Handling: Improve error handling and provide feedback to the user if there are any issues.

Key Takeaways

  • TypeScript for Type Safety: Using TypeScript provides type safety and helps catch errors during development.
  • Modular Design: Breaking down your application into smaller, reusable functions (like `updatePreview`) makes your code easier to understand and maintain.
  • Library Integration: Using libraries like `marked` can greatly simplify complex tasks, such as Markdown parsing.
  • Event-Driven Programming: Event listeners (like the `input` listener) are essential for building interactive web applications.

FAQ

Here are some frequently asked questions:

  1. Why use TypeScript? TypeScript adds static typing to JavaScript, which helps you catch errors early, improves code readability, and makes your code more maintainable.
  2. What is Markdown? Markdown is a lightweight markup language that allows you to format text using plain text syntax. It’s designed to be easy to read and write.
  3. How does the `marked` library work? The `marked` library takes Markdown text as input and converts it into HTML. It handles the parsing and formatting of Markdown syntax.
  4. How can I deploy this application? You can deploy this application by uploading the HTML, CSS, and JavaScript files to a web server or using a platform like Netlify or Vercel.
  5. Can I use this previewer with other Markdown flavors? The `marked` library supports various Markdown flavors. You may need to configure it to match the specific flavor you want to use.

In this tutorial, we’ve covered the fundamentals of building a Markdown previewer with TypeScript. This is a great starting point for anyone looking to create their own text editing tools or understand how Markdown works. You’ve learned how to integrate libraries, handle user input, and dynamically update the user interface. By building this application, you’ve gained practical experience with TypeScript, HTML, CSS, and JavaScript, and you are well on your way to building more complex web applications. The power of a real-time Markdown previewer isn’t just in the immediate feedback; it’s in the way it streamlines the writing process, allowing you to focus on the content itself, not the formatting. This efficiency and ease of use is something that can be applied to nearly any writing-based project. With the knowledge gained from this tutorial, you can now extend and customize your previewer to meet your specific needs and create a powerful and efficient writing environment.