JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
222: Building a Markdown Previewer
You've probably seen plenty of "Markdown Previewers" in junior portfolios. On the surface, they seem trivial: you take a string of text, swap some symbols for HTML tags, and shove it into a div. But if you're building this for a real production environment, there is a massive gap between a "demo that works" and a feature that won't get your company hacked.
The temptation of a few regexes
When I first started building these, my instinct was to handle the conversion myself. I'd think, "How hard can it be? I'll just use a regular expression to find two asterisks and wrap the text in a <strong> tag." It feels clever and lightweight. You avoid adding a dependency, and for a few basic test cases, it looks perfect.
const naiveParse = (text) => {
return text
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/\n/g, '<br>');
};
// Usage
previewElement.innerHTML = naiveParse(userInput);
The problem is that Markdown is a deceptively complex specification. The moment you try to handle nested lists, blockquotes, or escaped characters, your regexes will start to collide. You'll spend three days fighting edge cases that someone else already solved a decade ago. I've seen developers descend into "regex hell" trying to support just five different Markdown flavors, only to realize they've essentially written a buggy, incomplete version of a parser.
When the browser becomes a weapon
The bigger issue here isn't the lack of features—it's the security hole. In the example above, I'm using innerHTML. This is the most dangerous way to inject content into a page because it tells the browser to execute whatever HTML it finds. If your "previewer" is purely local, maybe you don't care. But the second you allow that Markdown to be saved to a database or shared with another user, you've handed over the keys to your application.
I'll give you a specific example. If a malicious user types <img src="x" onerror="alert('Hacked!')"> into your text area, your naiveParse function won't touch it because it doesn't match your bold or italic patterns. It passes straight through to innerHTML, and the browser executes that JavaScript. This is a classic Cross-Site Scripting (XSS) attack. In a real app, that alert could be a script that steals session cookies or redirects users to a phishing site.
Delegating the heavy lifting
The professional approach is to separate parsing from sanitization. First, use a battle-tested library like marked to handle the Markdown-to-HTML conversion. These libraries handle the weird edge cases of the spec so you don't have to. But—and this is the part people forget—you never trust the output of a parser. You run that HTML through a sanitizer like DOMPurify before it ever touches the DOM.
import { marked } from 'marked';
import DOMPurify from 'dompurify';
const renderMarkdown = (text) => {
// 1. Convert Markdown to HTML string
const rawHtml = marked.parse(text);
// 2. Strip out dangerous tags and attributes
const cleanHtml = DOMPurify.sanitize(rawHtml);
// 3. Now it is safe to inject
previewElement.innerHTML = cleanHtml;
};
By adding that sanitization step, you're creating a whitelist of "safe" HTML. If marked produces a <script> tag or an onerror attribute, DOMPurify simply deletes it. It's a small amount of overhead for a massive increase in reliability. I always tell my team: if you're using innerHTML with user-generated content, you should be terrified unless there's a sanitizer in the pipeline.
📋 Practical Task
Build a Sanitized Live-Preview Editor
Create a Markdown previewer that implements the professional workflow discussed in the lesson. Your application must include:
- A
textareafor user input and adivfor the preview. - Integration with the
markedlibrary to convert the input in real-time (using aninputevent listener). - Integration with
DOMPurifyto ensure that any HTML tags manually entered into the textarea (like<script>or<iframe>) are stripped out before being rendered to the preview pane. - A test case: Verify that typing
**Bold Text**renders as bold, but typing<img src=x onerror=alert(1)>does not trigger an alert.
There are no comments for now.